공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 13 / 22

암호화·개인정보 보호

AES-GCM, 키 관리·교체, 검색용 HMAC, 컬럼 암호화, 마스킹
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 인코딩 vs 해시 vs 암호화

세 개념은 자주 섞여 쓰이지만 목적이 전혀 다릅니다. 표로 구분합니다.

구분 목적 원본 복원 예
인코딩 데이터 형식 변환 항상 가능 Base64, URL 인코딩
해시 무결성·비밀번호 검증 불가능(일방향) SHA-256, PBKDF2
암호화 기밀성 보호, 필요 시 복원 키가 있으면 가능 AES-GCM, RSA

Base64 는 암호화가 아닙니다. 누구나 디코딩할 수 있는 인코딩일 뿐이므로, "Base64 로 암호화했다"는 표현은 코드 리뷰에서 반드시 지적해야 할 오해입니다.

2.2 개인정보 안전성 확보조치 기준이 요구하는 것

법령 조문을 전부 외울 필요는 없지만, 개발자가 코드로 직접 구현해야 하는 부분은 명확합니다. 네 가지로 요약합니다.

  • 비밀번호: 복호화 불가능한 일방향 해시로 저장(인증 01 PBKDF2 참조)
  • 고유식별정보(주민번호)·계좌번호·카드번호: 암호화하여 저장
  • 전송 구간: TLS 로 네트워크 도청 방지
  • 접근 기록: 개인정보 처리 시스템 접속·조회 기록을 일정 기간 보관

이 중 "암호화하여 저장"에 해당하는 항목을 이 레슨에서 AES-256-GCM 으로 구현합니다. 접근 기록 보관은 별도 로깅·감사 체계의 영역이라 이 레슨 범위 밖입니다.

2.3 AES-256-GCM 구조

GCM(Galois/Counter Mode) 은 암호화와 무결성 검증을 동시에 제공하는 인증 암호화(AEAD) 방식입니다. 네 요소로 구성됩니다.

  • 키: 256비트(32바이트), 노출되면 전체 데이터가 위험해지는 최상위 비밀
  • IV(nonce): 12바이트, 암호화할 때마다 무작위로 새로 생성
  • 인증 태그: 16바이트, 위·변조를 감지하는 서명 역할
  • AAD(선택): 암호화하지 않지만 무결성만 검증하고 싶은 부가 데이터

IV 재사용은 GCM 에서 가장 치명적인 실수입니다. 같은 키로 같은 IV 를 두 번 쓰면 암호문에서 평문 일부와 인증키가 복원될 수 있어, 반드시 SecureRandom 으로 매번 새로 생성해야 합니다.

2.4 레거시 AES/CBC/PKCS5Padding 과 패딩 오라클

기존 시스템에서 AES/CBC/PKCS5Padding 을 쓰는 경우를 여전히 자주 봅니다. CBC 는 무결성 검증이 없어서, 공격자가 암호문을 조작한 뒤 패딩 오류 메시지 유무로 평문을 한 바이트씩 복원하는 패딩 오라클 공격에 취약합니다.

신규 개발은 예외 없이 GCM 을 씁니다. 기존 CBC 암호문은 한꺼번에 재암호화하기보다, 읽을 때 CBC 로 복호화하고 쓸 때 GCM 으로 다시 암호화하는 지연 마이그레이션이 실무에서 부담이 적습니다.

2.5 키 관리

키를 코드나 Git 저장소에 넣는 것은 암호화를 하지 않은 것과 같습니다. 실무에서 쓰는 방법을 우선순위로 정리합니다.

  • 최선: KMS(클라우드) 또는 HSM(폐쇄망) 에서 키를 직접 관리
  • 차선: 환경 변수(CRYPTO_KEY_V2 등)로 배포 시 주입
  • 폐쇄망 최소 요건: 권한 600 파일에 키를 저장하고 애플리케이션 계정만 읽기 허용

키 교체(key rotation) 는 유출 의심이나 정기 보안 정책에 따라 발생합니다. 새 키로 새 데이터를 암호화하면서 기존 암호문은 그대로 두고, 접두어(v1:, v2:)로 어떤 키를 썼는지 구분해 점진적으로 재암호화하는 방식이 안전합니다.

2.6 암호문 저장 형식과 컬럼 길이

이 레슨의 저장 형식은 "v{버전}:" + Base64(IV 12바이트 + 암호문 + 태그 16바이트) 입니다. 버전 접두어 덕분에 여러 키가 섞여 있어도 복호화 시 올바른 키를 자동으로 선택할 수 있습니다.

Oracle VARCHAR2 컬럼 길이는 원문 길이 기준으로 넉넉히 잡아야 합니다. 대략적인 계산식은 아래와 같습니다.

text
Base64 길이 = ceil((IV 12 + 원문바이트 + 태그 16) / 3) * 4
저장 길이 = 버전 접두어 길이 + 1(콜론) + Base64 길이

예를 들어 주민등록번호(13바이트) 는 접두어 포함 저장 길이가 약 59자입니다. 여유를 두어 VARCHAR2(100) 정도로 잡는 편이 안전합니다.

2.7 검색용 HMAC 과 부분 검색 한계

GCM 암호문은 IV 가 매번 달라 같은 평문도 다른 암호문이 되므로, 암호화된 컬럼을 WHERE 컬럼 = ? 으로 직접 검색할 수 없습니다. 해결책은 별도의 검색용 해시 컬럼입니다.

같은 값을 HMAC-SHA256 으로 해시하면 항상 같은 결과가 나오므로, 이 해시를 별도 컬럼에 저장하고 검색은 해시 컬럼으로 합니다. 다만 HMAC 은 동등 비교만 가능하고, LIKE 부분 검색이나 정렬은 지원하지 않는다는 한계가 있습니다.

2.8 MyBatis TypeHandler 로 투명 암복호화

TypeHandler 를 등록하면 매퍼 XML 이나 서비스 코드에서 암복호화를 신경 쓰지 않아도 됩니다. setParameter 에서 암호화하고 getResult 에서 복호화하는 구조입니다.

java
@MappedTypes(String.class)
public class EncryptedStringHandler extends BaseTypeHandler<String> {
    private final Crypto crypto; // 생성자로 주입

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            String parameter, JdbcType type) throws SQLException {
        ps.setString(i, crypto.encrypt(parameter));
    }

    @Override
    public String getNullableResult(ResultSet rs, String column)
            throws SQLException {
        String token = rs.getString(column);
        return token == null ? null : crypto.decrypt(token);
    }
}

매퍼 컬럼에 typeHandler="EncryptedStringHandler" 를 지정하면 애플리케이션 코드는 평문만 다루고, DB 에는 암호문만 저장됩니다.

2.9 마스킹과 암호화의 역할 분담

암호화는 저장소 보호, 마스킹은 화면·로그 노출 방지로 역할이 다릅니다. 두 개는 동시에 필요하고 서로 대체할 수 없습니다.

  • 암호화: DB 컬럼에 저장할 때, 복호화 가능해야 함
  • 마스킹: 화면 출력·로그 기록 시, 복원할 필요 없음

콜센터 상담원 화면에 주민번호를 그대로 띄우면, DB 는 암호화됐어도 화면 캡처나 로그 유출로 개인정보가 노출됩니다. 조회 화면은 항상 마스킹된 값을 기본으로 하고, 필요한 경우만 별도 권한 확인 후 복호화된 값을 보여줍니다.

2.10 로그·힙 덤프·SecureRandom 주의

예외 메시지나 디버그 로그에 평문 개인정보를 그대로 찍는 실수가 흔합니다. log.debug("rrn={}", rrn) 같은 코드는 운영 로그 서버에 평문이 영구히 남습니다.

힙 덤프도 같은 문제입니다. 장애 분석용 힙 덤프에는 그 시점 메모리의 모든 문자열이 그대로 담기므로, 가능하면 평문 보유 시간을 최소화하고 char[] 를 다 쓴 뒤 지우는 방식을 검토합니다.

SecureRandom 과 new Random() 은 반드시 구분해야 합니다. Random 은 시드만 알면 다음 값을 예측할 수 있어 IV·키 생성에 쓰면 안 되고, 암호학적으로 안전한 SecureRandom 만 사용합니다.

2.11 Spring 대응과 비밀번호 해시

Spring Boot 프로젝트에서 application.yml 의 DB 비밀번호를 평문으로 두면 Git 저장소나 배포 파일에 그대로 노출됩니다. Jasypt 라이브러리를 쓰면 password: ENC(암호화된값) 형태로 설정 파일에 저장할 수 있습니다.

yaml
spring:
  datasource:
    password: ENC(3sQK...)
jasypt:
  encryptor:
    password: ${JASYPT_KEY} # JVM 옵션이나 환경 변수로 주입

암호화 키(JASYPT_KEY) 는 설정 파일이 아닌 JVM 실행 옵션(-Djasypt.encryptor.password=...) 이나 환경 변수로 주입해야 설정 파일만 유출됐을 때도 안전합니다. 비밀번호 자체의 해시 저장(PBKDF2) 은 인증 01 레슨에서 다루므로 이 레슨은 반복하지 않습니다.

핵심 원리
  • 2.1 인코딩 vs 해시 vs 암호화
  • 2.2 개인정보 안전성 확보조치 기준이 요구하는 것
  • 2.3 AES-256-GCM 구조
  • 2.4 레거시 AES/CBC/PKCS5Padding 과 패딩 오라클
  • 2.5 키 관리
  • 2.6 암호문 저장 형식과 컬럼 길이
  • 2.7 검색용 HMAC 과 부분 검색 한계
  • 2.8 MyBatis TypeHandler 로 투명 암복호화
  • 2.9 마스킹과 암호화의 역할 분담
  • 2.10 로그·힙 덤프·SecureRandom 주의
  • 2.11 Spring 대응과 비밀번호 해시
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제