From e393c591394d206e8fb6f89d5e0a709ea5aeb3cd Mon Sep 17 00:00:00 2001 From: kamser0415 Date: Sun, 27 Oct 2024 22:23:21 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20AES=20=EC=95=94=ED=98=B8=ED=99=94?= =?UTF-8?q?=20=EC=9C=A0=ED=8B=B8=EB=A6=AC=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/sendbox/sendbox/buyer/Buyer.java | 2 +- .../shop/sendbox/sendbox/util/AesEncrypt.java | 90 +++++++++++++++++++ .../{EncryptUtil.java => HashingEncrypt.java} | 2 +- .../sendbox/sendbox/util/AesEncryptTest.java | 46 ++++++++++ 4 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java rename src/main/java/shop/sendbox/sendbox/util/{EncryptUtil.java => HashingEncrypt.java} (95%) create mode 100644 src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java diff --git a/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java b/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java index 9811353..2ab564b 100644 --- a/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java +++ b/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java @@ -2,7 +2,7 @@ import static shop.sendbox.sendbox.buyer.BuyerStatus.*; import static shop.sendbox.sendbox.buyer.DeleteStatus.*; -import static shop.sendbox.sendbox.util.EncryptUtil.*; +import static shop.sendbox.sendbox.util.HashingEncrypt.*; import java.time.LocalDateTime; diff --git a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java new file mode 100644 index 0000000..efb40e2 --- /dev/null +++ b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java @@ -0,0 +1,90 @@ +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; + +@Slf4j +public class AesEncrypt { + + private static final String algorithm = "AES/CBC/PKCS5Padding"; + /** + * TODO + * 패스워드와 salt를 어떻게 보관할 것인지 고민입니다. + */ + private static final String password = "password"; + private static final String salt = "salt"; + private static final String delimiter = ":"; + private static final String pbkdf2WithHmacSHA256 = "PBKDF2WithHmacSHA256"; + private static final Base64.Encoder encoder = Base64.getEncoder(); + private static final Base64.Decoder decoder = Base64.getDecoder(); + + private static SecretKey getKeyFromPassword() { + 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(pbkdf2WithHmacSHA256); + return factory.generateSecret(spec).getEncoded(); + } + + private static IvParameterSpec generateIv() { + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } + + public static String encrypt(String input) { + SecretKey key = getKeyFromPassword(); + 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(String cipherText) { + if (cipherText == null) { + throw new IllegalArgumentException("암호화된 문자열에 값이 없습니다."); + } + final String[] encryptAndIv = cipherText.split(delimiter); + final SecretKey key = getKeyFromPassword(); + 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); + } + } + +} diff --git a/src/main/java/shop/sendbox/sendbox/util/EncryptUtil.java b/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java similarity index 95% rename from src/main/java/shop/sendbox/sendbox/util/EncryptUtil.java rename to src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java index 03a0848..cf77ad7 100644 --- a/src/main/java/shop/sendbox/sendbox/util/EncryptUtil.java +++ b/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java @@ -7,7 +7,7 @@ import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; -public class EncryptUtil { +public class HashingEncrypt { static final int saltLength = 16; public static String encrypt(String input, String salt) { diff --git a/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java b/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java new file mode 100644 index 0000000..e5f9259 --- /dev/null +++ b/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java @@ -0,0 +1,46 @@ +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"; + String encrypted = AesEncrypt.encrypt(input); + + // when + String decrypted = AesEncrypt.decrypt(encrypted); + + // then + Assertions.assertThat(decrypted).isEqualTo(input); + } + + @Test + @DisplayName("복호화시 값이 없으면 예외가 발생합니다.") + void decryptTestWithNull() { + // given + String encrypted = null; + + // when & then + Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(encrypted)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("암호화된 문자열에 값이 없습니다."); + } + + @Test + @DisplayName("복호화시 암호문 양식이 올바르지 않으면 예외가 발생합니다.") + void decryptTestWithNot() { + // given + String encrypted = "test"; + + // when & then + Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(encrypted)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("암호화된 문자열 양식이 올바르지 않습니다."); + } +} From 371e86ec234c685e2b3583a15c68453517f69bce Mon Sep 17 00:00:00 2001 From: kamser0415 Date: Mon, 28 Oct 2024 22:12:37 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EC=83=81=EC=88=98=20=EC=BB=A8?= =?UTF-8?q?=EB=B2=A4=EC=85=98=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../shop/sendbox/sendbox/util/AesEncrypt.java | 30 +++++++++---------- .../sendbox/sendbox/util/HashingEncrypt.java | 4 +-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java index efb40e2..ddbe990 100644 --- a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java +++ b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java @@ -18,20 +18,20 @@ @Slf4j public class AesEncrypt { - private static final String algorithm = "AES/CBC/PKCS5Padding"; + private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; /** * TODO * 패스워드와 salt를 어떻게 보관할 것인지 고민입니다. */ - private static final String password = "password"; - private static final String salt = "salt"; - private static final String delimiter = ":"; - private static final String pbkdf2WithHmacSHA256 = "PBKDF2WithHmacSHA256"; - private static final Base64.Encoder encoder = Base64.getEncoder(); - private static final Base64.Decoder decoder = Base64.getDecoder(); + private static final String PASSWORD = "password"; + private static final String 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(); private static SecretKey getKeyFromPassword() { - KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256); + KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), SALT.getBytes(), 65536, 256); try { return new SecretKeySpec(generateSecret(keySpec), "AES"); } catch (Exception e) { @@ -40,7 +40,7 @@ private static SecretKey getKeyFromPassword() { } private static byte[] generateSecret(final KeySpec spec) throws InvalidKeySpecException, NoSuchAlgorithmException { - SecretKeyFactory factory = SecretKeyFactory.getInstance(pbkdf2WithHmacSHA256); + SecretKeyFactory factory = SecretKeyFactory.getInstance(PBKDF2_WITH_HMAC_SHA256); return factory.generateSecret(spec).getEncoded(); } @@ -54,10 +54,10 @@ public static String encrypt(String input) { SecretKey key = getKeyFromPassword(); final IvParameterSpec iv = generateIv(); try { - Cipher cipher = Cipher.getInstance(algorithm); + 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()); + return ENCODER.encodeToString(cipherText) + DELIMITER + ENCODER.encodeToString(iv.getIV()); } catch (Exception e) { throw new RuntimeException(e); } @@ -67,18 +67,18 @@ public static String decrypt(String cipherText) { if (cipherText == null) { throw new IllegalArgumentException("암호화된 문자열에 값이 없습니다."); } - final String[] encryptAndIv = cipherText.split(delimiter); + final String[] encryptAndIv = cipherText.split(DELIMITER); final SecretKey key = getKeyFromPassword(); 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]); + 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 cipher = Cipher.getInstance(ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, key, iv); byte[] plainText = cipher.doFinal(encryptBytes); return new String(plainText); diff --git a/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java b/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java index cf77ad7..423a1ee 100644 --- a/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java +++ b/src/main/java/shop/sendbox/sendbox/util/HashingEncrypt.java @@ -8,7 +8,7 @@ import com.google.common.hash.Hashing; public class HashingEncrypt { - static final int saltLength = 16; + static final int SALT_LENGTH = 16; public static String encrypt(String input, String salt) { final HashCode hashCode = Hashing.sha256().hashString(input + salt, Charsets.UTF_8); @@ -17,7 +17,7 @@ public static String encrypt(String input, String salt) { public static String generateSalt() { SecureRandom random = new SecureRandom(); - byte[] salt = new byte[saltLength]; + byte[] salt = new byte[SALT_LENGTH]; random.nextBytes(salt); return Base64.getEncoder().encodeToString(salt); } From b136ab68d045866349bb87e95a960cb6209a53c9 Mon Sep 17 00:00:00 2001 From: kamser0415 Date: Mon, 28 Oct 2024 22:42:11 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EC=95=A0=EB=85=B8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EC=97=AD=ED=95=A0=20=EC=84=A4=EB=AA=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sendbox/sendbox/buyer/ApiResponse.java | 1 - .../shop/sendbox/sendbox/buyer/Buyer.java | 9 +++++++- .../sendbox/buyer/BuyerController.java | 7 +++--- .../sendbox/sendbox/buyer/BuyerService.java | 7 ++++-- .../shop/sendbox/sendbox/util/AesEncrypt.java | 4 ++++ .../sendbox/buyer/ApiResponseTest.java | 1 - .../shop/sendbox/sendbox/buyer/BuyerTest.java | 7 +++++- .../sendbox/login/LoginControllerTest.java | 23 ++++++++++++------- 8 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/main/java/shop/sendbox/sendbox/buyer/ApiResponse.java b/src/main/java/shop/sendbox/sendbox/buyer/ApiResponse.java index c42e81c..cb1badc 100644 --- a/src/main/java/shop/sendbox/sendbox/buyer/ApiResponse.java +++ b/src/main/java/shop/sendbox/sendbox/buyer/ApiResponse.java @@ -4,7 +4,6 @@ import lombok.Getter; -// Getter 으로 Jackson 라이브러리가 JSON 형태로 변환할 수 있도록 한다 @Getter public class ApiResponse { diff --git a/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java b/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java index 2ab564b..a242a69 100644 --- a/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java +++ b/src/main/java/shop/sendbox/sendbox/buyer/Buyer.java @@ -15,7 +15,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; -// @Entity 어노테이션은 JPA가 관리하는 엔티티 클래스임을 나타냅니다. +/* +@Entity 을 추가하면 JPA 스캐너에 의해 JPA 엔티티로 인식되며, +데이터베이스 테이블과 매핑합니다. + +@Getter는 클래스내 모든 필드의 get 메소드를 자동으로 만들어줍니다. + +@NoArgsConstructor는 기본 생성자를 만들어주며 접근 권한을 access 값으로 설정할 수 있습니다. + */ @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/shop/sendbox/sendbox/buyer/BuyerController.java b/src/main/java/shop/sendbox/sendbox/buyer/BuyerController.java index 4e5d0fd..e99a9dc 100644 --- a/src/main/java/shop/sendbox/sendbox/buyer/BuyerController.java +++ b/src/main/java/shop/sendbox/sendbox/buyer/BuyerController.java @@ -7,9 +7,10 @@ import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; - -// 빈 등록과 해당 클래스가 컨트롤러 역할,그리고 Json 응답을 변환해주는 @RestController 애노테이션을 추가했습니다. -// RequiredArgsConstructor는 생성자에 필요한 필수 파라미터가 포함된 생성자를 만들어줍니다. +/* +빈 등록과 해당 클래스가 컨트롤러 역할,그리고 Json 응답을 변환해주는 @RestController 애노테이션을 추가했습니다. +RequiredArgsConstructor는 생성자에 필요한 필수 파라미터가 포함된 생성자를 만들어줍니다. + */ @RestController @RequiredArgsConstructor public class BuyerController { diff --git a/src/main/java/shop/sendbox/sendbox/buyer/BuyerService.java b/src/main/java/shop/sendbox/sendbox/buyer/BuyerService.java index 36bc800..967de60 100644 --- a/src/main/java/shop/sendbox/sendbox/buyer/BuyerService.java +++ b/src/main/java/shop/sendbox/sendbox/buyer/BuyerService.java @@ -19,8 +19,11 @@ public class BuyerService implements LoginHandler { private final BuyerRepository buyerRepository; - /** - * Transactional을 명시적으로 추가하여 해당 메서드는 트랜잭션 범위에서 실행됨을 명시했습니다. + /* + @Transactional 애노테이션은 메서드가 실행될 때 트랜잭션을 시작합니다. + 메서드가 정상 종료되면 트랜잭션을 커밋합니다. + 만약 예외가 발생하면 롤백합니다. + 예외는 런타임 예외를 지정하거나 rollbackFor 속성을 사용하여 롤백할 예외를 지정할 수 있습니다. */ @Transactional public BuyerResponse signUp(final BuyerRequest buyerRequest, final LocalDateTime createdAt) { diff --git a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java index ddbe990..f0511b1 100644 --- a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java +++ b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java @@ -15,6 +15,10 @@ import lombok.extern.slf4j.Slf4j; +/* +@Slf4j 애노테이션을 클래스에 붙이면 자동으로 log 객체를 생성합니다. +log 객체를 직접 생성하지 않고 log 메서드를 사용할 수 있습니다. + */ @Slf4j public class AesEncrypt { diff --git a/src/test/java/shop/sendbox/sendbox/buyer/ApiResponseTest.java b/src/test/java/shop/sendbox/sendbox/buyer/ApiResponseTest.java index 53e3fab..37e8726 100644 --- a/src/test/java/shop/sendbox/sendbox/buyer/ApiResponseTest.java +++ b/src/test/java/shop/sendbox/sendbox/buyer/ApiResponseTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.*; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.HttpStatus; diff --git a/src/test/java/shop/sendbox/sendbox/buyer/BuyerTest.java b/src/test/java/shop/sendbox/sendbox/buyer/BuyerTest.java index 470f98c..a8edfbe 100644 --- a/src/test/java/shop/sendbox/sendbox/buyer/BuyerTest.java +++ b/src/test/java/shop/sendbox/sendbox/buyer/BuyerTest.java @@ -9,7 +9,12 @@ import org.junit.jupiter.api.Test; class BuyerTest { - + /* + @Test 애노테이션이 붙은 메서드는 테스트 메서드로 인식됩니다. + Junit 프레임워크는 @Test 붙은 메서드를 찾아서 실행하며, + 코드의 특정 부분이 제대로 동작하는지 검증할 수 있습니다. + 해당 애노테이션이 붙은 메서드는 private이나 static이 아니여야합니다. + */ @Test @DisplayName("구매자가 회원가입을 하면 활성화 상태이다.") void create() { diff --git a/src/test/java/shop/sendbox/sendbox/login/LoginControllerTest.java b/src/test/java/shop/sendbox/sendbox/login/LoginControllerTest.java index 6830724..0fa8129 100644 --- a/src/test/java/shop/sendbox/sendbox/login/LoginControllerTest.java +++ b/src/test/java/shop/sendbox/sendbox/login/LoginControllerTest.java @@ -5,33 +5,40 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpSession; import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MockMvcBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +/* +MVC 테스트와 관련된 컴포넌트만 등록합니다. +@Controller,@ControllerAdvice,@JsonComponent,@Converter,@Filter,@WebMvcConfigure 빈이 포함되며 +@Component,@Service,@Repository 빈은 포함되지 않습니다. +컨트롤러 계층을 슬라이스 테스트 하고 싶을 때 사용합니다. + */ @WebMvcTest(controllers = {LoginController.class}) class LoginControllerTest { @Autowired ObjectMapper objectMapper; - + /* + @Autowired의 역할은 스프링 컨테이너에 등록된 빈을 주입하는 것입니다. + MockMvc는 @WebMvcTest을 사용하는 경우 제공되는 빈입니다. + */ @Autowired MockMvc mockMvc; - + /* + @WebMvcTest에 필요한 의존성 빈을 Mock으로 대체합니다. + Mock이란 실제 객체와 동일한 구조를 가지지만 실제 로직이나 기능을 수행하지 않는 객체를 말합니다. + 컨트롤러를 테스트 할때 필요한 결과를 반환하도록 설정할 수 있습니다. + */ @MockBean LoginService loginService; From 45dd3b1844f51a45348bedc6ad4686ffe165fbaf Mon Sep 17 00:00:00 2001 From: kamser0415 Date: Thu, 14 Nov 2024 23:27:57 +0900 Subject: [PATCH 4/4] fix: encrypt password, salt --- .../shop/sendbox/sendbox/util/AesEncrypt.java | 21 +++++++++---------- .../sendbox/sendbox/util/AesEncryptTest.java | 15 +++++++++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java index f0511b1..2d61b28 100644 --- a/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java +++ b/src/main/java/shop/sendbox/sendbox/util/AesEncrypt.java @@ -16,8 +16,8 @@ import lombok.extern.slf4j.Slf4j; /* -@Slf4j 애노테이션을 클래스에 붙이면 자동으로 log 객체를 생성합니다. -log 객체를 직접 생성하지 않고 log 메서드를 사용할 수 있습니다. +애노테이션 프로세서는 해당 애노테이션을 확인하면 컴파일 시점에 Logger 클래스를 생성합니다. +따라서 별도의 Logger 객체를 생성할 필요 없이 log 객체를 사용할 수 있습니다. */ @Slf4j public class AesEncrypt { @@ -27,15 +27,14 @@ public class AesEncrypt { * TODO * 패스워드와 salt를 어떻게 보관할 것인지 고민입니다. */ - private static final String PASSWORD = "password"; - private static final String 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() { - KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), SALT.getBytes(), 65536, 256); + 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) { @@ -49,13 +48,13 @@ private static byte[] generateSecret(final KeySpec spec) throws InvalidKeySpecEx } private static IvParameterSpec generateIv() { - byte[] iv = new byte[16]; + byte[] iv = new byte[IV_LENGTH]; new SecureRandom().nextBytes(iv); return new IvParameterSpec(iv); } - public static String encrypt(String input) { - SecretKey key = getKeyFromPassword(); + 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); @@ -67,12 +66,12 @@ public static String encrypt(String input) { } } - public static String decrypt(String cipherText) { + 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(); + final SecretKey key = getKeyFromPassword(password, salt); if (cipherText.isEmpty() || encryptAndIv.length != 2) { throw new IllegalArgumentException("암호화된 문자열 양식이 올바르지 않습니다."); } diff --git a/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java b/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java index e5f9259..0b80930 100644 --- a/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java +++ b/src/test/java/shop/sendbox/sendbox/util/AesEncryptTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; + class AesEncryptTest { @Test @@ -11,10 +12,12 @@ class AesEncryptTest { void encryptTest() { // given String input = "test"; - String encrypted = AesEncrypt.encrypt(input); + final String password = "AesEncrypt.PASSWORD"; + final String salt = "AesEncrypt.SALT"; + String encrypted = AesEncrypt.encrypt(password, salt, input); // when - String decrypted = AesEncrypt.decrypt(encrypted); + String decrypted = AesEncrypt.decrypt(password, salt, encrypted); // then Assertions.assertThat(decrypted).isEqualTo(input); @@ -25,9 +28,11 @@ void encryptTest() { void decryptTestWithNull() { // given String encrypted = null; + final String password = "AesEncrypt.PASSWORD"; + final String salt = "AesEncrypt.SALT"; // when & then - Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(encrypted)) + Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(password, salt, encrypted)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("암호화된 문자열에 값이 없습니다."); } @@ -37,9 +42,11 @@ void decryptTestWithNull() { void decryptTestWithNot() { // given String encrypted = "test"; + final String password = "AesEncrypt.PASSWORD"; + final String salt = "AesEncrypt.SALT"; // when & then - Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(encrypted)) + Assertions.assertThatThrownBy(() -> AesEncrypt.decrypt(password, salt, encrypted)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("암호화된 문자열 양식이 올바르지 않습니다."); }