# Encrypt - AES128/256 ECB and CBC(with random iv) 테스트 코드
aes 암호화/복호화 ecb, cbc 테스트 코드 입니다.
# 테스트에 앞서
- aes128 과 aes256의 차이는 비밀키의 길이 입니다.
- byteArray 16자리: aes128
- byteArray 32자리: aes256
- IV의 길이는 16자리 고정 입니다.
- aes encrypt example : https://aesencryption.net/
- cbc vs ecb : https://yoda.wiki/wiki/Block_cipher_mode_of_operation
초기화 벡터(IV)
초기화 벡터는 키와 다른 보안 요건을 가지므로 보통 IV가 비밀일 필요는 없습니다.
대부분의 블록 암호 모드에서는 초기화 벡터를 같은 키(암호 난스)로 재사용하지 않는 것이 중요합니다.
많은 블록 암호 모드에는 IV가 랜덤 또는 의사 난수여야 하는 등 더 강력한 요건이 있습니다.
일부 블록 암호에는 (일부 키에 대해) 모든 0 IV가 암호화를 생성하지 않는 등 특정 초기화 벡터에 특정 문제가 있습니다.
CBC 모드에서는 암호화 시 IV는 예측할 수 없는(랜덤 또는 의사랜덤) 것이어야 합니다.
특히, (이전에는) 메시지의 마지막 암호 텍스트블록을 다음 메시지의 IV로 재사용하는 일반적인 방법은 안전하지 않습니다(예를 들어 이 방식은 SSL 2.0에서 사용되었습니다).
공격자가 다음 평문이 지정되기 전에 IV(또는 이전 암호문 블록)를 알고 있으면 이전에 같은 키로 암호화되어 있던 일부 블록의 평문에 대한 추측을 확인할 수 있습니다(이것은 TLS CBC IV [9]공격이라고 불립니다).
아래 테스트 코드에서는 iv를 고정된 값으로 사용 하고 있어, 좋은 코드라고 볼 수 없습니다.
참고만 부탁드립니다.
- random한 IV를 사용해서 암호화 및 복호화를 해야 된다면,,,,,
- 맨 아래 Example Code (CBC128 and Random IV) 를 확인해 주세요!
- 참고만 해주세요!
# 환경
/**
* tool: STS 4.13.0
* version: 2.7.3-SNAPSHOT
* java: 11
* type: MAVEN
* view: THYMELEAF
* jQuery: 3.6.0
*/
# Example Code (CBC 128)
public static void main(String[] args) {
try {
String encryptStr = "";
String decryptStr = "";
byte[] lawData = "PrivateContent".getBytes("UTF-8");
byte[] aes128SecretKey = "0123456789ABCDEF".getBytes("UTF-8");
byte[] aesIv = "0123456789ABCDEF".getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKey128 = new SecretKeySpec(aes128SecretKey, "AES");
IvParameterSpec secretIv = new IvParameterSpec(aesIv);
// AES 128 암호화
cipher.init(Cipher.ENCRYPT_MODE, secretKey128, secretIv);
byte[] encryptBytes128 = cipher.doFinal(lawData);
encryptStr = Base64.getEncoder().encodeToString(encryptBytes128);
System.out.println("CBC-128-E: " + encryptStr);
// AES 128 복호화
cipher.init(Cipher.DECRYPT_MODE, secretKey128, secretIv);
byte[] decryptBytes128 = cipher.doFinal(Base64.getDecoder().decode(encryptStr));
decryptStr = new String(decryptBytes128, "UTF-8");
System.out.println("CBC-128-D: " + decryptStr);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/* 결과
CBC-128-E: QX4cT+Oxgz9dKZFlIcJamA==
CBC-128-D: PrivateContent
*/
# Example Code (CBC 256)
public static void main(String[] args) {
try {
String encryptStr = "";
String decryptStr = "";
byte[] lawData = "PrivateContent".getBytes("UTF-8");
byte[] aes256SecretKey = "0123456789ABCDEF0123456789ABCDEF".getBytes("UTF-8");
byte[] aesIv = "0123456789ABCDEF".getBytes("UTF-8");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKey256 = new SecretKeySpec(aes256SecretKey, "AES");
IvParameterSpec secretIv = new IvParameterSpec(aesIv);
// AES 256 암호화
cipher.init(Cipher.ENCRYPT_MODE, secretKey256, secretIv);
byte[] encryptBytes256 = cipher.doFinal(lawData);
encryptStr = Base64.getEncoder().encodeToString(encryptBytes256);
System.out.println("CBC-256-E: " + encryptStr);
// AES 256 복호화
cipher.init(Cipher.DECRYPT_MODE, secretKey256, secretIv);
byte[] decryptBytes256 = cipher.doFinal(Base64.getDecoder().decode(encryptStr));
decryptStr = new String(decryptBytes256, "UTF-8");
System.out.println("CBC-256-D: " + decryptStr);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/* 결과
CBC-256-E: S26DxNceALGuBG1jo8tI8Q==
CBC-256-D: PrivateContent
*/
# Example Code (ECB 128)
public static void main(String[] args) {
try {
String encryptStr = "";
String decryptStr = "";
byte[] lawData = "PrivateContent".getBytes("UTF-8");
byte[] secretKeyECB = "0123456789ABCDEF".getBytes("UTF-8");
Cipher ECB = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec secretKey = new SecretKeySpec(secretKeyECB, "AES");
// ECB 128 암호화
ECB.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptBytesECB = ECB.doFinal(lawData);
encryptStr = Base64.getEncoder().encodeToString(encryptBytesECB);
System.out.println("ECB-128-E: " + encryptStr);
// ECB 128 복호화
ECB.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decryptBytesECB = ECB.doFinal(Base64.getDecoder().decode(encryptStr));
decryptStr = new String(decryptBytesECB, "UTF-8");
System.out.println("ECB-128-D: " + decryptStr);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
e.printStackTrace();
}
}
/* 결과
ECB-128-E: wethk3G5rBW+4zeRwPubjA==
ECB-128-D: PrivateContent
*/
# Example Code (CBC128 and Random IV)
@Configuration
public class CryptoConfig {
public byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
public static void main(String[] args) {
try {
// use random iv
CryptoConfig config = new CryptoConfig();
String encryptStr = "";
String decryptStr = "";
String originData = "OriginPrivateContent";
String strSecretKey = "0123456789ABCDEF"; // AES 128
String strRandomIV = EncryptService.byteArrayToHexString(config.generateSalt());
Cipher cipherCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher cipherECB = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec privateKey = new SecretKeySpec(strSecretKey.getBytes(), "AES");
// ENCRYPT
IvParameterSpec randomIV = new IvParameterSpec(EncryptService.hexStringToByteArray(strRandomIV));
cipherCBC.init(Cipher.ENCRYPT_MODE, privateKey, randomIV);
byte[] encodeData = cipherCBC.doFinal(originData.getBytes());
encryptStr = Base64.getEncoder().encodeToString(encodeData);
System.out.println("RANDOM-IV-CBC-E : " + encryptStr);
String addIvEncryptStr = strRandomIV.concat("|").concat(encryptStr);
System.out.println("RANDOM-IV-ECB-E : " + addIvEncryptStr);
cipherECB.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] encodeData2 = cipherECB.doFinal(addIvEncryptStr.getBytes());
encryptStr = Base64.getEncoder().encodeToString(encodeData2);
System.out.println("RANDOM-IV-RESULT: " + encryptStr);
// DECRYPT
cipherECB.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodeData1 = cipherECB.doFinal(Base64.getDecoder().decode(encryptStr));
decryptStr = new String(decodeData1, "UTF-8");
System.out.println("RANDOM-IV-ECB-D : " + decryptStr);
String[] ivAndData = decryptStr.split("\\|");
String decIv = ivAndData[0];
String decData = ivAndData[1];
IvParameterSpec randomIV2 = new IvParameterSpec(EncryptService.hexStringToByteArray(decIv));
cipherCBC.init(Cipher.DECRYPT_MODE, privateKey, randomIV2);
byte[] decodeData2 = cipherCBC.doFinal(Base64.getDecoder().decode(decData));
decryptStr = new String(decodeData2, "UTF-8");
System.out.println("RANDOM-IV-CBC-D : " + decryptStr);
} catch (IllegalBlockSizeException | BadPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException| UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
@Service
public class EncryptService {
public static String byteArrayToHexString(byte[] bytes) {
if(bytes == null || bytes.length == 0) {
return "";
}
StringBuilder builder = new StringBuilder();
for (byte b: bytes) {
builder.append(String.format("%02X", b));
}
return builder.toString();
}
public static byte[] hexStringToByteArray(String hexString) {
if(StringUtils.isEmpty(hexString)) {
return new byte[0];
}
byte[] bytes = new byte[hexString.length()/2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(hexString.substring(i*2, i*2 + 2), 16);
}
return bytes;
}
}
/* 결과
RANDOM-IV-CBC-E : iH6e43aKeK7FmNt0dsIsw+jkhy7yQ3fwF8WSTls+3FI=
RANDOM-IV-ECB-E : 95E03239B309C0E8C3E585E15284988E|iH6e43aKeK7FmNt0dsIsw+jkhy7yQ3fwF8WSTls+3FI=
RANDOM-IV-RESULT: n7ln6UkZA8Xo2/PYpYyYJXJyqq+76nAjYqEkN1/LuYJ7vUxNrZcj31Ik+WpQ+79cZCpukPo/NVyUGoYCfncvJMnV2ruQCKPUD4B6qWtvfyU=
RANDOM-IV-ECB-D : 95E03239B309C0E8C3E585E15284988E|iH6e43aKeK7FmNt0dsIsw+jkhy7yQ3fwF8WSTls+3FI=
RANDOM-IV-CBC-D : OriginPrivateContent
*/
- 이런식의 코드 작성을 올바르지 않을 수 있음.
- 하지만 IV 을 Random으로 사용해야 된다고 생각했을 때의 작성한 코드다.
- 순서는 아래와 같다.
[ 암호화 ]
- 평문을 Random한 IV 값으로 CBC 암호화 진행.
- RandomIV 값과 암호화된 평문을 "|" 로 연결.
- 연결된 값을 ECB 방식으로 암호화.
[ 복호화 ]
- [ 암호화 ] 에서 작성한 암호화값을 ECB 방식으로 복호화.
- 복호화된 값을 "|" 값으로 나누어 앞에는 IV, 뒤에는 암호화값으로 확인.
- 확인한 IV 값을 이용하여 암호화 값을 CBC 방식으로 복호화.
# 암복호화 시리즈
https://hjho95.tistory.com/25 Encrypt - AES128/256 ECB and CBC(with random iv) 테스트 코드
https://hjho95.tistory.com/26 Encrypt - AES128 CBC(with iv) example code
https://hjho95.tistory.com/27 Encrypt - SHA256(with salt) MessageDigest example code
# 암복호화 시리즈 참조 페이지
aes example: https://aesencryption.net/
cbc vs ecb: https://yoda.wiki/wiki/Block_cipher_mode_of_operation
sha example: http://wiki.hash.kr/index.php/SHA256#cite_note-8
'스프링 부트' 카테고리의 다른 글
[SpringBoot] Encrypt - SHA256(with salt) MessageDigest example code (2) | 2022.10.03 |
---|---|
[SpringBoot] Encrypt - AES128 CBC(with iv) example code (0) | 2022.10.03 |
[SpringBoot] jdk 1.8 > jdk 11 로 변경하기 (2) | 2022.09.12 |
[SpringBoot] ajax - POST방식으로 PUT, PATCH, DELETE 사용하기! (HiddenHttpMethodFilter) (0) | 2022.08.15 |
[SpringBoot] ajax - PUT, PATCH, DELETE 로 요청하기! (0) | 2022.08.15 |