feat: AES-256-GCM 암호화 적용 (tel, email), 로그/배포 설정 추가

This commit is contained in:
2026-04-21 10:32:46 +09:00
parent c9d41ef288
commit 22204a48a1
7 changed files with 108 additions and 14 deletions

View File

@@ -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);

View File

@@ -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);

View 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);
}
}
}

View File

@@ -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'"
}
]}

View File

@@ -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바이트)

View File

@@ -30,3 +30,5 @@ server:
app:
upload:
path: D:/uploads/owrawww/careers # 기본값 (프로파일별로 override)
encryption:
key: PIYAsB81yr7uETVoM/E7Sz9Sp5kNB46uHyjnfQE3XOs= # AES-256 키 (운영에서는 반드시 별도 키로 교체)

View File

@@ -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">