-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: answerNumber , answer 값 AES 대칭키 적용 #425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.example.cs25service.domain.quiz.util; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.crypto.Cipher; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.crypto.SecretKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.crypto.spec.IvParameterSpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import javax.crypto.spec.SecretKeySpec; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.nio.charset.StandardCharsets; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.security.SecureRandom; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Base64; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class AesUtil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final String secretKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public AesUtil(@Value("${aes.secret.key}") String secretKey) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.secretKey = secretKey; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final String ALGORITHM = "AES/CBC/PKCS5Padding"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private SecretKey getKey() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+17
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 비정상 AES 키 길이 허용으로 인한 런타임 실패 위험 현재 - private final String secretKey;
-
- public AesUtil(@Value("${aes.secret.key}") String secretKey) {
- this.secretKey = secretKey;
- }
-
- private SecretKey getKey() {
- return new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "AES");
- }
+ private final SecretKey secretKey;
+
+ public AesUtil(@Value("${aes.secret.key}") String secretKey) {
+ byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8);
+ if (keyBytes.length != 16 && keyBytes.length != 24 && keyBytes.length != 32) {
+ throw new IllegalArgumentException("aes.secret.key는 16, 24 또는 32바이트여야 합니다.");
+ }
+ this.secretKey = new SecretKeySpec(keyBytes, "AES");
+ }
+
+ private SecretKey getKey() {
+ return secretKey;
+ }📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** 암호화 */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String encrypt(String plainText) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (plainText == null) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Cipher cipher = Cipher.getInstance(ALGORITHM); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] iv = new byte[16]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| new SecureRandom().nextBytes(iv); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| IvParameterSpec ivSpec = new IvParameterSpec(iv); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cipher.init(Cipher.ENCRYPT_MODE, getKey(), ivSpec); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // IV와 암호문을 Base64로 함께 인코딩 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] combined = new byte[iv.length + encrypted.length]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.arraycopy(iv, 0, combined, 0, iv.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.arraycopy(encrypted, 0, combined, iv.length, encrypted.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return Base64.getEncoder().encodeToString(combined); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("AES encryption error", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** 복호화 */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public String decrypt(String cipherText) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (cipherText == null) return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] decoded = Base64.getDecoder().decode(cipherText); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] iv = new byte[16]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] encrypted = new byte[decoded.length - 16]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.arraycopy(decoded, 0, iv, 0, iv.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| System.arraycopy(decoded, iv.length, encrypted, 0, encrypted.length); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Cipher cipher = Cipher.getInstance(ALGORITHM); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cipher.init(Cipher.DECRYPT_MODE, getKey(), new IvParameterSpec(iv)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| byte[] original = cipher.doFinal(encrypted); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new String(original, StandardCharsets.UTF_8); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new RuntimeException("AES decryption error", e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
암호화로 인해 기존 응답 스키마가 깨집니다
Line 61과 Line 79에서 바로 암호화된 값을 내려보내면
/todayQuiz실제 엔드포인트를 소비하는 기존 프런트가 즉시 깨집니다(현재 배포본은 복호화 로직이 없음). 프런트 적용이 완료될 때까지는 최소한 기능 플래그나 엔드포인트 버전 분리로 기존 소비자에게 평문을 유지해야 합니다.Also applies to: 79-80