컬리가 2023년 9월 오픈한 새 배송 시스템에 쓸 자체 암호화 모듈 cowcow-enc 개발기다. 애플리케이션 레벨 암·복호화, 유연한 키 관리, 엔티티 자동 처리, 기존 평문 데이터 마이그레이션 네 가지 요구를 채우려고 오픈소스 대신 직접 구현했다. JPA @Converter 채택, 봉투(envelope) 암호화, base64 제거로 크기와 성능을 최적화했다.
핵심 포인트- 요구사항: 앱 레벨 암·복호화, 하루 여러 번 로테이션 가능한 외부 키 관리, 엔티티 조회/저장 시 자동 처리, 오픈 전 기존 평문 전량 암호화가 필요했다.
- 자체 구현 결정: 후보 오픈소스에 보안 취약점 이력이 있어, 외부 의존 대신 통제·유지보수·성능 최적화 자유를 얻으려 in-house 로 갔다.
- 자동 암복호 3안: @PostLoad 는 dirty check 로 불필요 UPDATE, Hibernate PreLoadEventListener 는 Spring Boot 3.0.4 버그, 최종은 JPA @Converter 를 선택했다.
- 최적화: 봉투 헤더 슬림화로 봉투 크기를 10~30% 줄이고, base64 를 없애 33% 오버헤드를 제거했다.
- 테스트: fixture-monkey/jqwik 로 1,000회 랜덤 암복호를 돌리고 IntelliJ Profiler 로 성능·메모리를 검증했다.
상세 정리- 배경: 2023년 9월 오픈하는 배송 시스템을 위해 데이터를 앱 레벨에서 암호화할 모듈이 필요했다.
- 오픈소스 배제: 여러 후보에서 보안 취약점이 보고돼, 미래 유지보수가 불투명한 외부 의존보다 자체 구현이 통제와 유연성 면에서 낫다고 판단했다.
- 1안 @PostLoad(탈락): 로드 후 @Transient 필드에 복호값을 넣자 Hibernate 가 엔티티를 dirty 로 판단해 읽기 쿼리에도 UPDATE 가 나갔다.
- 2안 PreLoadEventListener(탈락): 리플렉션으로 pre-load 시점에 복호화해 dirty 문제는 피했으나, Spring Boot 3.0.4 의 Hibernate 6.x PreLoad 타이밍 버그로 실패했다(6.2.0/Spring Boot 3.1 에서 수정).
- 3안 @Converter(채택): AttributeConverter 로 필드 단위 암복호를 처리하며 리플렉션이 불필요하고 타입 무관하며 JPQL/QueryDSL 의 DTO projection 에서도 정상 동작했다.
- 봉투 암호화: 클라우드 마스터 키로 암호화 키와 암호화된 키 버전을 받아 평문을 암호화하고, 암호문과 암호화된 키를 봉투로 묶은 뒤 평문 키는 즉시 폐기한다.
- 봉투 크기 최적화: 범용 오픈소스가 호환성 위해 넣는 불필요 메타데이터(키 길이 등)를 제거해 보안을 유지하며 크기를 10~30% 줄였다.
- base64 제거: 기존엔 암호문을 varchar/text 저장 위해 base64 인코딩(33% 오버헤드)했으나, PostgreSQL·MySQL 에 바이너리 타입으로 직접 저장해 인코딩/디코딩 비용과 저장 공간을 줄였다.
- 배포/의존성: 공용 아키텍처 모듈 저장소 cowcow 에 배포하고, CompileClasspath 는 의존성 노출을 0 으로, RuntimeClasspath 는 4개만 노출해 버전 충돌(NoSuchMethodException/ClassNotFoundException)을 차단했다.
- 테스트: fixture-monkey 와 jqwik EdgeCasesMode.FIRST 로 null·빈 문자열·공백·경계값을 포함해 1,000회 랜덤 암복호를 돌리고, cowcow-tests 로 Spring Boot 버전별 통합 테스트를 했다.
- 성능: M1 MacBook Pro 16GB(서버 모드, G1 GC)에서 대량 평문 반복 시 암호화 약 5.58초·복호화 약 5.36초, 11분 이상 지속 실행에도 CPU 가 안정적이고 힙 메모리가 정상 회수됐다.
왜 읽나JPA 환경에서 애플리케이션 레벨 암호화 모듈을 직접 설계하는 백엔드/보안 엔지니어에게 방식 선택·봉투 암호화·저장 최적화 레퍼런스.