2024. 8. 22. 15:52ㆍ이슈 창고
모든 개발 기능들을 마무리하고 기존에 운영중이던 단방향 비밀번호 암호화를 양방향으로 바꿔달라는 요청이 발생되어 진행하는 도중에 복호화가 정상적으로 수행되지 않는 이슈가 발생하였습니다.
javax.crypto.BadPaddingException: Given final block not properly padded ~ 에러가 발생하면서 복호화 시 사용되는 키값이 암호화할 때 사용되던 키와 일치하지 않다고 계속해서 에러가 발생되었습니다.
문제 발생
이전 양방향 암호화 Config 클래스
@Slf4j
@Component
public class AES128Config {
private static final Charset ENCODING_TYPE = StandardCharsets.UTF_8;
private static final String INSTANCE_TYPE = "AES/CBC/PKCS5Padding";
@Value("${supplier.pwd.secret.key}")
private String secretKey;
private IvParameterSpec ivParameterSpec;
private SecretKeySpec secretKeySpec;
private Cipher cipher;
@PostConstruct
public void init() throws NoSuchPaddingException, NoSuchAlgorithmException {
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16]; // 16bytes = 128bits
secureRandom.nextBytes(iv);
this.ivParameterSpec = new IvParameterSpec(iv);
this.secretKeySpec = new SecretKeySpec(this.secretKey.getBytes(ENCODING_TYPE), "AES");
this.cipher = Cipher.getInstance(INSTANCE_TYPE);
}
// AES 암호화
public String encryptAes(String plaintext) throws InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
log.info("시크릿 키 : {}", secretKey);
log.info("암호화할 비밀번호 : {}", plaintext);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encryted = cipher.doFinal(plaintext.getBytes(ENCODING_TYPE));
return new String(Base64.getEncoder().encode(encryted), ENCODING_TYPE);
}
// AES 복호화
public String decryptAes(String plaintext) throws InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
log.info("시크릿 키 : {}", secretKey);
log.info("복호화할 비밀번호 : {}", plaintext);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decoded = Base64.getDecoder().decode(plaintext.getBytes(ENCODING_TYPE));
return new String(cipher.doFinal(decoded), ENCODING_TYPE);
}
}
우선 이전에 사용하고 있던 양방향 암/복호화 클래스 파일을 보면 PostConstruct가 적용된 init이라는 함수가 존재하는데, 이 AES128Config 파일을 암/복호화하고자하는 다른 클래스들에서 의존성 주입받아 사용하게 되면 Spring Bean Container의 Bean들이 초기화 되고 의존성 주입이 수행됨과 동시에 해당 init 함수가 실행되면서 암/복호화될 때 필요한 ivParameterSpec, secretKeySpec, cipher가 초기 설정되어 생성되게 됩니다.
설정된 ivParameterSpec, secretKeySpec, cipher들을 가지고 암/복호화를 수행하게 되는데, 간단하게 계정을 생성하고 로그인 시 정상적으로 암/복호화를 통해 로그인이 되는지 확인해보았습니다.
ID : newAccount3
Password : newAccount3
아이디와 비밀번호를 newAccount3 라고 하고 계정을 생성했을 때, 정상적으로 양방향 암호화가 진행되어 저장된 것을 확인할 수 있었습니다.
또한 로그인을 수행했더니 정상적으로 복호화를 수행하면서 로그인 되는 것 또한 확인할 수 있었습니다.
그런데, 서버를 껏다가 다시 실행하거나 다른 기능들 중에서 또 암/복호화를 수행해야되는 기능들을 실행하게 되면 javax.crypto.BadPaddingException: Given final block not properly padded ~ 에러가 발생하면서 암/복호화 키가 일치하지 않아 수행할 수 없다라는 에러가 계속해서 발생되게 되었습니다.
아무리 로직을 확인을 해봐도 암/복호화 키는 공통적인 키값을 사용하며, 해당 키 값은 properties파일에 하나의 설정값으로 만들어놓고 불러와서 사용하기 때문에 불일치하는 이슈는 절대 발생해서도, 그럴 수도 없는 일이였는데 지속적으로 에러가 발생되었습니다.
문제 파악 1
일단 제가 처음 의심을 해보았던 부분은 PostConstruct 어노테이션이 적용된 init 함수 부분이였습니다.
@PostConstruct
public void init() throws NoSuchPaddingException, NoSuchAlgorithmException {
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16]; // 16bytes = 128bits
secureRandom.nextBytes(iv);
this.ivParameterSpec = new IvParameterSpec(iv);
this.secretKeySpec = new SecretKeySpec(this.secretKey.getBytes(ENCODING_TYPE), "AES");
this.cipher = Cipher.getInstance(INSTANCE_TYPE);
}
아무래도 PostConstruct 어노테이션을 적용하게되면 위에서 언급했다시피 이 클래스를 의존성 주입하고자 하는 클래스에서 호출하게 되면 호출할 때 마다 Bean이 초기화 되고 의존성 주입이 동시에 수행된다기에 이 init 함수가 계속해서 새롭게 실행되어 키 값 내용 및 암호화 알고리즘이 초기화 되어 새로운 암/복호화 알고리즘이 재생성되기 때문에 계속해서 발생된 것이라고 의심되었습니다.
따라서 특정 기능 실행 구간에서 이 init 함수를 한 번 실행하고 그 이후에는 적용된 내용을 가지고 재활용해서 필요한 함수마다 AES128Config 클래스를 매개변수로 받아 사용하게끔 수정했습니다.
하지만 이 또한 동일한 에러가 계속해서 발생되었습니다.
계속 암/복호화에 사용된 키가 일치하지 않다라는 에러가 발생되었는데, 키는 동일한 것을 확인했음에도 불구하고 지속적인 동일한 에러가 발생되어 답답했습니다.
문제 파악 2
그런데 문득 init 함수를 다시 한 번 살펴보게되었습니다.
@PostConstruct
public void init() throws NoSuchPaddingException, NoSuchAlgorithmException {
SecureRandom secureRandom = new SecureRandom();
byte[] iv = new byte[16]; // 의심되는 코드
secureRandom.nextBytes(iv);
this.ivParameterSpec = new IvParameterSpec(iv);
this.secretKeySpec = new SecretKeySpec(this.secretKey.getBytes(ENCODING_TYPE), "AES");
this.cipher = Cipher.getInstance(INSTANCE_TYPE);
}
이번에도 역시나 init 함수를 다시 한 번 살펴보게 되었습니다.
키 값은 여러번 확인해도 문제가 없으니, 다른 스펙들인 ivParameterSpec, cipher 부분을 확인해봐야겠다고 생각했습니다.
그런데 이 때 ivParameterSpec을 결정짓는 바이트 타입의 iv 변수가 new byte[16]으로 되어있는 것을 보았습니다.
위에서 계속 언급했다시피 PostConstruct 가 적용되어있기 때문에 계속해서 의존성을 주입받고 실행될 때 마다 Bean 이 초기화 되고 새롭게 주입되기 때문에 new Byte[] 로 선언하게 되면 매번 다른 iv 파라미터가 생성되는 것을 의미하고, 이는 암호화 알고리즘이 매번 다르게 생성되는 것을 의미하는 것으로 생각되었습니다.
AES128Config() throws NoSuchPaddingException, NoSuchAlgorithmException {
byte[] iv = secretKey.substring(0, 16).getBytes();
this.ivParameterSpec = new IvParameterSpec(iv);
this.secretKeySpec = new SecretKeySpec(this.secretKey.getBytes(ENCODING_TYPE), "AES");
this.cipher = Cipher.getInstance(INSTANCE_TYPE);
}
따라서 해당 함수를 PostConstruct 어노테이션을 빼고 생성자로 변경하였고, iv 변수를 사용하고있는 키 값을 기준으로 16자리 수 까지 잘라 공통된 iv 파라미터를 사용하도록 고정시켜주었습니다.
이제 다시 에러가 났었던 기능들을 실행해보았더니,
정상적으로 암/복호화가 수행되면서 원하던 데이터들을 호출할 수 있었습니다.
역시 원인은 키 값이 아니라 iv 변수였던 것입니다.
+ 정상 수행 동작 변경 코드
@Slf4j
@Component
public class AES128Config {
private static final Charset ENCODING_TYPE = StandardCharsets.UTF_8;
private static final String INSTANCE_TYPE = "AES/CBC/PKCS5Padding";
private final String secretKey = "{암/복호화 시크릿 키}";
private IvParameterSpec ivParameterSpec;
private SecretKeySpec secretKeySpec;
private Cipher cipher;
AES128Config() throws NoSuchPaddingException, NoSuchAlgorithmException {
byte[] iv = secretKey.substring(0, 16).getBytes(); // 16bytes = 128bits
this.ivParameterSpec = new IvParameterSpec(iv);
this.secretKeySpec = new SecretKeySpec(this.secretKey.getBytes(ENCODING_TYPE), "AES");
this.cipher = Cipher.getInstance(INSTANCE_TYPE);
}
// AES 암호화
public String encryptAes(String plaintext) throws InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
log.info("시크릿 키 : {}", secretKey);
log.info("암호화할 비밀번호 : {}", plaintext);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] encryted = cipher.doFinal(plaintext.getBytes(ENCODING_TYPE));
return new String(Base64.getEncoder().encode(encryted), ENCODING_TYPE);
}
// AES 복호화
public String decryptAes(String plaintext) throws InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException {
log.info("시크릿 키 : {}", secretKey);
log.info("복호화할 비밀번호 : {}", plaintext);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] decoded = Base64.getDecoder().decode(plaintext.getBytes(ENCODING_TYPE));
return new String(cipher.doFinal(decoded), ENCODING_TYPE);
}
}
'이슈 창고' 카테고리의 다른 글
[이슈] 배포 서버 웹 서버 사용량 MAX로 인한 이슈 (1) | 2024.02.14 |
---|---|
[이슈] input 태그에 파일을 여러 번 파일을 등록할 시 발생되는 이슈 (0) | 2023.12.29 |
[이슈] Swagger 각각 다른 데이터 형식의 다중 요청 매개변수 존재 시 json 형식으로 묶음처리 되는 이슈 (0) | 2023.10.05 |
[이슈] Spring Security 적용 시 api 기능 동작 불가 이슈 (0) | 2022.10.07 |