-
Notifications
You must be signed in to change notification settings - Fork 0
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
feat:AES 암호화 방식 유틸리티 추가#15 #16
base: feature/#13-login
Are you sure you want to change the base?
Changes from all commits
e393c59
371e86e
b136ab6
ed94b5c
45dd3b1
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,93 @@ | ||
package shop.sendbox.sendbox.util; | ||
|
||
import java.security.NoSuchAlgorithmException; | ||
import java.security.SecureRandom; | ||
import java.security.spec.InvalidKeySpecException; | ||
import java.security.spec.KeySpec; | ||
import java.util.Base64; | ||
|
||
import javax.crypto.Cipher; | ||
import javax.crypto.SecretKey; | ||
import javax.crypto.SecretKeyFactory; | ||
import javax.crypto.spec.IvParameterSpec; | ||
import javax.crypto.spec.PBEKeySpec; | ||
import javax.crypto.spec.SecretKeySpec; | ||
|
||
import lombok.extern.slf4j.Slf4j; | ||
|
||
/* | ||
애노테이션 프로세서는 해당 애노테이션을 확인하면 컴파일 시점에 Logger 클래스를 생성합니다. | ||
따라서 별도의 Logger 객체를 생성할 필요 없이 log 객체를 사용할 수 있습니다. | ||
*/ | ||
@Slf4j | ||
public class AesEncrypt { | ||
|
||
private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; | ||
/** | ||
* TODO | ||
* 패스워드와 salt를 어떻게 보관할 것인지 고민입니다. | ||
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. password 와 salt 의 차이는 무엇인가요? application.yml 에 보관하는 것은 어떨까요? 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. password는 암호화 및 복호화를 위해 필요한 key입니다 현재 로직에서는 고정된 salt를 사용하여 salt를 사용하는 의미가 없어졌습니다. |
||
*/ | ||
private static final String DELIMITER = ":"; | ||
private static final String PBKDF2_WITH_HMAC_SHA256 = "PBKDF2WithHmacSHA256"; | ||
private static final Base64.Encoder ENCODER = Base64.getEncoder(); | ||
private static final Base64.Decoder DECODER = Base64.getDecoder(); | ||
public static final int IV_LENGTH = 16; | ||
|
||
private static SecretKey getKeyFromPassword(final String password, final String salt) { | ||
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256); | ||
try { | ||
return new SecretKeySpec(generateSecret(keySpec), "AES"); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
private static byte[] generateSecret(final KeySpec spec) throws InvalidKeySpecException, NoSuchAlgorithmException { | ||
SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_WITH_HMAC_SHA256); | ||
return factory.generateSecret(spec).getEncoded(); | ||
} | ||
|
||
private static IvParameterSpec generateIv() { | ||
byte[] iv = new byte[IV_LENGTH]; | ||
new SecureRandom().nextBytes(iv); | ||
return new IvParameterSpec(iv); | ||
} | ||
|
||
public static String encrypt(final String password, final String salt, String input) { | ||
SecretKey key = getKeyFromPassword(password, salt); | ||
final IvParameterSpec iv = generateIv(); | ||
try { | ||
Cipher cipher = Cipher.getInstance(ALGORITHM); | ||
cipher.init(Cipher.ENCRYPT_MODE, key, iv); | ||
byte[] cipherText = cipher.doFinal(input.getBytes()); | ||
return ENCODER.encodeToString(cipherText) + DELIMITER + ENCODER.encodeToString(iv.getIV()); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
public static String decrypt(final String password, final String salt, String cipherText) { | ||
if (cipherText == null) { | ||
throw new IllegalArgumentException("암호화된 문자열에 값이 없습니다."); | ||
} | ||
final String[] encryptAndIv = cipherText.split(DELIMITER); | ||
final SecretKey key = getKeyFromPassword(password, salt); | ||
if (cipherText.isEmpty() || encryptAndIv.length != 2) { | ||
throw new IllegalArgumentException("암호화된 문자열 양식이 올바르지 않습니다."); | ||
} | ||
final int encryptIndex = 0; | ||
final int ivIndex = 1; | ||
final byte[] encryptBytes = DECODER.decode(encryptAndIv[encryptIndex]); | ||
final byte[] ivBytes = DECODER.decode(encryptAndIv[ivIndex]); | ||
IvParameterSpec iv = new IvParameterSpec(ivBytes); | ||
try { | ||
Cipher cipher = Cipher.getInstance(ALGORITHM); | ||
cipher.init(Cipher.DECRYPT_MODE, key, iv); | ||
byte[] plainText = cipher.doFinal(encryptBytes); | ||
return new String(plainText); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package shop.sendbox.sendbox.util; | ||
|
||
import org.assertj.core.api.Assertions; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
||
|
||
class AesEncryptTest { | ||
|
||
@Test | ||
@DisplayName("올바른 값을 입력시 암호화 복호화가 정상동작 합니다.") | ||
void encryptTest() { | ||
// given | ||
String input = "test"; | ||
final String password = "AesEncrypt.PASSWORD"; | ||
final String salt = "AesEncrypt.SALT"; | ||
String encrypted = AesEncrypt.encrypt(password, salt, input); | ||
|
||
// when | ||
String decrypted = AesEncrypt.decrypt(password, salt, encrypted); | ||
|
||
// then | ||
Assertions.assertThat(decrypted).isEqualTo(input); | ||
} | ||
|
||
@Test | ||
@DisplayName("복호화시 값이 없으면 예외가 발생합니다.") | ||
void decryptTestWithNull() { | ||
// given | ||
String encrypted = null; | ||
final String password = "AesEncrypt.PASSWORD"; | ||
final String salt = "AesEncrypt.SALT"; | ||
|
||
// when & then | ||
Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(password, salt, encrypted)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("암호화된 문자열에 값이 없습니다."); | ||
} | ||
|
||
@Test | ||
@DisplayName("복호화시 암호문 양식이 올바르지 않으면 예외가 발생합니다.") | ||
void decryptTestWithNot() { | ||
// given | ||
String encrypted = "test"; | ||
final String password = "AesEncrypt.PASSWORD"; | ||
final String salt = "AesEncrypt.SALT"; | ||
|
||
// when & then | ||
Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(password, salt, encrypted)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("암호화된 문자열 양식이 올바르지 않습니다."); | ||
} | ||
} |
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.
해당 어노테이션의 동작방식에 대해서도 적어주시면 더 좋을 것 같습니다.
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.
네 추가했습니다 :)