From ee3033a2793fa3f03b8f026dc4dd59c167c92085 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Sat, 25 Jan 2025 15:22:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=20feat:=20AES256,=20SHA256=20=EC=95=94?= =?UTF-8?q?=ED=98=B8=ED=99=94=20=EC=95=8C=EA=B3=A0=EB=A6=AC=EC=A6=98=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/encrypt/Aes256Cipher.java | 82 +++++++++++++++++++ .../common/encrypt/Sha256Cipher.java | 36 ++++++++ .../listywave/common/exception/ErrorCode.java | 1 + src/main/resources/application.yml | 8 +- .../com/listywave/common/IntegrationTest.java | 6 ++ .../common/encrypt/Aes256CipherTest.java | 43 ++++++++++ .../common/encrypt/Sha256CipherTest.java | 23 ++++++ src/test/resources/application.yml | 6 ++ 8 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/listywave/common/encrypt/Aes256Cipher.java create mode 100644 src/main/java/com/listywave/common/encrypt/Sha256Cipher.java create mode 100644 src/test/java/com/listywave/common/encrypt/Aes256CipherTest.java create mode 100644 src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java diff --git a/src/main/java/com/listywave/common/encrypt/Aes256Cipher.java b/src/main/java/com/listywave/common/encrypt/Aes256Cipher.java new file mode 100644 index 00000000..11b74393 --- /dev/null +++ b/src/main/java/com/listywave/common/encrypt/Aes256Cipher.java @@ -0,0 +1,82 @@ +package com.listywave.common.encrypt; + +import static com.listywave.common.exception.ErrorCode.ENCRYPT_ERROR; + +import com.listywave.common.exception.CustomException; +import jakarta.annotation.PostConstruct; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +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.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class Aes256Cipher { + + private static final String ALGORITHM = "AES/CBC/PKCS5Padding"; + + @Value("${aes.password}") + private String password; + @Value("${aes.salt}") + private String salt; + + private SecretKey secretKey; + private IvParameterSpec iv; + + @PostConstruct + public void init() throws NoSuchAlgorithmException, InvalidKeySpecException { + this.secretKey = createSecretKey(); + this.iv = createIv(); + } + + private SecretKey createSecretKey() throws NoSuchAlgorithmException, InvalidKeySpecException { + SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256); + return new SecretKeySpec(secretKeyFactory.generateSecret(keySpec).getEncoded(), "AES"); + } + + private IvParameterSpec createIv() { + byte[] iv = new byte[16]; + new SecureRandom().nextBytes(iv); + return new IvParameterSpec(iv); + } + + public String encrypt(String plainText) { + try { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv); + byte[] cipherText = cipher.doFinal(plainText.getBytes()); + return Base64.getEncoder().encodeToString(cipherText); + } catch ( + NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | + InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException e) { + throw new CustomException(ENCRYPT_ERROR, e.getMessage()); + } + } + + public String decrypt(String cipherText) { + try { + Cipher cipher = Cipher.getInstance(ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKey, iv); + byte[] plainText = cipher.doFinal(Base64.getDecoder().decode(cipherText)); + return new String(plainText); + } catch ( + NoSuchPaddingException | IllegalBlockSizeException | NoSuchAlgorithmException | + InvalidAlgorithmParameterException | BadPaddingException | InvalidKeyException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/listywave/common/encrypt/Sha256Cipher.java b/src/main/java/com/listywave/common/encrypt/Sha256Cipher.java new file mode 100644 index 00000000..ed5fc6da --- /dev/null +++ b/src/main/java/com/listywave/common/encrypt/Sha256Cipher.java @@ -0,0 +1,36 @@ +package com.listywave.common.encrypt; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class Sha256Cipher { + + @Value("${sha.salt}") + private String salt; + + public String encrypt(String plainText) { + try { + MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + byte[] encodedHash = messageDigest.digest(plainText.concat(salt).getBytes()); + return bytesToHex(encodedHash); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + private String bytesToHex(byte[] encodedHash) { + StringBuilder hexString = new StringBuilder(2 * encodedHash.length); + + for (byte hash : encodedHash) { + String hex = Integer.toHexString(0xff & hash); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } +} diff --git a/src/main/java/com/listywave/common/exception/ErrorCode.java b/src/main/java/com/listywave/common/exception/ErrorCode.java index 31031edc..9bd4c187 100644 --- a/src/main/java/com/listywave/common/exception/ErrorCode.java +++ b/src/main/java/com/listywave/common/exception/ErrorCode.java @@ -21,6 +21,7 @@ public enum ErrorCode { INVALID_ACCESS_TOKEN(UNAUTHORIZED, "유효하지 않은 AccessToken 입니다. 다시 로그인해주세요."), INVALID_ACCESS(FORBIDDEN, "접근 권한이 존재하지 않습니다."), CANNOT_COLLECT_OWN_LIST(BAD_REQUEST, "리스트 작성자는 자신의 리스트에 콜렉트할 수 없습니다."), + ENCRYPT_ERROR(INTERNAL_SERVER_ERROR, "암호화 과정 중 문제가 발생했습니다."), // Http Request METHOD_ARGUMENT_TYPE_MISMATCH(BAD_REQUEST, "요청 한 값 타입이 잘못되어 binding에 실패하였습니다."), diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0a89fb32..379d7fb0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -7,7 +7,7 @@ spring: jpa: hibernate: - ddl-auto: create + ddl-auto: none properties: hibernate: format_sql: true @@ -72,3 +72,9 @@ server: local-login: id: password: + +aes: + password: 12345 + salt: 54321 +sha: + salt: 12345 diff --git a/src/test/java/com/listywave/common/IntegrationTest.java b/src/test/java/com/listywave/common/IntegrationTest.java index 2b86fd38..7757c9bf 100644 --- a/src/test/java/com/listywave/common/IntegrationTest.java +++ b/src/test/java/com/listywave/common/IntegrationTest.java @@ -14,6 +14,8 @@ import com.listywave.collection.application.service.CollectionService; import com.listywave.collection.application.service.FolderService; import com.listywave.collection.repository.CollectionRepository; +import com.listywave.common.encrypt.Aes256Cipher; +import com.listywave.common.encrypt.Sha256Cipher; import com.listywave.history.repository.HistoryRepository; import com.listywave.list.application.domain.list.ListEntity; import com.listywave.list.application.service.CommentService; @@ -102,6 +104,10 @@ public abstract class IntegrationTest { protected NoticeRepository noticeRepository; @Autowired protected ReactionService reactionService; + @Autowired + protected Aes256Cipher aes256Cipher; + @Autowired + protected Sha256Cipher sha256Cipher; protected User dh, js, ej, sy; protected ListEntity list; diff --git a/src/test/java/com/listywave/common/encrypt/Aes256CipherTest.java b/src/test/java/com/listywave/common/encrypt/Aes256CipherTest.java new file mode 100644 index 00000000..f4f30c13 --- /dev/null +++ b/src/test/java/com/listywave/common/encrypt/Aes256CipherTest.java @@ -0,0 +1,43 @@ +package com.listywave.common.encrypt; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.listywave.common.IntegrationTest; +import org.junit.jupiter.api.Test; + +class Aes256CipherTest extends IntegrationTest { + + @Test + void 암호화를_한다() { + // given + String plainText = "plain"; + + // when + String result = aes256Cipher.encrypt(plainText); + String result2 = aes256Cipher.encrypt(plainText); + String result3 = aes256Cipher.encrypt(plainText); + + // then + assertAll( + () -> assertThat(result).isEqualTo(result2).isEqualTo(result3), + () -> assertThat(plainText).isNotEqualTo(result) + .isNotEqualTo(result2) + .isNotEqualTo(result3) + ); + } + + @Test + void 복호화를_한다() { + // given + String plainText = "plain"; + + String encrypted = aes256Cipher.encrypt(plainText); + + // when + String decrypted = aes256Cipher.decrypt(encrypted); + + // then + assertThat(plainText).isEqualTo(decrypted); + } +} diff --git a/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java b/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java new file mode 100644 index 00000000..5d48dcdb --- /dev/null +++ b/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java @@ -0,0 +1,23 @@ +package com.listywave.common.encrypt; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.listywave.common.IntegrationTest; +import org.junit.jupiter.api.Test; + +class Sha256CipherTest extends IntegrationTest { + + @Test + void 암호화를_한다() { + // given + String plainText = "myPassword"; + + // when + String encrypted1 = sha256Cipher.encrypt(plainText); + String encrypted2 = sha256Cipher.encrypt(plainText); + + // then + assertThat(plainText).isNotEqualTo(encrypted1); + assertThat(encrypted1).isEqualTo(encrypted2); + } +} diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 1e9587a2..7f651f96 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -58,3 +58,9 @@ jwt: local-login: id: password: + +aes: + password: 12345 + salt: 54321 +sha: + salt: 12345 From ca3a08962b8abc995799ba9d3ac657db9fe1ec85 Mon Sep 17 00:00:00 2001 From: kdkdhoho Date: Mon, 27 Jan 2025 11:51:32 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=20feat:=20=EC=96=B4=EB=93=9C=EB=AF=BC=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=88=98=EC=A0=95=20API?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20(#342)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/listywave/admin/Admin.java | 4 ++++ .../java/com/listywave/admin/AdminController.java | 8 ++++++++ .../java/com/listywave/admin/AdminService.java | 15 ++++++++++++++- .../com/listywave/admin/AdminUpdateRequest.java | 6 ++++++ .../common/encrypt/Sha256CipherTest.java | 2 +- 5 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/listywave/admin/AdminUpdateRequest.java diff --git a/src/main/java/com/listywave/admin/Admin.java b/src/main/java/com/listywave/admin/Admin.java index 23a14704..6a6d61ff 100644 --- a/src/main/java/com/listywave/admin/Admin.java +++ b/src/main/java/com/listywave/admin/Admin.java @@ -38,4 +38,8 @@ public void validatePassword(String password) { } throw new CustomException(INVALID_ACCESS); } + + public void update(String password) { + this.password = password; + } } diff --git a/src/main/java/com/listywave/admin/AdminController.java b/src/main/java/com/listywave/admin/AdminController.java index 4ffe5d66..3c3299f2 100644 --- a/src/main/java/com/listywave/admin/AdminController.java +++ b/src/main/java/com/listywave/admin/AdminController.java @@ -2,6 +2,7 @@ import static com.listywave.common.exception.ErrorCode.RESOURCE_NOT_FOUND; +import com.listywave.common.auth.Auth; import com.listywave.common.exception.CustomException; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; @@ -10,6 +11,7 @@ import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @Controller @@ -45,4 +47,10 @@ ResponseEntity login(@RequestBody AdminLoginRequest adminLog AdminLoginResponse result = adminService.login(adminLoginRequest.account(), adminLoginRequest.password()); return ResponseEntity.ok(result); } + + @PutMapping("/admin") + ResponseEntity updateInfo(@Auth Long adminId, @RequestBody AdminUpdateRequest request) { + adminService.update(adminId, request.password()); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/listywave/admin/AdminService.java b/src/main/java/com/listywave/admin/AdminService.java index 3b2b275f..c7dd5afc 100644 --- a/src/main/java/com/listywave/admin/AdminService.java +++ b/src/main/java/com/listywave/admin/AdminService.java @@ -3,6 +3,7 @@ import static com.listywave.common.exception.ErrorCode.INVALID_ACCESS; import com.listywave.auth.application.domain.JwtManager; +import com.listywave.common.encrypt.Sha256Cipher; import com.listywave.common.exception.CustomException; import java.util.Optional; import lombok.RequiredArgsConstructor; @@ -15,6 +16,7 @@ public class AdminService { private final JwtManager jwtManager; + private final Sha256Cipher sha256Cipher; private final AdminRepository adminRepository; @Transactional(readOnly = true) @@ -27,7 +29,12 @@ public AdminLoginResponse login(String account, String password) { Optional optionalAdmin = adminRepository.findByAccount(account); if (optionalAdmin.isPresent()) { Admin admin = optionalAdmin.get(); - admin.validatePassword(password); + + // 암호화 적용으로 인해, 임시로 작성해둔 코드입니다. + // 모든 어드민이 암호를 변경하면 if 조건식만 제거합니다. + if (!password.equals("12345")) { + admin.validatePassword(sha256Cipher.encrypt(password)); // 해당 라인은 제거하지 않습니다. + } String accessToken = jwtManager.createAdminAccessToken(admin.getId()); String refreshToken = jwtManager.createAdminRefreshToken(admin.getId()); @@ -35,4 +42,10 @@ public AdminLoginResponse login(String account, String password) { } throw new CustomException(INVALID_ACCESS); } + + public void update(Long adminId, String password) { + Admin admin = adminRepository.getById(adminId); + String encryptedNewPassword = sha256Cipher.encrypt(password); + admin.update(encryptedNewPassword); + } } diff --git a/src/main/java/com/listywave/admin/AdminUpdateRequest.java b/src/main/java/com/listywave/admin/AdminUpdateRequest.java new file mode 100644 index 00000000..88762a7d --- /dev/null +++ b/src/main/java/com/listywave/admin/AdminUpdateRequest.java @@ -0,0 +1,6 @@ +package com.listywave.admin; + +public record AdminUpdateRequest( + String password +) { +} diff --git a/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java b/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java index 5d48dcdb..cfa46743 100644 --- a/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java +++ b/src/test/java/com/listywave/common/encrypt/Sha256CipherTest.java @@ -10,7 +10,7 @@ class Sha256CipherTest extends IntegrationTest { @Test void 암호화를_한다() { // given - String plainText = "myPassword"; + String plainText = "1234"; // when String encrypted1 = sha256Cipher.encrypt(plainText);