XAES-256-GCM: 확장 논스 AEAD
(words.filippo.io)- XAES-256-GCM은 고수준 암호화 API에서 논스 관리를 덜 위험하게 만들기 위해 256비트 키와 192비트 논스를 쓰는 새 AEAD 명세임
- 큰 논스는 메시지마다 운영체제 CSPRNG에서 새 값을 자동 생성하는 방식을 가능하게 하며, 2⁸⁰개 메시지에서 충돌 위험 2⁻³² 수준을 목표로 함
- 내부적으로는 입력 키와 큰 논스에서 파생 키와 96비트 논스를 만든 뒤, 표준 AES-256-GCM을 그대로 사용하는 확장 논스 구성임
- Go 참조 구현은
crypto/cipher와crypto/aes만으로 100줄 미만에 들어가며, 메시지당 AES-256 호출 3회 중 일부는 사전 계산 가능함 - FIPS 140 준수와 라이브러리 호환성을 중시해, XChaCha20Poly1305·AES-GCM-SIV와 함께 논스 없는 AEAD API 후보로 쓰일 수 있음
고수준 API를 위한 큰 논스 AEAD
- XAES-256-GCM은 추가 인증 데이터가 있는 인증 암호화(AEAD) 알고리듬으로, 256비트 키와 192비트 논스를 사용함
- 설계 목표는 세 가지로 압축됨
- 사실상 무제한에 가까운 메시지 수에서도 무작위 생성이 안전한 큰 논스 지원
- 완전하고 직접적인 FIPS 140 준수
- 일반적인 암호화 라이브러리 위에서 쉬운 구현
- 큰 논스 덕분에 사용자가 birthday bound 계산을 직접 하지 않아도, 메시지마다 운영체제 CSPRNG에서 새 논스를 읽는 API를 만들 수 있음
- 준수성과 호환성을 우선해, 다른 큰 논스 AEAD를 쓰기 어려운 환경에서도 AEAD가 필요한 곳에 적용 가능함
AES-256-GCM을 그대로 활용하는 확장 논스 구성
- XAES-256-GCM은 XChaCha20Poly1305처럼 기존 AEAD 위에 올리는 확장 논스 구성임
- 입력 키와 192비트 논스에서 내부 AES-256-GCM용 파생 키와 파생 논스를 계산함
- 입력 키와 논스는 각각
K,N - 파생 AES-256-GCM 키와 논스는
Kₓ,Nₓ
- 입력 키와 논스는 각각
AES-256ₖ호출과 논스 앞부분으로Kₓ를 만들고, 입력 논스의 뒤 96비트를Nₓ로 사용함- 메시지당
AES-256ₖ호출은 3회 필요함- 이 중 하나는 주어진 키에 대해 사전 계산 가능함
- 나머지 두 호출은 같은 키 스케줄을 재사용할 수 있음
구현은 짧고 표준 구성 요소로 기술 가능
- Go 참조 구현은 사전 계산 최적화와 대부분의 보일러플레이트를 포함해 100줄 미만임
- Go 구현은 표준 라이브러리의
crypto/cipher와crypto/aes만 사용함 - XAES-256-GCM은 표준 NIST SP 800-108r1 KDF와 표준 NIST AES-256-GCM AEAD로도 기술 가능함
- KDF는 counter-based KDF
- PRF는 CMAC-AES256
- 입력 키는
Kin - 라벨은 ASCII 문자
X, 즉0x58 - 컨텍스트는 입력 논스의 첫 96비트
- 카운터 크기는 16비트
- 선택적
L필드는 생략 - 출력은 256비트 파생 키
- 파생 키와 입력 논스의 마지막 96비트가 AES-256-GCM에 들어감
- 매개변수 선택 덕분에 KDF와 CMAC 추상화를 걷어내면, 단순히 카운터에 AES-256을 호출하는 방식보다 약간만 느리고 복잡함
- 같은 매개변수는 고수준 OpenSSL API에서도 지원됨
서드파티 구현과 테스트 벡터
- 2024-06-29 편집 기준으로 서드파티 구현이 추가됨
- Web Cryptography API 구현은 256비트 AES-CBC
CryptoKey를 사용함 - 명세에는 두 주요 코드 경로에 대한 테스트 벡터가 포함됨
MSB₁(L) = 0MSB₁(L) = 1
- 10,000회 또는 1,000,000회 무작위 반복을 압축한 누적 테스트 벡터도 제공됨
/11 포기와 대안
- 이전 구상에는
XAES-256-GCM/11이라는 이름이 있었지만, 최종 명세에서는/11을 포기함 /11은 성능 최적화였으며, AES-GCM을 쓰는 이유 중 하나가 FIPS 140 준수이기 때문에 라운드 수를 바꾸면 준수성이 사라짐- FIPS 140 준수가 목표가 아니라면 대안은 여러 가지임
- AES-GCM-SIV
- AES 코어 기반의 현대적 AEAD 구성
- 명세의 Alternatives 섹션은 각 대안을 XAES-256-GCM과 비교함
Go와 논스 없는 AEAD API에서의 위치
- XAES-256-GCM은 안전하고, 지루하며, 준수 가능하고, 상호운용 가능한 AEAD를 목표로 함
- 주요 사용처는 Go에 추가하고 싶은 종류의 고수준 API임
- XAES-256-GCM은 XChaCha20Poly1305와 AES-GCM-SIV를 보완하며, 가상의 논스 없는 AEAD API 구현 후보로 설계됨
- Go 표준 라이브러리에 Go 전용 구성을 추가하는 것을 선호하지 않기 때문에, 다른 암호화 라이브러리 유지관리자의 의견이 필요함
댓글과 토론
Hacker News 의견들
-
설계가 아주 영리함: CMAC 기반이라 하위 수준 기본 요소가 없어도 AES-CBC로 키를 파생할 수 있음
AES-CBC 관점에서는L = AES-CBC-256ₖ(iv = 0¹²⁸, plaintext = 0¹²⁸)[:16]로 시작해K1을 만들고,M1,M2를 암호화해Kₓ를 얻은 뒤Nₓ = N[12:]를 쓰는 식으로 볼 수 있음
AES-CBC-256은 암호문의 첫 128비트 블록만 반환하고 패딩 블록은 버린다고 보면 되며, 패딩을 끌 수 없더라도 하위 수준 구현보다 같은 키로 AES 호출이 3번 더 드는 정도라 나쁘지 않음
이 특성을 이용한 WebCrypto API 기반 JS 구현은 https://github.com/dchest/xaes에 있으며, AES-CBC용CryptoKey를 그대로 받아extractable=false로 IndexedDB에 저장하는 기능 같은CryptoKey특성도 지원함- 이 분야에서는 표준 표기처럼 보이지만, 솔직히 암호학 표기법은 싫음
저 의사코드에서 숫자의 절반은 바이트 수를, 나머지 절반은 비트 수를 세는 것 같고, 알고리즘을 이미 모르면 어느 쪽인지 거의 알 수 없음
예를 들어N[:12]는 12바이트처럼 보이지만0¹²⁸은 16바이트이고,X는 실제 문자'X', 즉 비트열01011000인데L은 비트열01001100이 아니라 변수임
수학자들은 컴퓨터과학 쪽 사람들만큼 모호하지 않은 표기를 좋아하지 않는 게 분명해 보임 0¹²⁰10000111같은 으스스해 보이는 상수는, CMAC이 쓰는 하위 블록 암호의 블록 크기b에 대해 차수b인 기약다항식 중 0이 아닌 항 개수가 최소인 것들 가운데 사전순으로 첫 번째인 다항식의 계수를 나타낸 비트열임
숨겨진 의도 같은 건 없음- 암호 쪽 전문가는 아니지만, 요약하면 표준 AES-GCM AEAD는 서로 다른 메시지에 같은 논스를 두 번 쓰면 치명적으로 깨지고[1], 논스 크기도 많은 경우 무작위 논스를 안전하게 쓰기엔 작다는 얘기로 이해됨
이 작업은 AES-GCM 호출마다 논스뿐 아니라 키도 바꾸는 방식으로 그 문제를 쉽게 피하게 해줌
또한 AES-GCM이 있으면 대체로 사용 가능한 “평범한” AES만 쓰고, 아직 약점이 있을지 모르는 새로운 복잡한 구성은 피함
메시지당 오버헤드는 “평범한” AES로 암복호화해야 하는 작은 버퍼 2개와 더 긴 192비트 논스 정도임
[1]: https://frereit.de/aes_gcm/
- 이 분야에서는 표준 표기처럼 보이지만, 솔직히 암호학 표기법은 싫음
-
무작위 논스를 쓸 때 약 2^32개 메시지마다 키를 교체해야 하는 순정 AES-GCM의 함정을 없애는 것처럼 보임
AES-GCM에서 논스 충돌은 치명적이고, 적어도 공격자가 임의 메시지에 서명할 수 있게 만듦
무작위 논스를 꼭 써야 하는 건 아니지만 보통 권장되며, 카운터 기반 키 파생 함수와 순정 GCM이라는 두 기본 요소를 써서 FIPS 준수로 만드는 점이 꽤 영리함- 정확히는 이 방식이 애초에 무작위 논스를 안전하게 만들어줌
표준 AES-GCM에서는 96비트가 무작위 충돌을 피하기에 충분하지 않으므로 결정적 논스 생성을 써야 함
또한 논스를 어떻게 만들었든 카운터가 롤오버되어 다음 블록이 첫 블록과 같은 논스+카운터를 쓰게 되므로, 2^32 블록 이후에는 논스나 키를 바꿔야 함
- 정확히는 이 방식이 애초에 무작위 논스를 안전하게 만들어줌
-
정말 굉장함. 몇 년 전 마지막으로 암호화 파일시스템을 만들 때 이게 있었으면 좋았겠음
대규모 파일시스템 배포에서는 논스 충돌이 큰 걱정거리임
2^32는 커 보이지만, PB급 배열에서 초당 100k IOPS로 쓰기를 하면서 의사난수 생성기의 무작위성에 기대면 충돌 가능성은 거의 보장된 수준임- CAESAR 경쟁[1]은 2019년에 끝났고, 논스 공간이 충분한 여러 AEAD가 결과로 나왔음
[1] https://en.m.wikipedia.org/wiki/CAESAR_Competition - 그런데 왜 논스 충돌이 문제인지 궁금함
두 블록이 같은 암호화 키를 공유한다는 뜻일 뿐 아닌가?
어느 블록의 평문도 모른다면 그게 시스템 보안을 어떻게 약화시키는지 모르겠음
- CAESAR 경쟁[1]은 2019년에 끝났고, 논스 공간이 충분한 여러 AEAD가 결과로 나왔음
-
보관용 파일 암호화 용도로 FIPS 준수 age 변형에 이게 쓰이면 좋겠음
은행권 감사에서 age가 ChaCha를 쓴다는 이유로 이 용도에 퇴짜를 놨고, age의 X25519 공개키 부분은 괜찮다고 봤음. X25519는 비교적 최근 NIST 승인을 받은 것으로 알고 있었음
Go 경험은 없지만 age 명세를 보면 바로 끼워 넣을 수 있을 것 같고, 시간이 나면 시도해볼 수도 있겠음
이름은 “compliant actually good encryption”의 의미로 “cage”라고 부르면 될 듯함
1: https://github.com/FiloSottile/age- “compliant actually good encryption”은 어쩌면 모순어법일 수도 있음
- 확인해보니 Ed25519는 승인된 것 같지만(FIPS 186-5), X25519는 아직 아닌 것으로 보임
- 참고로 Go 표준 라이브러리에는 아직 XAES 구현이 없고, C2SP의 참조 구현만 있음
- bge, bureaucratic good encryption
-
비암호학자로서 궁금한데, 왜 192비트 논스를 쓰고 256비트를 쓰지 않는지 모르겠음
실용적인 애플리케이션에서 그 추가 비트들이 비용으로 여겨질 것 같지는 않음- 256비트를 넣을 공간이 없음. 192비트는 하위 논스 공간에서 오는 96비트와, 필요한 접두사와 함께 128비트 CMAC 블록에 들어가는 96비트로 구성됨
CMAC 입력을 더 길게 만들 수도 있지만, 그러면 AES-256 블록 함수를 더 여러 번 실행해야 하고 CMAC 키 파생 함수에서 성가신 키 제어 문제도 마주치게 됨
XChaCha20Poly1305가 192비트 논스를 쓰는 이유와도 비슷하며, 다른 주요 확장 논스 AEAD와 일관된다는 점도 약한 장점임
- 256비트를 넣을 공간이 없음. 192비트는 하위 논스 공간에서 오는 96비트와, 필요한 접두사와 함께 128비트 CMAC 블록에 들어가는 96비트로 구성됨
-
“2⁸⁰개 메시지에서 충돌 위험 2⁻³²”라고 했는데, AES 블록 크기가 128비트뿐이라는 사실 때문에 그 전에 문제가 생기지는 않나?
- 블록에 대한 생일 한계(https://sweet32.info)를 말하는 거라면, 그건 단일 키로 암호화된 블록 수에 대한 제한임
XAES는 메시지마다 큰 키를 파생하므로 흔히 생일 한계보다 나은 보장이라고 부르는 것을 달성함 - 아님
왜 문제가 된다고 생각하는지 더 자세한 맥락이 없으면 이보다 더 자세히 답하기는 어려움
- 블록에 대한 생일 한계(https://sweet32.info)를 말하는 거라면, 그건 단일 키로 암호화된 블록 수에 대한 제한임
-
“안전하고, 지루하고, 준수 가능하고, 상호운용 가능하다”
내가 가장 좋아하는 종류의 기술임 -
좋음. 실제로 NIST 기반 구성이 있다는 건 반가움
다만 NIST 키 파생 함수의 장점인 레이블과 컨텍스트 같은 기능을 여러 개 포기하게 되는 점은 아쉬움
AES 호출 수를 최소화하려고 희생한 건 이해하지만, 특히 수백 바이트보다 긴 메시지라면 AES 호출 몇 번을 아끼는 것보다 강한 암호학적 분리를 더 우선했을 것 같음
마지막으로, 96비트보다 긴 무작위 GCM 논스는 확실히 오해를 많이 받고 있으며 96비트 논스보다 더 나은 보장을 제공함[1]
물론 메시지마다 새 키를 파생할 수 있다면 그쪽이 확실히 더 낫음
[1] https://neilmadden.blog/2024/05/23/galois-counter-mode-and-r...