AES 모드를 고를 때 무엇을 보는가
ECB는 패턴을 남기고 CTR은 nonce를 재사용하면 평문이 드러납니다. 모드별 차이와 GCM을 기본으로 두는 이유를 정리했습니다.
암호화 라이브러리를 쓰면 알고리즘만이 아니라 모드를 함께 지정해야 합니다. aes-256-cbc, aes-256-gcm 같은 문자열의 뒷부분입니다. 예제에 있는 값을 그대로 복사해 쓰기 쉬운데, 이 선택에 따라 결과가 크게 달라집니다.
블록 암호는 16바이트씩 처리한다
AES는 한 번에 16바이트를 암호화합니다. 그보다 긴 데이터는 나눠서 처리해야 하고, 나눈 블록들을 어떻게 이어 붙일지가 모드입니다. 키 길이(128, 192, 256비트)와는 별개의 선택입니다.
ECB는 패턴을 그대로 남긴다
가장 단순한 방식입니다. 블록마다 따로 암호화합니다. 그래서 같은 평문 블록은 항상 같은 암호문 블록이 됩니다. 같은 16바이트를 네 번 반복한 값을 ECB로 암호화하면 결과가 이렇습니다.
12dd273cfb6dd09328f94d2d0588e7e7
12dd273cfb6dd09328f94d2d0588e7e7
12dd273cfb6dd09328f94d2d0588e7e7
12dd273cfb6dd09328f94d2d0588e7e7
암호문만 보고도 원본에 반복이 있다는 사실을 알 수 있습니다. 이미지를 ECB로 암호화하면 윤곽이 그대로 보이는 예시가 유명한 것도 이 때문입니다. 데이터베이스 컬럼을 ECB로 암호화하면 같은 값을 가진 행을 골라낼 수 있습니다. 쓸 자리가 없습니다.
같은 입력을 CBC로 암호화하면 블록이 전부 달라집니다.
c92f5187a1aa881c4085c03f44a09ff5
50aacaf4f0fc58618581940eb4959ad8
fd4acdd6318b0cb759d723cf9ba2435c
1deb13bbc52e752990bc65813b064fb4
CBC는 IV가 조건이다
CBC는 앞 블록의 암호문을 다음 블록에 섞습니다. 첫 블록에는 섞을 것이 없으므로 초기화 벡터(IV)를 넣습니다.
- IV는 매번 새로 만든 예측 불가능한 값이어야 합니다. 고정하면 같은 평문이 같은 암호문이 되어 ECB와 비슷한 문제가 생깁니다
- IV는 비밀이 아닙니다. 암호문 앞에 붙여 보내는 것이 일반적입니다
- 16바이트 단위를 맞추기 위해 패딩이 붙습니다. 64바이트 평문이 80바이트가 됩니다
CBC의 더 큰 문제는 변조를 감지하지 못한다는 점입니다. 암호문의 비트를 바꾸면 복호화 결과가 달라지는데, 그것이 공격인지 알 방법이 없습니다. 패딩이 맞는지에 대한 응답 차이를 이용해 평문을 복원하는 패딩 오라클 공격이 여기서 나옵니다.
CTR은 nonce를 재사용하면 끝난다
CTR은 카운터를 암호화해 키스트림을 만들고 평문과 XOR합니다. 블록 암호를 스트림처럼 쓰는 방식이라 패딩이 필요 없고 길이가 그대로 유지됩니다.
대신 같은 키와 같은 nonce로 두 번 암호화하면 키스트림이 같아집니다. 그러면 두 암호문을 XOR한 결과가 두 평문을 XOR한 것과 같아지고, 한쪽 평문을 알면 다른 쪽이 그대로 드러납니다.
평문1 = "attack at dawn!!"
평문2 = "retreat at noon!"
암호문1 XOR 암호문2 XOR 평문1 → "retreat at noon!"
키를 몰라도 복원됩니다. nonce 관리가 곧 안전성입니다.
GCM은 인증을 포함한다
GCM은 CTR로 암호화하고 인증 태그를 함께 만듭니다. 복호화할 때 태그를 검증하므로 암호문이 변조되면 복호화가 실패합니다. 이렇게 암호화와 인증을 함께 처리하는 방식을 AEAD라고 합니다.
- 태그는 16바이트입니다. 64바이트 평문이면 암호문 64바이트 + 태그 16바이트가 됩니다
- nonce는 12바이트를 씁니다. CTR과 마찬가지로 재사용하면 안 되고, GCM에서는 인증 키까지 노출될 수 있어 피해가 더 큽니다
- 암호화하지 않고 인증만 할 데이터(헤더, 메타데이터)를 AAD로 함께 넣을 수 있습니다
무엇을 고르나
| 모드 | 인증 | 패딩 | 쓸 자리 |
|---|---|---|---|
| ECB | 없음 | 필요 | 없음 |
| CBC | 없음 | 필요 | 기존 시스템 호환이 필요할 때만 |
| CTR | 없음 | 불필요 | 인증을 따로 붙일 때 |
| GCM | 있음 | 불필요 | 기본 선택 |
특별한 이유가 없으면 GCM입니다. 라이브러리 선택지에 ChaCha20-Poly1305가 있다면 그것도 같은 계열의 AEAD이고, 하드웨어 가속이 없는 환경에서 더 빠릅니다.
CBC를 써야 한다면 암호문에 HMAC을 따로 붙이고, 복호화 전에 HMAC부터 검증합니다. 순서가 중요합니다.
알고리즘 자체가 낡은 것들
- DES — 키가 56비트입니다. 전수 조사로 뚫립니다
- 3DES — DES를 세 번 적용한 것으로, 블록이 64비트라 데이터가 쌓이면 충돌 문제가 생깁니다. 표준에서 퇴출됐습니다
- RC4 — 키스트림에 편향이 있어 TLS에서 금지됐습니다
기존 시스템의 데이터를 읽어야 하는 경우가 아니면 선택지에 두지 않습니다.
실제 문제는 키 관리
모드를 맞게 골라도 키가 소스 코드나 저장소에 있으면 소용없습니다.
- 키는 환경 변수나 시크릿 관리 서비스에서 읽습니다
- 비밀번호에서 키를 만들어야 한다면 그대로 쓰지 말고 PBKDF2나 Argon2로 유도합니다
- 키 교체 경로를 미리 정해 둡니다. 암호문에 키 버전을 함께 저장해 두면 교체할 때 나눠 처리할 수 있습니다
tools.onuel.dev의 AES 도구에서 같은 입력을 모드만 바꿔 암호화하면 ECB의 반복 패턴이 바로 보입니다. DES와 3DES도 있어 기존 데이터를 확인해야 할 때 쓸 수 있습니다. 계산은 브라우저 안에서 이루어집니다.
- #AES
- #암호화
- #보안