암호 기술 · Cryptography
인터넷을 떠받치는 자물쇠, AES-GCM
지금 이 문서가 화면에 도착하기까지, 보이지 않는 곳에서 같은 절차가 수십 번 되풀이되었다. 주고받는 모든 조각마다 자물쇠가 채워지고, 그 자물쇠에는 누군가 손댄 흔적이 남는 봉인까지 함께 붙는다. 오늘날 인터넷 통신의 기밀성과 무결성을 동시에 책임지는 그 장치의 이름이 AES-GCM이다.
웹사이트에 접속하고, 메시지를 보내고, 영상을 받아 보는 동안 브라우저는 끊임없이 데이터를 암호화한다. 그런데 "암호화한다"는 말은 생각보다 까다로운 요구를 담고 있다. 내용을 읽지 못하게 가리는 것만으로는 부족하다. 가려진 내용을 누군가 몰래 바꿔치기하지 못하게 막는 일까지 해내야 비로소 안전한 통신이라 부를 수 있다. AES-GCM은 이 두 가지를 한 번의 연산 흐름 안에서 처리하도록 설계된 방식이다. 그 구조를 처음부터 차근차근 따라가 본다.
1.AES는 128비트씩만 다룬다
블록 암호와 운용 모드
AES(Advanced Encryption Standard, 고급 암호화 표준)는 오늘날 가장 널리 쓰이는 블록 암호다. 블록 암호란 정해진 크기의 데이터 덩어리 하나를 비밀 키로 뒤섞어 알아볼 수 없는 같은 크기의 덩어리로 바꾸는 함수다. AES가 한 번에 처리하는 덩어리의 크기는 128비트, 즉 16바이트로 고정되어 있다. 키 길이는 128·192·256비트 중에서 고른다.
문제는 우리가 보내려는 데이터가 거의 언제나 16바이트보다 길다는 점이다. 웹페이지 하나, 이미지 한 장, 메시지 한 통은 수천에서 수백만 비트에 이른다. 그래서 긴 데이터를 128비트 블록 여러 개로 나누어 AES를 반복 적용하는 규칙이 필요하다. 이 규칙을 운용 모드(mode of operation)라 부른다.
운용 모드를 어떻게 짜느냐에 따라 안전성이 크게 갈린다. 가장 단순한 방법은 블록마다 똑같이 AES를 적용해 평문 블록을 그대로 암호문 블록으로 바꾸는 것인데, 이를 ECB(Electronic Codebook, 전자 부호표) 모드라 한다. ECB는 치명적인 약점을 안고 있다. 같은 평문 블록은 언제나 같은 암호문 블록이 되므로, 원본의 반복 패턴이 암호문에 그대로 드러난다. 그림 파일을 ECB로 암호화하면 윤곽이 비쳐 보이는 사고가 유명한 예다. 좋은 운용 모드는 같은 내용이라도 매번 다른 암호문이 나오도록 무작위성을 주입해야 한다.
2.블록 암호를 흐름 암호로 — 카운터 모드
CTR 모드의 원리
AES-GCM이 기밀성을 처리하는 토대는 CTR(Counter Mode, 카운터 모드)이다. CTR의 발상은 영리하다. 평문을 직접 암호화하지 않는다. 대신, 매번 달라지는 숫자를 AES로 암호화해 무작위처럼 보이는 비트열, 즉 키스트림을 만들고, 이 키스트림을 평문과 겹쳐 섞는다.
매번 달라지게 만드는 핵심 재료가 논스(nonce, number used once — 한 번만 쓰는 수)다. 암호화를 시작할 때 무작위 논스 하나를 정한 뒤, 거기에 1씩 증가하는 카운터를 붙여 첫 번째 블록은 (논스+0), 두 번째는 (논스+1), 세 번째는 (논스+2)… 이렇게 입력을 만든다. 각 입력을 AES로 암호화하면 블록마다 전혀 다른 키스트림 조각이 쏟아진다. AES는 입력이 1만 달라져도 출력이 완전히 뒤바뀌므로, 이 키스트림은 사실상 난수와 구별되지 않는다.
키스트림이 준비되면 평문과 합치는 연산은 가장 단순한 XOR(배타적 논리합)이다. 키와 논스가 비밀이거나 예측 불가능하면 키스트림도 비밀이 되고, 따라서 XOR로 가린 암호문은 키스트림을 모르는 사람에게는 의미 없는 잡음으로 보인다. 게다가 각 블록의 키스트림이 서로 독립적이므로 여러 블록을 동시에 병렬로 계산할 수 있어 속도도 빠르다.
CTR 모드는 일회용 난수 테이프로 메시지를 덮는 일과 같다. 발신자와 수신자는 같은 비밀 키를 공유하므로, 같은 논스만 정해 주면 양쪽이 똑같은 난수 테이프를 만들어 낼 수 있다. 발신자는 평문 위에 테이프를 겹쳐 가리고, 수신자는 같은 테이프를 다시 겹쳐 벗겨 낸다. 단, 한 번 쓴 (키, 논스) 짝을 다시 쓰면 같은 테이프가 재사용되는데, 이것이 나중에 살펴볼 가장 위험한 함정이 된다.
3.읽지 못해도 바꿀 수는 있다
스트림 암호의 치명적 빈틈 — 변조 가능성
CTR 모드는 내용을 잘 가린다. 그러나 가린다는 것과 지킨다는 것은 다른 문제다. CTR을 비롯한 모든 흐름 암호 방식에는 변조 가능성(malleability)이라는 빈틈이 있다. 암호문을 읽어 낼 수는 없더라도, 암호문을 의도대로 바꿔치기할 수는 있다는 뜻이다.
이유는 XOR의 성질에 있다. 복호화는 암호화와 똑같은 키스트림을 만들어 암호문에 다시 XOR하는 과정이다. 그런데 공격자가 전송 중인 암호문의 특정 비트들을 뒤집으면, 복호화 뒤 평문에서 정확히 같은 위치의 비트들이 함께 뒤집힌다. 키스트림이 무엇인지 전혀 몰라도 그렇다. 공격자가 암호문에 더한 변화가 그대로 평문에 전달되기 때문이다.
이 빈틈은 추상적인 위험이 아니다. 가령 "계좌 A로 100만 원 송금"이라는 메시지가 오가는 중이라면, 공격자는 그 내용을 읽지 못하더라도 계좌 번호에 해당하는 비트들을 자신의 계좌 번호로 밀어 넣을 수 있다. 송금 사실은 그대로지만 돈이 가는 곳만 바뀐다. 더 곤란한 점은, 이런 변조가 일어났는지 받는 쪽이 알아채기 어렵다는 데 있다.
밀봉된 봉투 안의 글씨는 보이지 않는다. 그런데 어떤 봉투는, 밖에서 특정 자리를 정확히 눌러 안의 글자 획을 밀어 바꿀 수 있다고 해 보자. 내용을 읽지는 못해도 "A"를 "B"로 슬쩍 고쳐 놓을 수는 있는 셈이다. CTR 모드의 암호문이 꼭 이런 봉투다. 필요한 것은 봉투를 더 두껍게 만드는 일이 아니라, 누가 손대면 깨지는 봉인을 함께 붙이는 일이다.
4.봉인을 더하다 — 인증 태그
기밀성에 무결성을 결합하는 인증 암호화
해법은 암호문 곁에 인증 태그(authentication tag)라는 짧은 검증값을 함께 보내는 것이다. 태그는 암호문 전체와 그 밖의 보호 대상을 요약한 값으로, 그중 단 한 비트라도 바뀌면 수신자가 다시 계산한 태그와 어긋나도록 설계된다. 받는 쪽은 복호화에 앞서 "내가 받은 태그"와 "내가 직접 계산한 태그"가 일치하는지부터 확인한다. 어긋나면 메시지를 통째로 버린다. 실제 암호 라이브러리는 이때 예외를 던져 작업을 중단시킨다.
이처럼 기밀성(읽지 못하게)과 무결성(바꾸지 못하게)을 한 묶음으로 제공하는 방식을 AE(Authenticated Encryption, 인증 암호화)라 한다. AES-GCM의 GCM은 Galois/Counter Mode(갈루아/카운터 모드)의 약자로, 바로 이 태그를 만들어 붙이는 절차의 이름이다. 즉 "AES를 GCM 방식으로 운용한다"는 말은, AES와 CTR로 암호문을 만들면서 동시에 갈루아 체(field) 위의 계산으로 그 암호문을 요약한 태그까지 함께 산출한다는 뜻이다.
인증 태그는 깨지는 밀랍 봉인과 같다. 편지를 봉투에 넣어 가리는 것이 암호화라면, 봉투를 가로질러 찍은 밀랍 도장은 누군가 봉투를 열거나 손댔는지 알려 준다. 봉인이 멀쩡해야만 내용을 신뢰할 수 있고, 금이 가 있으면 읽지 않고 버린다. 다만 평범한 도장은 흉내 낼 수 있다는 게 문제다. AES-GCM은 발신자만 찍을 수 있는, 흉내 낼 수 없는 도장을 어떻게 만드는지가 핵심이다.
5.흉내 낼 수 없는 도장 — 비밀 보조키 H와 GHASH
갈루아 체 위의 곱셈으로 만드는 인증값
태그가 안전하려면, 키를 모르는 공격자가 변조한 암호문에 맞는 태그를 새로 계산해 낼 수 없어야 한다. AES-GCM은 이를 위해 먼저 해시 보조키 H를 만든다. 방법은 단순하다. 128비트가 모두 0인 블록을 비밀 키로 AES 암호화한 결과가 곧 H다. 0을 넣어도 AES는 무작위처럼 보이는 출력을 내놓으므로, H는 키에서 결정론적으로 만들어지지만 키를 모르면 알 수 없는 비밀값이 된다.
이제 태그를 만드는 함수가 GHASH다. GHASH는 누산기(처음 값은 0)에 보호 대상 블록을 하나씩 넣으며 다음을 되풀이한다. 새 블록을 누산기에 XOR한 뒤, 그 결과에 보조키 H를 곱한다. 암호문 블록 C₁, C₂, C₃…를 차례로 이렇게 처리하고, 마지막에 데이터 길이 정보를 담은 블록까지 한 번 더 같은 방식으로 섞는다. 길이 블록을 넣는 이유는, 블록을 슬쩍 잘라 내거나 덧붙이는 변조까지 막기 위해서다.
여기서 "곱한다"는 연산은 우리가 아는 정수 곱셈이 아니라, 갈루아 체 GF(2¹²⁸) 위의 곱셈이다. 128비트 값을 다항식으로 취급해 서로 곱한 뒤, 정해진 기약 다항식(x¹²⁸ + x⁷ + x² + x + 1)으로 나눈 나머지를 취한다. 개념은 우리가 익숙한 "나머지 연산"과 비슷하되, 비트 수준에서 자리 올림 없이 이루어진다는 점이 다르다. AES 내부에서도 8비트짜리 갈루아 체 연산이 쓰이는데, GHASH는 그 128비트 확장판인 셈이다. 중요한 것은, 이 곱셈을 거치면 결과가 무작위처럼 흩어져 공격자가 손쉽게 역산할 수 없다는 사실이다.
마지막 한 단계가 남는다. GHASH로 얻은 누산값에, 첫 카운터 블록을 AES 암호화한 마스크값을 한 번 더 XOR한다. 이 마스킹이 비밀 H가 태그에서 직접 드러나는 것을 막아 준다. 그렇게 나온 128비트가 최종 인증 태그 T다.
H는 발신자만 가진 비밀 잉크라고 생각하면 된다. 도장을 찍을 때마다 메시지의 모든 부분을 이 잉크로 한 겹씩 덧칠해(곱셈) 누적하므로, 잉크의 정체를 모르는 사람은 똑같은 도장을 다시 만들 수 없다. 잉크 H는 비밀 키로 "0"을 암호화해 얻은 값이라 키 없이는 손에 넣을 수 없고, 그래서 위조가 사실상 불가능해진다.
6.두 흐름이 맞물리다 — AES-GCM의 전체 그림
CTR과 GHASH의 결합
지금까지의 두 갈래를 하나로 합치면 AES-GCM의 전모가 드러난다. 위쪽 흐름은 기밀성을 맡는다. 논스와 카운터로 키스트림을 만들어 평문 P를 XOR해 암호문 C를 얻는다. 아래쪽 흐름은 무결성을 맡는다. 그 암호문 C를 누산기에 차례로 넣고 비밀 H를 곱해 나가다, 길이 블록과 마스크까지 섞어 인증 태그 T를 뽑아낸다. 암호화 한 번에 암호문과 태그가 함께 나온다.
복호화는 이 과정을 거꾸로 밟는다. 같은 키스트림으로 암호문을 XOR해 평문을 되살리는 동시에, 받은 암호문으로 GHASH를 다시 돌려 태그를 직접 계산한다. 그리고 사용자에게 평문을 돌려주기 전에 두 태그가 같은지 확인한다. 키가 틀려도, 암호문이 중간에 바뀌어도, 논스가 손상되어도 태그가 어긋나므로, 그런 메시지는 곧장 폐기된다.
7.암호화하지 않고 인증만 — 부가 데이터
AEAD와 연관 데이터
실제 통신에는 숨겨야 할 부분과 드러내야 할 부분이 섞여 있다. 인터넷 패킷을 예로 들면, 어디로 보낼지 적은 헤더는 중간 장비가 읽어야 하므로 암호화할 수 없다. 그러나 그 헤더를 누군가 위조하는 일은 막고 싶다. 데이터베이스 기록이라면 기본 키는 그대로 두되 개인정보 항목만 암호화하고 싶을 수도 있다.
AES-GCM은 이런 요구를 위해 연관 데이터(AAD, Additional Authenticated Data — 추가 인증 데이터)를 받아들인다. AAD는 암호화하지는 않지만 GHASH에는 함께 넣어 인증한다. 그래서 헤더 같은 공개 정보도 변조되면 태그가 어긋나 걸러진다. 이렇게 "암호화한 것"과 "암호화하지 않고 인증만 한 것"을 함께 보호하는 방식을 AEAD(Authenticated Encryption with Associated Data, 연관 데이터 인증 암호화)라 부른다. 발신자는 공개 논스, AAD, 암호문, 태그를 함께 보내고, 수신자는 그 묶음만으로 복호화와 검증을 모두 마칠 수 있다.
소포를 떠올리면 쉽다. 겉면의 주소는 누구나 읽어야 배달되므로 가릴 수 없지만(AAD), 주소가 도중에 바뀌면 곤란하다. 안에 든 물건은 아무도 못 보게 가린다(암호문). AEAD는 주소는 보이게 두되 손대면 표가 나게 하고, 물건은 숨기면서 동시에 봉인하는, 하나의 봉인 도장으로 둘 다 지키는 방식이다.
8.단 한 번의 실수가 모든 것을 무너뜨린다
논스 재사용과 '금지된 공격'
AES-GCM의 안전성에는 절대 어겨선 안 되는 전제가 하나 있다. 같은 키로는 같은 논스를 두 번 써서는 안 된다. 논스(nonce)는 이름 그대로 "한 번만 쓰는 수"이며, 이를 어기는 순간 방어가 두 겹으로 무너진다.
먼저 기밀성이 깨진다. 같은 (키, 논스)는 같은 키스트림을 낳으므로, 같은 논스로 암호화된 두 메시지의 암호문을 XOR하면 키스트림이 상쇄되고 두 평문의 XOR이 드러난다. 더 심각한 것은 무결성 쪽이다. 논스가 재사용되면 공격자는 다항식의 근을 찾는 표준 기법으로 비밀 보조키 H 자체를 복원할 수 있다. H를 손에 넣으면 그때부터는 원하는 대로 암호문을 변조하고, 길이를 바꾸고, 그에 맞는 태그까지 위조할 수 있다. 인증은 사실상 사라지고 평범한 CTR 모드로 전락한다. 이 공격은 2006년 표준화 과정에서 지적된 이래 '금지된 공격'(forbidden attack)이라 불리며, 실제 운영 환경에서 위조가 성공한 사례도 보고되었다.
변조보다 더 나쁜 단 하나는, 알아채지 못하는 변조다. 메시지가 바뀐 줄 알면 버리면 그만이지만, 모르면 그 결과를 그대로 받아들이게 된다. 논스 재사용은 바로 그 "알아채지 못하는 변조"의 문을 활짝 연다.
TLS(Transport Layer Security, 전송 계층 보안) 1.2에서는 논스 영역이 8바이트로 비교적 짧아, 무작위로 뽑으면 충돌 위험이 있었다. 그래서 안전한 구현은 반드시 카운터로 논스를 관리해야 했는데, 이를 잘못 다룬 일부 구현에서 실제 취약점이 드러났다. TLS 1.3은 이 위험을 줄이기 위해 레코드 순번에서 논스를 결정론적으로 끌어내도록 바꿨다.
9.대안과 현실 — 그리고 태그의 길이
ChaCha20-Poly1305, 그리고 어디에나 있다는 사실
AES-GCM에는 사실상 하나의 짝꿍이자 대안이 있다. ChaCha20-Poly1305다. ChaCha20은 흐름 암호이고 Poly1305는 메시지 인증 코드인데, 결합 방식의 큰 줄기는 GHASH와 같다. 다만 Poly1305는 (키, 논스) 짝마다 일회용 인증 키를 새로 쓰기 때문에, 논스가 재사용되더라도 해당 메시지 하나가 깨질 뿐 "영원히 위조 가능한" 장기 키가 통째로 새어 나가지는 않는다는 점에서 덜 취약하다. 또한 하드웨어 AES 가속이 없는 환경에서 일정 시간(constant-time) 구현이 쉬워 부채널 공격에 강하다. 그래서 모바일이나 저전력 기기에서 자주 선택된다.
TLS 1.3은 안전성이 검증된 AEAD 방식만 허용하는데, 사실상 이 둘 — AES-GCM과 ChaCha20-Poly1305 — 만 남아 있다. 한편 AES-GCM은 특허에 얽매이지 않도록 설계되었고, TLS뿐 아니라 IPsec, SSH, 그리고 이더넷 보안 표준인 MACsec(IEEE 802.1AE) 등 폭넓은 곳에 자리 잡았다. 최신 프로세서의 AES 가속 명령과 자리 올림 없는 곱셈 명령 덕분에 속도도 매우 빠르다.
태그의 길이는 어떨까. 기본은 128비트지만, 통신량을 아끼려 96비트 등으로 줄여 쓰기도 한다. 다만 너무 짧게 자르면 곤란하다. 극단적으로 태그가 1비트뿐이라면 공격자가 절반의 확률로 맞는 태그를 우연히 만들어 낼 수 있다. 일반적으로 태그를 t비트로 자르면 위조 성공 확률이 대략 블록 수에 비례해 커지고, 실패한 위조 시도조차 H에 관한 정보를 조금씩 흘린다. 인터넷 트래픽에서 몇 바이트의 여유는 큰 부담이 아니므로, 특별한 이유가 없다면 128비트를 그대로 쓰는 편이 안전하다.
마지막으로, 이 모든 절차가 얼마나 자주 일어나는지 짚고 싶다. 답은 보내는 거의 모든 메시지마다다. 웹페이지를 요청할 때, 이미지를 내려받을 때, 메시지 한 통을 보낼 때마다 발신 측은 태그를 계산해 붙이고 수신 측은 그것을 검증한다. 만약 이 봉인이 없었다면, 누군가 송금 계좌를, 비밀번호를, 거래 내역을 소리 없이 바꿔치기하는 일이 일상이 되었을 것이다. AES-GCM은 그런 세계와 우리 사이에 놓인, 평소엔 보이지 않는 자물쇠다.
정리하면 AES-GCM은 두 가지 약속을 한 번에 지킨다. 첫째, 키스트림으로 가려 내용을 읽지 못하게 한다. 둘째, 비밀 H로 빚은 태그로 손댄 흔적을 남겨 내용을 바꾸지 못하게 한다. 봉투와 봉인을 하나의 도장으로 동시에 처리한다는 이 단순한 발상이, 매 순간 오가는 수많은 메시지의 안전을 떠받치고 있다.