feat: AES-256-GCM 암호화 적용 (tel, email), 로그/배포 설정 추가
This commit is contained in:
@@ -2,6 +2,7 @@ package com.owrawww.service;
|
||||
|
||||
import com.owrawww.domain.Careers;
|
||||
import com.owrawww.domain.mapper.CareersMapper;
|
||||
import com.owrawww.util.AesUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -22,6 +23,7 @@ public class CareersService {
|
||||
private String uploadPath;
|
||||
|
||||
private final CareersMapper careersMapper;
|
||||
private final AesUtil aesUtil;
|
||||
|
||||
public boolean submit(Careers careers, MultipartFile file) throws IOException {
|
||||
Careers saved = new Careers();
|
||||
@@ -29,8 +31,8 @@ public class CareersService {
|
||||
saved.setTitle(careers.getTitle());
|
||||
saved.setComment(careers.getContent());
|
||||
saved.setName(careers.getName());
|
||||
saved.setTel(careers.getTel());
|
||||
saved.setEmail(careers.getEmail());
|
||||
saved.setTel(aesUtil.encrypt(careers.getTel()));
|
||||
saved.setEmail(aesUtil.encrypt(careers.getEmail()));
|
||||
saved.setTopCode(2);
|
||||
saved.setLeftCode(1);
|
||||
saved.setSubGubun(1);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.owrawww.service;
|
||||
|
||||
import com.owrawww.domain.Inquiry;
|
||||
import com.owrawww.domain.mapper.InquiryMapper;
|
||||
import com.owrawww.util.AesUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -10,6 +11,7 @@ import org.springframework.stereotype.Service;
|
||||
public class InquiryService {
|
||||
|
||||
private final InquiryMapper inquiryMapper;
|
||||
private final AesUtil aesUtil;
|
||||
|
||||
public boolean submit(Inquiry inquiry) {
|
||||
Inquiry savedInquiry = new Inquiry();
|
||||
@@ -17,8 +19,8 @@ public class InquiryService {
|
||||
savedInquiry.setTitle(inquiry.getTitle());
|
||||
savedInquiry.setComment(inquiry.getContent());
|
||||
savedInquiry.setName(inquiry.getName());
|
||||
savedInquiry.setTel(inquiry.getTel());
|
||||
savedInquiry.setEmail(inquiry.getEmail());
|
||||
savedInquiry.setTel(aesUtil.encrypt(inquiry.getTel()));
|
||||
savedInquiry.setEmail(aesUtil.encrypt(inquiry.getEmail()));
|
||||
savedInquiry.setTopCode(2);
|
||||
savedInquiry.setLeftCode(1);
|
||||
savedInquiry.setSubGubun(1);
|
||||
|
||||
78
src/main/java/com/owrawww/util/AesUtil.java
Normal file
78
src/main/java/com/owrawww/util/AesUtil.java
Normal file
@@ -0,0 +1,78 @@
|
||||
package com.owrawww.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.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* AES-256-GCM 암호화/복호화 유틸리티
|
||||
* - IV(12 bytes) + 암호문을 Base64로 인코딩하여 저장
|
||||
*/
|
||||
@Component
|
||||
public class AesUtil {
|
||||
|
||||
private static final String ALGORITHM = "AES/GCM/NoPadding";
|
||||
private static final int IV_LENGTH = 12; // GCM 권장 IV 크기
|
||||
private static final int TAG_BIT_LEN = 128; // GCM 인증 태그 크기
|
||||
|
||||
private final SecretKey secretKey;
|
||||
|
||||
public AesUtil(@Value("${app.encryption.key}") String base64Key) {
|
||||
byte[] keyBytes = Base64.getDecoder().decode(base64Key);
|
||||
if (keyBytes.length != 32) {
|
||||
throw new IllegalArgumentException("암호화 키는 Base64 인코딩된 32바이트(256bit)여야 합니다.");
|
||||
}
|
||||
this.secretKey = new SecretKeySpec(keyBytes, "AES");
|
||||
}
|
||||
|
||||
/**
|
||||
* 평문 → AES-256-GCM 암호화 → Base64 문자열
|
||||
*/
|
||||
public String encrypt(String plainText) {
|
||||
if (plainText == null || plainText.isEmpty()) return plainText;
|
||||
try {
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
new SecureRandom().nextBytes(iv);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(TAG_BIT_LEN, iv));
|
||||
byte[] encrypted = cipher.doFinal(plainText.getBytes("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("암호화 실패", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64 암호문 → AES-256-GCM 복호화 → 평문
|
||||
*/
|
||||
public String decrypt(String encryptedText) {
|
||||
if (encryptedText == null || encryptedText.isEmpty()) return encryptedText;
|
||||
try {
|
||||
byte[] combined = Base64.getDecoder().decode(encryptedText);
|
||||
byte[] iv = new byte[IV_LENGTH];
|
||||
byte[] cipherText = new byte[combined.length - IV_LENGTH];
|
||||
|
||||
System.arraycopy(combined, 0, iv, 0, IV_LENGTH);
|
||||
System.arraycopy(combined, IV_LENGTH, cipherText, 0, cipherText.length);
|
||||
|
||||
Cipher cipher = Cipher.getInstance(ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(TAG_BIT_LEN, iv));
|
||||
return new String(cipher.doFinal(cipherText), "UTF-8");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("복호화 실패", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,12 @@
|
||||
{"properties": [{
|
||||
"name": "app.upload.path",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'app.upload.path'"
|
||||
}]}
|
||||
{"properties": [
|
||||
{
|
||||
"name": "app.upload.path",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'app.upload.path'"
|
||||
},
|
||||
{
|
||||
"name": "app.encryption.key",
|
||||
"type": "java.lang.String",
|
||||
"description": "A description for 'app.encryption.key'"
|
||||
}
|
||||
]}
|
||||
@@ -28,6 +28,9 @@ logging:
|
||||
max-history: 30
|
||||
file-name-pattern: /home/www/owrainfo/logs/owrawww.%d{yyyy-MM-dd}.%i.log
|
||||
|
||||
app:
|
||||
app:
|
||||
upload:
|
||||
path: /home/www/owrainfo/uploads
|
||||
encryption:
|
||||
key: PIYAsB81yr7uETVoM/E7Sz9Sp5kNB46uHyjnfQE3XOs= # 운영 전용 키로 교체 필요 (base64 인코딩된 32바이트)
|
||||
|
||||
@@ -30,3 +30,5 @@ server:
|
||||
app:
|
||||
upload:
|
||||
path: D:/uploads/owrawww/careers # 기본값 (프로파일별로 override)
|
||||
encryption:
|
||||
key: PIYAsB81yr7uETVoM/E7Sz9Sp5kNB46uHyjnfQE3XOs= # AES-256 키 (운영에서는 반드시 별도 키로 교체)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<div class="inner">
|
||||
<div class="bottom">
|
||||
<ul class="ft_menu">
|
||||
<li><a href="">이용약관</a></li>
|
||||
<li><a href="" class="point">개인정보처리방침</a></li>
|
||||
<li><a href="">회사소개</a></li>
|
||||
<li><a href="">제휴문의</a></li>
|
||||
<li><a href="/terms">이용약관</a></li>
|
||||
<li><a href="/privacy" class="point">개인정보처리방침</a></li>
|
||||
<li><a href="/company/intro">회사소개</a></li>
|
||||
<li><a href="/bbs/partnership">제휴문의</a></li>
|
||||
</ul>
|
||||
<div class="ft-info">
|
||||
<div class="company-info">
|
||||
|
||||
Reference in New Issue
Block a user