
AES(Advanced Encryption Standard) ๋ ๋์นญ ํค ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก, ๋ฐ์ดํฐ๋ฅผ ์ํธํํ๊ณ ๋ณตํธํํ ๋ ๋์ผํ ๋น๋ฐ ํค๋ฅผ ์ฌ์ฉํฉ๋๋ค. AES๋ 1997๋ ๋ฏธ๊ตญ ๊ตญ๊ฐํ์ค๊ธฐ์ ์ฐ๊ตฌ์(NIST)์ ์ํด ๋์ค์ ๊ณต๊ฐ๋์๊ณ , 2001๋ ์ DES(Data Encryption Standard)๋ฅผ ๋์ฒดํ์ฌ ํ์ค ๋์นญ ํค ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ์ฑํ๋์์ต๋๋ค.
AES๋ ๋ธ๋ก ์ํธํ ๋ฐฉ์์ ๋ฐ๋ฅด๋ฉฐ, ๊ณ ์ ๋ ํฌ๊ธฐ์ ๋ธ๋ก(128๋นํธ)์ ์ํธํํฉ๋๋ค. ์ด๋, ์ํธํ์ ์ฌ์ฉํ ์ ์๋ ํค ๊ธธ์ด๋ 128๋นํธ, 192๋นํธ, 256๋นํธ๋ก ์ค์ ํ ์ ์์ด, ๋ณด์ ์๊ตฌ์ ๋ฐ๋ผ ์ ํ ๊ฐ๋ฅํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก AES๋ ์๋์ ๋ณด์์ฑ ๋ฉด์์ ๋งค์ฐ ํจ์จ์ ์ด๋ฉฐ, ๊ธ์ต ์์คํ , ์ ์ ์๊ฑฐ๋, ๋ฌด์ ๋คํธ์ํฌ ๋ฑ์์ ๋๋ฆฌ ์ฌ์ฉ๋ฉ๋๋ค.
๋์นญ ์ํธํ๋ ์ํธํ์ ๋ณตํธํ์ ๋์ผํ ํค๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์์ ๋๋ค.
ES๋ ๋ฐ์ดํฐ๋ฅผ ์ํธํํ ๋, ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ 128๋นํธ ๋ธ๋ก ๋จ์๋ก ๋๋์ด ์ํธํํฉ๋๋ค. ๊ฐ ๋ธ๋ก์ ์ฌ๋ฌ ๋ฒ์ ๋ผ์ด๋(Round)๋ฅผ ๊ฑฐ์ณ ์ํธํ๋๋ฉฐ, ๋ผ์ด๋์ ํ์๋ ํค ๊ธธ์ด์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
AES-128: 10 ๋ผ์ด๋
AES-192: 12 ๋ผ์ด๋
AES-256: 14 ๋ผ์ด๋
๊ฐ ๋ผ์ด๋์์๋ ์นํ(Substitution), ์ ์น(Permutation), ํค ํ์ฅ(Key Expansion) ๋ฑ์ ์ฐ์ฐ์ด ์ด๋ฃจ์ด์ ธ ๋ฐ์ดํฐ๊ฐ ์ ์ ๋ ์ํธํ๋ฉ๋๋ค.
AES๋ ์ํธํ ๋ชจ๋์ ๋ฐ๋ผ ์ฌ๋ฌ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ํธํํ ์ ์์ต๋๋ค. ๋ํ์ ์ธ ๋ชจ๋๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
ECB (Electronic Codebook)
๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฉ์์ผ๋ก, ๋์ผํ ํ๋ฌธ ๋ธ๋ก์ด ํญ์ ๋์ผํ ์ํธ๋ฌธ ๋ธ๋ก์ผ๋ก ์ํธํ๋ฉ๋๋ค. ๋ณด์์ด ์ทจ์ฝํ๊ธฐ ๋๋ฌธ์ ์ ์ฌ์ฉ๋์ง ์์ต๋๋ค.
CBC (Cipher Block Chaining)
๊ฐ ๋ธ๋ก์ ์ํธํํ ๋ ์ด์ ๋ธ๋ก์ ์ํธ๋ฌธ๊ณผ XOR ์ฐ์ฐ์ ํ ํ ์ํธํํ๋ ๋ฐฉ์์
๋๋ค. ๋ณด์์ฑ์ด ๋ฐ์ด๋๋ฉฐ,
์ด๊ธฐํ ๋ฒกํฐ(IV)๊ฐ ํ์ํฉ๋๋ค.
GCM (Galois/Counter Mode)
๊ณ ์ ์ํธํ์ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ ๊ธฐ๋ฅ์ ๋์์ ์ ๊ณตํ๋ ๋ชจ๋์
๋๋ค. ๋ณด์์ฑ๊ณผ ์ฑ๋ฅ์ด ๋ชจ๋ ๋ฐ์ด๋๋ฉฐ, ๋คํธ์ํฌ ํต์ ์ด๋ HTTPS์์ ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค.
CTR (Counter Mode)
๊ฐ ๋ธ๋ก์ ๊ณ ์ ํ ์นด์ดํฐ ๊ฐ์ ์ฌ์ฉํ์ฌ ๋ณ๋ ฌ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ ์ํธํ ๋ฐฉ์์
๋๋ค. ์ฑ๋ฅ์ด ์ข๊ณ , ๋ค์ํ ์ฉ๋๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
๋์นญ ํค ์ํธํ
๋์ผํ ํค๋ฅผ ์ํธํ์ ๋ณตํธํ ๋ชจ๋์ ์ฌ์ฉํฉ๋๋ค. ๋ฐ๋ผ์ ํค ๊ด๋ฆฌ๊ฐ ์ค์ํฉ๋๋ค.
๊ณ ์ ๋ ๋ธ๋ก ํฌ๊ธฐ
AES๋ 128๋นํธ ๊ณ ์ ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.
ํค ๊ธธ์ด
AES๋ 128๋นํธ, 192๋นํธ, 256๋นํธ ์ธ ๊ฐ์ง ํค ๊ธธ์ด๋ฅผ ์ง์ํ์ฌ ๋ณด์ ์์ค์ ์ ์ฐํ๊ฒ ์ค์ ํ ์ ์์ต๋๋ค.
๋น ๋ฅธ ์ฒ๋ฆฌ ์๋
ํ๋์จ์ด ๋ฐ ์ํํธ์จ์ด ๋ชจ๋์์ ๋งค์ฐ ๋น ๋ฅด๊ฒ ์ฒ๋ฆฌ๋ ์ ์์ด, ์ค์๊ฐ ๋ฐ์ดํฐ ์ํธํ์ ์ ํฉํฉ๋๋ค.
๊ฐ๋ ฅํ ๋ณด์์ฑ
AES๋ ๋๊ท๋ชจ์ ํค ๊ณต๊ฐ๊ณผ ๋ณต์กํ ์ํ์ ๋ณํ์ ์ฌ์ฉํ์ฌ ๋งค์ฐ ์์ ํ ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก ํ๊ฐ๋ฉ๋๋ค. ์์ ์ปดํจํฐ์ ๋ํ ๋ณด์์ฑ๋ ์๋์ ์ผ๋ก ๋ฐ์ด๋ฉ๋๋ค.
๋น ๋ฅธ ์ฑ๋ฅ
์ํํธ์จ์ด์ ํ๋์จ์ด ๋ชจ๋์์ ๋น ๋ฅธ ์ฐ์ฐ์ด ๊ฐ๋ฅํ์ฌ, ์ค์๊ฐ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ์ ํฉํฉ๋๋ค.
์ ์ฐ์ฑ
๋ค์ํ ํค ๊ธธ์ด๋ฅผ ์ง์ํ๋ฏ๋ก, ์๊ตฌ๋๋ ๋ณด์ ์์ค์ ๋ฐ๋ผ ์ ์ฐํ๊ฒ ์ ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
ํค ๊ด๋ฆฌ ๋ฌธ์
๋์นญ ํค ์ํธํ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์, ์ํธํ์ ๋ณตํธํ๋ฅผ ์ํด ๊ฐ์ ํค๋ฅผ ๊ณต์ ํด์ผ ํ๋ฉฐ, ์ด ํค๋ฅผ ์์ ํ๊ฒ ์ ๋ฌํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.
๊ณ ์ ๋ ๋ธ๋ก ํฌ๊ธฐ
AES๋ ๊ณ ์ ๋ 128๋นํธ ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก, ๋ธ๋ก ํฌ๊ธฐ๋ฅผ ๋ง์ถ๊ธฐ ์ํด ํจ๋ฉ์ ํด์ผ ํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
AES ์๊ณ ๋ฆฌ์ฆ์ ํตํด ์์ฑ๋ key๋ ํ๊ฒฝ๋ณ์์ ์ ์ฅํ์ฌ ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ํ๋ก์ ํธ์คํ ์ ํค๋ฅผ ์์ฑํ๊ณ ํด๋น ํค๋ฅผ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํฉ๋๋ค.
aes:
algorithm: AES
key-size: 128
secret: ${aes-key}
mode: AES/CBC/PKCS5Padding
iv-size: 16
ํค๋ฅผ ์์ฑํ๋๋ฐ ํ์ํ ์๊ณ ๋ฆฌ์ฆ, ํค ์ฌ์ด์ฆ๋ฅผ ์ค์ ํฉ๋๋ค. ์์์ ์ค๋ช
ํ CBC ๋ชจ๋๋ฅผ ์ฌ์ฉํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ด์ ํ์ํ mode, Initial Vector Size ๋ ์ค์ ํฉ๋๋ค. secret ํค๋ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํด์ ์ฌ์ฉํฉ๋๋ค. ํ๊ฒฝ๋ณ์ ๋ฑ๋ก๋ฐฉ๋ฒ์ Link๋ฅผ ํ์ธํ์ธ์.
CryptoService ๋ฅผ ๊ตฌํํ๋ AesCryptService๋ฅผ ์์ฑํฉ๋๋ค. AES ํค ์์ฑ, ํค ์กฐํ, ์/๋ณตํธํ ์ฒ๋ฆฌ๋ฅผ ํฉ๋๋ค.
common.encryption.aes.AesCryptoService
@Configuration
@Slf4j
public class AesCryptoService implements CryptoService {
private final String algorithm;
private final int keySize;
private final String aesKey;
private final String encryptionMode;
private final byte[] initialVector;
public AesCryptoService(@Value("${aes.algorithm}") String algorithm,
@Value("${aes.key-size}") int keySize,
@Value("${aes.secret}") String aesKey,
@Value("${aes.mode}") String encryptionMode,
@Value("${aes.iv-size}") int ivSize) throws NoSuchAlgorithmException {
this.algorithm = algorithm;
this.keySize = keySize;
this.aesKey = aesKey;
this.encryptionMode = encryptionMode;
this.initialVector = new byte[ivSize];
if (this.aesKey.isEmpty()) {
log.error("AES key does not exist. create new key. โผ");
createKey();
}
}
//๊ณ์..
application.yml ์ ์ค์ ํ ๊ฐ์ ์์ฑ์์์ ๋ถ๋ฌ์ต๋๋ค. AES key๊ฐ ์์ฑ๋์ด ์์ง ์์ ๊ฒฝ์ฐ ์๋ก ์์ฑํฉ๋๋ค.
@Override
public void createKey() throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance(this.algorithm);
keyGen.init(this.keySize);
SecretKey aesKey = keyGen.generateKey();
log.info("=================================AES KEY=================================");
log.info("AES key : {}", Base64.getEncoder().encodeToString(aesKey.getEncoded()));
}
ํค๋ฅผ ์์ฑํ๊ณ Base64๋ก ์ธ์ฝ๋ฉ ํ์ฌ ๋ก๊ทธ๋ก ์ถ๋ ฅํฉ๋๋ค. ์ถ๋ ฅ๋ ํค ๊ฐ์ ํ๊ฒฝ๋ณ์์ ๋ฑ๋กํฉ๋๋ค. IntelliJ์์ ํ๊ฒฝ๋ณ์๋ฅผ ๋ฑ๋กํ๋ ๋ฐฉ๋ฒ์ Link๋ฅผ ํ์ธํ์ธ์.
ํค๋ฅผ Base64 ๋ก decoding ํ์ฌ ์ ๋ฌํฉ๋๋ค. ServiceException ์ ๋ํด์๋ Link ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
@Override
public SecretKey getPublicKey() {
byte[] decodeKey = Base64.getDecoder().decode(this.aesKey);
return new SecretKeySpec(decodeKey, 0, decodeKey.length, this.algorithm);
}
๋์ผํ ํค๋ฅผ ์ฌ์ฉํ์ฌ ์/๋ณตํธํ ํ๋ ๋ฉ์๋๋ฅผ ์์ฑํฉ๋๋ค.
@Override
public String decrypt(String encryptedText) {
if(StringUtils.isEmpty(encryptedText)) return encryptedText;
try {
Cipher cipher = Cipher.getInstance(this.encryptionMode);
IvParameterSpec ivParameterSpec = new IvParameterSpec(this.initialVector);
cipher.init(Cipher.DECRYPT_MODE, getPublicKey(), ivParameterSpec);
byte[] decodedBytes = Base64.getDecoder().decode(encryptedText);
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
return new String(decryptedBytes);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
| BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
log.error("Decryption error. encryptedText : {}", encryptedText);
throw new ServiceException(ErrorCode.SERVICE_ERROR, e);
}
}
@Override
public String encrypt(String plainText) {
if(StringUtils.isEmpty(plainText)) return plainText;
try {
Cipher cipher = Cipher.getInstance(this.encryptionMode);
IvParameterSpec ivParameterSpec = new IvParameterSpec(this.initialVector);
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(), ivParameterSpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
| BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new ServiceException(ErrorCode.SERVICE_ERROR, e);
}
}
์ด์ ํ๋ก์ ํธ๋ฅผ ์คํํ๋ฉด AES key ๊ฐ ํ์ฌ๋ ์๊ธฐ ๋๋ฌธ์ ์ฝ์์ ์์ฑ๋ key๊ฐ ์ถ๋ ฅ๋๊ฒ ๋ฉ๋๋ค.
console
AES key does not exist. create new key. โผ
=================================AES KEY=================================
AES key : wcofmroZy...
์ถ๋ ฅ๋ ํค๋ฅผ ๋ณต์ฌํด์ ์์์ ๋ฑ๋กํ ํ๊ฒฝ๋ณ์ aes-key์ ๊ฐ์ ๋ฑ๋กํฉ๋๋ค.
๋ง์ผ ์ํธํ ๋ ๊ฐ์ ์๋ฆฌ์ ๊ณ ์ ์์ฒญ์ด ์๋ค๋ฉด ์๋์ ๋ฉ์๋๋ฅผ ํ์ฉํฉ๋๋ค. ์ํธํ๋ ๊ฐ + ๋๋ค ํ ์คํธ๋ก 256์๋ฆฌ์๋ฅผ ๋ง๋๋ ๋ฉ์๋ ์ ๋๋ค.
private String padTo256(String base64Encoded) {
int encryptedTextLength = 256;
StringBuilder padded = new StringBuilder(base64Encoded);
SecureRandom random = new SecureRandom();
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
while (padded.length() < encryptedTextLength) {
char randomChar = characters.charAt(random.nextInt(characters.length()));
padded.append(randomChar);
}
return padded.substring(0, encryptedTextLength);
}
ํด๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค๋ฉด ๋ณตํธํ ์์๋ ๋๋ค ํ ์คํธ๋ฅผ ์๋ผ๋ด์ด ์๋์ ์ํธํ ๊ฐ๋ง ๋จ๊ธฐ๋๋ก ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ์ถ๊ฐํด ์ฃผ์ด์ผ ํฉ๋๋ค.
encryptedText = encryptedText.substring(0, encryptedText.indexOf("==") + 2);
์ด์ RSA๋ฅผ ๊ตฌํํ ๋ ์ฌ์ฉํ๋ CryptoController์ AES ํค ์์ฒญ method ๋ฅผ ์ถ๊ฐํ๊ฒ ์ต๋๋ค.
@RestController
@RequiredArgsConstructor
@Tag(name = "์ํธํ ๊ด๋ จ ์ ๋ณด ์์ฒญ", description = "๋ณด์์ด ํ์ํ ์ ๋ณด๋ฅผ ์ํธํ ํ๊ธฐ ์ํ ํค ์์ฒญ")
@RequestMapping("v1")
public class CryptoController {
private final CryptoService rsaCryptoService;
private final CryptoService aesCryptoService; //์ถ๊ฐ
private final MessageConfig messageConfig;
//์๋ต..
@Operation(summary = "AES Key ์์ฒญ", description = """
""", operationId = "API-999-02")
@PostMapping(value = "/key/aes", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ItemResponse<PublicKeyResponse>> getAESPublicKey() {
SecretKey key = (SecretKey) this.aesCryptoService.getPublicKey();
String keyString = Base64.getEncoder().encodeToString(key.getEncoded());
PublicKeyResponse publicKeyResponse = PublicKeyResponse.builder().publicKey(keyString).build();
return ResponseEntity.ok()
.body(ItemResponse.<PublicKeyResponse>builder()
.status(messageConfig.getCode(NormalCode.SEARCH_SUCCESS))
.message(messageConfig.getMessage(NormalCode.SEARCH_SUCCESS))
.item(publicKeyResponse)
.build());
์์์ ์์ฑํ ํค๋ฅผ Base64๋ก ์ธ์ฝ๋ฉ ํ์ฌ ์ ์กํฉ๋๋ค.

์ถ๊ฐ์ ์ผ๋ก ํค๋ฅผ ์์ฒญํ๋ URI๋ ์ธ์ฆ์ด ํ์์๊ธฐ ๋๋ฌธ์ Spring Security์ ์ธ์ฆ URI ๋ชฉ๋ก์์ ์ ์ธํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๊ธฐ ์ํด SecurityConfig์ ignoreUris ๋ชฉ๋ก์ ์ฃผ์๋ฅผ ์ถ๊ฐํด ์ค๋๋ค.
์ด์ ํด๋ผ์ด์ธํธ๋ ๋ฐ์ key ๋ฅผ Base64๋ก ๋์ฝ๋ฉ ํ ํ ๋์จ ํค๋ก password ๋ฑ์ ์ํธํ ํ์ฌ ์ ์กํ๋ฉด ๋ฉ๋๋ค. ๋ณตํธํ๋ RSA์ ๋ง์ฐฎ๊ฐ์ง๋ก AesCryptoService์ decrypt method๋ฅผ ํธ์ถํ๋ฉด ๋ฉ๋๋ค.
AES๋ ๋น ๋ฅด๊ณ ๊ฐ๋ ฅํ ๋ณด์ ์๊ณ ๋ฆฌ์ฆ์ผ๋ก, ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ๋ขฐ๋ฐ๋ ๋์นญํค ์ํธํ ์๊ณ ๋ฆฌ์ฆ์ ๋๋ค. ํค ๊ธธ์ด์ ๋ฐ๋ผ ๋ณด์ ์์ค์ ์กฐ์ ํ ์ ์๊ณ , ๋ค์ํ ์ํธํ ๋ชจ๋๋ฅผ ํตํด ๋ค์ํ ํ๊ฒฝ์ ๋ง๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ค์์๋ ์์ ์์๋ณธ RSA ์ AES ๋ฅผ ํผํฉํ ํ์ด๋ธ๋ฆฌ๋ ๋ฐฉ์์ผ๋ก ์/๋ณตํธํ ๋ก์ง์ ๋ณ๊ฒฝํด๋ณด๊ฒ ์ต๋๋ค.