암호
비밀을 지키고, 바뀌지 않았음을 증명하고, 누가 보냈는지 확인하는 수학적 도구다.
목차 (10)
암호가 해 주는 네 가지 일
암호(cryptography)라고 하면 내용을 숨기는 일만 떠올리기 쉽지만, 실제로 쓰이는 암호 도구는 네 가지 질문에 답한다. 남이 읽을 수 있는가, 중간에 바뀌었는가, 누가 보냈는가, 보낸 사람이 나중에 아니라고 할 수 있는가. 질문마다 맞는 도구가 따로 있고, 하나로 나머지를 대신하지 못한다. 예를 들어 암호화만 하고 변조 검사를 빼면, 공격자가 내용을 읽지는 못해도 몰래 바꿀 수는 있다.
| 지키는 것 | 질문 | 도구 |
|---|---|---|
| 기밀성(confidentiality) | 남이 읽을 수 있는가 | 대칭 암호, 공개키 암호 |
| 무결성(integrity) | 중간에 바뀌었는가 | 해시(우연한 손상), MAC·인증 암호(일부러 바꾼 것) |
| 인증(authentication) | 누가 보냈는가 | MAC(키를 나눈 상대), 디지털 서명(공개키의 주인) |
| 부인 방지(non-repudiation) | 보낸 사람이 부정할 수 있는가 | 디지털 서명. 키를 나눠 가진 MAC으로는 안 된다 |
이 목표들이 보안 전체에서 어디에 놓이는지는 보안의 기초 노트의 CIA 절에 있다. 내용을 숨기는 것과 존재 자체를 숨기는 것도 다르다. 그림 속에 글을 감추는 스테가노그래피는 들키면 그대로 읽히므로, 이미지 스테가노그래피 도구도 먼저 암호화한 뒤 숨기라고 안내한다.
대칭 암호: 같은 키로 잠그고 연다
대칭 암호(symmetric encryption)는 잠그는 키와 여는 키가 같다. 오늘날의 표준은 AES(Advanced Encryption Standard)다. 미국 NIST가 공개 공모로 여러 후보를 몇 해 동안 공격해 본 끝에 벨기에 연구자들의 Rijndael을 골라 2001년 FIPS 197로 정했다. AES는 128비트(16바이트) 블록 하나를 키에 따라 여러 라운드 섞는 블록 암호(block cipher)이고, 키 길이는 128·192·256비트 중에서 고른다.
대칭 암호는 빠르다. 요즘 CPU는 AES 전용 명령을 갖고 있어 디스크 전체 암호화나 동영상 스트리밍도 거의 부담 없이 돌린다. 약점은 키 배달이다. 두 사람이 같은 키를 갖고 있어야 하는데, 처음 만나는 상대와 그 키를 어떻게 나눌지는 대칭 암호만으로 풀 수 없다. 이 문제를 아래의 공개키 암호와 키 교환이 푼다.
블록 암호 모드: ECB를 쓰면 안 되는 이유
AES는 16바이트 블록 하나만 다룬다. 그보다 긴 메시지를 어떻게 이어 붙여 처리할지는 운용 모드(mode of operation)가 정한다. NIST SP 800-38A가 고전적인 모드들을 정의한다.
| 모드 | 방식 | 성질 |
|---|---|---|
| ECB(Electronic Codebook) | 블록마다 따로 암호화한다 | 같은 평문 블록이 늘 같은 암호문이 된다. 무늬가 샌다 |
| CBC(Cipher Block Chaining) | 앞 블록의 암호문을 다음 평문에 섞은 뒤 암호화한다. 첫 블록은 초기화 벡터(IV)와 섞는다 | IV는 예측할 수 없어야 한다. 변조 검사가 없다 |
| CTR(Counter) | 카운터를 암호화해 만든 키 흐름을 평문에 XOR한다 | 병렬 처리가 쉽다. 같은 키로 카운터를 다시 쓰면 안 된다. 변조 검사가 없다 |
ECB의 문제는 그림 하나로 드러난다. 같은 블록은 같은 암호문이 되므로, 하나하나의 값은 숨겨져도 어디가 같고 어디가 다른지라는 구조가 그대로 남는다. 그림을 ECB로 암호화하면 색은 바뀌어도 윤곽이 보이는 까닭이다. CBC처럼 앞 블록과 섞는 모드는 같은 평문 블록도 자리마다 다른 암호문이 되어 무늬가 사라진다. 라이브러리가 모드를 따로 묻는다면 ECB는 고르지 않는다.
인증 암호(AEAD): 숨기기와 변조 검사를 한 번에
CBC와 CTR은 내용을 숨기기만 한다. 공격자가 암호문의 일부 비트를 뒤집으면 받는 쪽은 바뀐 평문을 그대로 받아들이고, 복호화 오류 메시지의 차이를 이용해 내용을 알아내는 공격(패딩 오라클)까지 알려져 있다. 그래서 지금은 암호화와 변조 검사를 묶은 인증 암호(AEAD, Authenticated Encryption with Associated Data)를 기본으로 쓴다.
가장 널리 쓰이는 것은 AES-GCM(NIST SP 800-38D)과, AES 전용 명령이 없는 기기에서도 빠른 ChaCha20-Poly1305(RFC 8439)다. 둘 다 키와 논스(nonce, 한 번만 쓰는 수)를 받아 암호문과 함께 짧은 인증 태그를 낸다. 받는 쪽은 태그가 맞을 때만 평문을 돌려주고, 한 비트라도 바뀌었으면 아무것도 돌려주지 않는다. 암호화하지 않고 보호만 할 추가 데이터(패킷 헤더처럼 중간 장비가 읽어야 하는 부분)도 태그 계산에 넣을 수 있다. TLS 1.3은 이런 AEAD만 허용한다.
지켜야 할 규칙은 하나다. 같은 키로 같은 논스를 두 번 쓰지 않는다. GCM에서 논스가 겹치면 두 평문의 관계가 드러나고, 태그를 위조할 수 있는 값까지 새어 나간다. 논스는 비밀일 필요는 없어서 암호문 옆에 그대로 붙여 보내며, 메시지마다 카운터를 올리거나 충분히 긴 무작위 값을 쓴다.
공개키 암호: RSA와 타원 곡선
공개키 암호(public-key, asymmetric cryptography)는 키를 짝으로 만든다. 공개키는 누구에게 나눠 줘도 되고, 개인키는 주인만 갖는다. 공개키로 잠근 것은 개인키로만 열리고, 개인키로 서명한 것은 공개키로 누구나 확인할 수 있다. 처음 만난 상대와도 미리 비밀을 나누지 않고 안전하게 이야기를 시작할 수 있다는 점이 대칭 암호와 다르다. 기본 개념은 데이터 노트의 암호와 해시 절에도 정리되어 있다.
공개키 암호의 바탕은 한쪽으로는 쉽고 반대쪽으로는 몹시 어려운 계산이다. RSA는 큰 소수 두 개를 곱하기는 쉽지만, 곱한 결과에서 두 소수를 다시 찾아내기(소인수분해)는 어렵다는 사실에 기댄다. 타원 곡선 암호(ECC, elliptic curve cryptography)는 곡선 위의 한 점을 정해진 규칙으로 여러 번 더하기는 쉽지만, 결과 점만 보고 몇 번 더했는지 알아내기(이산 로그)는 어렵다는 사실을 쓴다. 같은 안전도를 훨씬 짧은 키로 얻어서 요즘 새로 만드는 시스템은 대부분 타원 곡선을 쓴다.
| 방식 | 어려운 문제 | 대략 128비트 안전도의 키 | 예 |
|---|---|---|---|
| RSA | 소인수분해 | 3072비트 | 오래된 인증서, RSA-PSS 서명 |
| 타원 곡선 | 타원 곡선 이산 로그 | 256비트 | ECDSA·Ed25519 서명, X25519 키 교환 |
| 격자 기반(양자 내성) | 격자 문제 | 수백~수천 바이트 | ML-KEM 키 캡슐화 |
공개키 연산은 대칭 암호보다 훨씬 느리다. 그래서 실제로는 공개키 암호로 대칭 키를 정하거나 서명만 하고, 내용은 AES-GCM 같은 대칭 암호로 보낸다. 앞으로 큰 양자 컴퓨터가 생기면 RSA와 타원 곡선은 모두 깨질 수 있어서, NIST는 2024년 격자 기반의 FIPS 203(ML-KEM) 같은 양자 내성 표준을 내놓았고, 브라우저와 TLS는 기존 방식과 섞어 쓰는 하이브리드 키 교환으로 옮겨 가고 있다.
해시, MAC, HMAC
암호 해시 함수(cryptographic hash function)는 어떤 길이의 데이터든 고정 길이의 지문으로 바꾼다. SHA-256(FIPS 180-4)은 256비트를 낸다. 좋은 암호 해시는 세 가지가 어렵다. 지문만 보고 원래 데이터 찾기(역상), 주어진 데이터와 지문이 같은 다른 데이터 찾기(제2 역상), 지문이 같은 아무 두 데이터 찾기(충돌)다. MD5와 SHA-1은 충돌을 실제로 만들 수 있게 되어 보안 용도로는 쓰지 않는다. 해시 테이블이 쓰는 빠른 해시는 이런 성질이 필요 없는 다른 물건이다(알고리즘의 해시 노트). 파일이나 글의 SHA-256 값은 인코딩 변환 도구에서 직접 계산해 볼 수 있다.
해시만으로는 일부러 바꾼 것을 막지 못한다. 해시는 누구나 계산할 수 있으므로, 메시지를 바꾼 사람이 새 지문을 계산해 함께 바꿔 놓으면 그만이다. 다운로드 페이지의 해시 값이 의미 있는 것은 그 값을 파일과 다른 믿을 만한 경로(HTTPS로 받은 공식 페이지)로 받았을 때뿐이다.
일부러 바꾼 것까지 잡으려면 키를 넣는다. MAC(message authentication code)은 메시지와 비밀 키로 태그를 만들어, 같은 키를 가진 상대만 태그를 만들고 확인할 수 있게 한다. 가장 흔한 것은 해시 함수로 만든 HMAC(RFC 2104)이다. 키와 메시지를 그냥 이어 붙여 해시하는 방식은 SHA-256 같은 구조에서 공격자가 메시지 끝에 내용을 덧붙이고 그에 맞는 태그까지 계산해 낼 수 있어(길이 확장) 안전하지 않고, HMAC은 키에서 만든 값을 안쪽과 바깥쪽에 한 번씩, 두 겹으로 해시하는 구조로 이를 막는다. 태그를 비교할 때는 앞에서부터 다른 곳이 나오면 바로 멈추는 일반 비교 대신, 걸리는 시간이 늘 같은 비교를 쓴다. 비밀번호 저장에는 일반 해시도 HMAC도 아닌 일부러 느린 함수를 쓴다. 그 이야기는 인증과 인가 노트에 있다.
키 교환: 디피–헬먼
처음 만난 두 사람이 모두가 엿듣는 통로에서 공통 비밀 키를 정할 수 있을까. 1976년 Whitfield Diffie와 Martin Hellman이 보인 디피–헬먼 키 교환(Diffie–Hellman key exchange)이 그 방법이다. 흔한 비유는 물감이다. 두 사람은 공개된 색 하나에 각자 몰래 고른 색을 섞어 서로에게 보낸다. 받은 색에 자기 비밀 색을 한 번 더 섞으면 두 사람의 결과는 세 색을 모두 섞은 같은 색이 된다. 엿본 사람은 섞인 색 둘을 봤지만, 섞인 색에서 원래 색을 떼어 내기는 어려워 같은 색을 만들지 못한다.
수학에서는 섞기가 거듭제곱의 나머지 계산이다. 아래 그림은 p = 23처럼 작은 수로 보였지만, 실제로는 2048비트 이상의 수를 쓰거나 타원 곡선 위에서 같은 일을 한다. 오늘날 가장 흔한 것은 RFC 7748의 X25519다. 이렇게 얻은 공통 값은 그대로 쓰지 않고 HKDF 같은 키 유도 함수에 넣어 용도별 대칭 키로 뽑아낸다.
두 사람은 공개된 수 p = 23, g = 5(공개 색)에 먼저 합의한다. 이 값은 누가 봐도 괜찮다.
디피–헬먼에는 빈틈이 하나 있다. 상대가 정말 그 사람인지는 알려 주지 않는다. 중간의 누군가가 양쪽 모두와 따로 키 교환을 하면(중간자 공격, man-in-the-middle) 두 사람은 각자 공격자와 비밀을 나누게 된다. 그래서 실제 프로토콜은 교환한 값에 아래의 디지털 서명을 붙여 상대를 확인한다. 또 연결마다 새 비밀 수를 쓰고 끝나면 버리면, 나중에 서버의 장기 키가 새어도 지난 대화는 풀리지 않는다. 이 성질을 전방 비밀성(forward secrecy)이라 하고, TLS 1.3은 전방 비밀성이 없던 옛 키 교환 방식(정적 RSA)을 아예 없앴다. P2P 화상 통화가 쓰는 WebRTC의 DTLS-SRTP도 연결마다 이런 키 교환으로 키를 정한다.
디지털 서명과 인증서
디지털 서명(digital signature)은 공개키 암호를 거꾸로 쓴다. 보내는 사람은 문서의 해시에 개인키로 서명을 만들고, 받는 사람은 받은 문서의 해시와 보낸 사람의 공개키로 서명이 맞는지 확인한다. 맞으면 두 가지가 함께 증명된다. 이 개인키의 주인이 서명했고, 서명한 뒤로 문서가 한 비트도 바뀌지 않았다. MAC과 달리 검증하는 사람은 서명을 만들 수 없으므로, 서명한 사람이 나중에 아니라고 하기 어렵다(부인 방지). 표준 방식은 FIPS 186-5에 정리된 RSA 서명, ECDSA, EdDSA(Ed25519)다.
남은 질문은 이 공개키가 정말 그 사람 것인가다. 인증서(certificate)가 답한다. 인증서는 "이 공개키는 example.com의 것"이라는 내용에 믿을 만한 인증 기관(CA)이 서명한 문서다. 브라우저는 몇몇 루트 인증 기관의 공개키를 미리 갖고 있다가, 서버가 보낸 인증서의 서명을 따라 올라가 그 루트에 닿는지 확인한다. 이 신뢰의 사슬과 HTTPS에서의 쓰임은 네트워크 보안 노트에서 다룬다. 소프트웨어 업데이트와 패키지에 붙는 코드 서명도 같은 원리다.
난수: Math.random과 암호용 난수
키, 논스, 세션 토큰, 비밀번호 재설정 링크는 모두 무작위 값이고, 공격자가 그 값을 맞힐 수 있으면 나머지 암호가 아무리 튼튼해도 소용없다. 컴퓨터가 내는 난수는 대부분 정해진 계산식으로 만든 의사 난수(pseudo-random)다. 게임이나 시뮬레이션용 생성기는 빠르고 고르게 퍼지는 것만 목표로 해서, 출력 몇 개로 내부 상태를 짐작하면 다음 값을 미리 계산할 수 있다. MDN의 Math.random() 문서도 이 함수가 보안 용도의 난수를 주지 않는다고 분명히 적는다.
보안에 쓰는 값은 암호학적으로 안전한 난수 생성기(CSPRNG)에서 받는다. 운영체제가 하드웨어 난수와 각종 타이밍에서 모은 엔트로피를 씨앗으로 쓰고, 앞의 출력을 모두 봐도 다음 출력을 맞힐 수 없게 설계되어 있다. 브라우저에서는 crypto.getRandomValues()가 이 생성기를 쓰고, UUID가 필요하면 crypto.randomUUID()를 쓴다. 이 사이트의 UUID·랜덤 문자열 도구도 이 함수들로 값을 만든다.
// 화면 효과: 예측되어도 상관없다
const sparkle = Math.random();
// 세션 토큰·키: 암호용 난수를 쓴다
const bytes = crypto.getRandomValues(new Uint8Array(32));
const requestId = crypto.randomUUID();암호는 직접 만들지 않는다
보안 분야의 오랜 격언은 "암호를 직접 만들지 말라(don't roll your own crypto)"다. 자기가 깨지 못하는 암호는 누구나 만들 수 있다. 어려운 것은 남들이 몇 해 동안 공격해도 깨지지 않는 암호다. 그래서 쓸 만한 알고리즘은 공개되어 있고 공개 검증을 오래 거쳤다. 알고리즘을 숨겨서 지키려 하지 말고 키만 숨기라는 원칙(케르크호프스의 원칙)은 보안의 기초 노트에 있다.
실제 사고는 알고리즘보다 그 위에서 더 자주 난다. AES를 썼어도 ECB로 쓰거나, 논스를 다시 쓰거나, 키를 소스 코드에 넣거나, 태그 비교에서 시간이 새거나, 암호화만 하고 무결성 검사를 빼면 깨진다. 검증된 라이브러리의 고수준 API는 이 결정을 대신 내려 준다. 브라우저의 Web Crypto(SubtleCrypto), 언어 표준 라이브러리의 암호 모듈, libsodium 같은 잘 알려진 라이브러리, 그리고 통신이라면 직접 짜지 말고 TLS를 쓴다. 어떤 알고리즘과 키 길이를 고를지는 OWASP의 암호 저장 치트 시트가 실무 기준을 정리해 둔다.
| 하려는 일 | 고를 것 | 피할 것 |
|---|---|---|
| 데이터 암호화 | AES-GCM, ChaCha20-Poly1305 | ECB, 인증 없는 CBC·CTR, 직접 만든 XOR 암호 |
| 무결성·출처 확인 | HMAC-SHA-256, Ed25519·ECDSA 서명 | MD5·SHA-1, 키를 이어 붙인 해시 |
| 키 교환 | TLS 1.3, X25519(필요하면 양자 내성 하이브리드) | 인증 없는 디피–헬먼 |
| 무작위 값 | crypto.getRandomValues(), 운영체제 CSPRNG | Math.random(), 시각을 씨앗으로 쓴 난수 |
| 비밀번호 저장 | Argon2, scrypt, bcrypt | 일반 해시, 되돌릴 수 있는 암호화 |
그림의 키 관리 층이 실무에서 가장 자주 무너진다. 키를 만들고, 나눠 주고, 보관하고, 정기적으로 바꾸고, 폐기하는 생애주기 전체는 NIST의 SP 800-57 Part 1 Rev. 5가 다룬다. 키는 소스 코드나 설정 파일에 두지 않고, 비밀 관리 서비스나 클라우드의 키 관리 서비스(KMS), 키를 장치 밖으로 꺼내지 않고 안에서 암호 연산만 해 주는 하드웨어 보안 모듈(HSM)에 둔다. 큰 데이터는 데이터 키로 암호화하고 그 데이터 키만 KMS의 마스터 키로 다시 감싸 함께 저장하는 봉투 암호화(envelope encryption)가 흔하다. 배포 파이프라인의 비밀값 다루기는 CI/CD 노트에 있다.
흔한 오해 몇 가지.
- "Base64로 바꿨으니 암호화했다": Base64나 URL 인코딩은 글자 표현만 바꾼 것이라 키 없이 누구나 되돌린다. JWT의 내용이 그대로 읽히는 것도 이 때문이다(인증과 인가 노트).
- "비밀번호를 암호화해서 저장한다": 비밀번호는 되돌릴 수 있게 암호화하지 않고 느린 해시로 저장한다. 암호화는 키가 새면 모두 풀리고, 해시는 애초에 되돌릴 길이 없다.
- "양자 컴퓨터는 먼 이야기다": 지금 엿들어 저장해 둔 암호문은 나중에 풀릴 수 있어, 오래 지켜야 할 데이터일수록 먼저 옮긴다. 영국 NCSC의 이전 일정 안내(2025)는 2028년까지 점검과 계획, 2031년까지 우선순위 높은 이전, 2035년까지 전체 이전을 목표로 제시한다. 원리는 암호학 노트에 있다.
- Cryptography — Wikipedia
- FIPS 197: Advanced Encryption Standard (AES) — NIST
- SP 800-38A: Block Cipher Modes of Operation — NIST
- SP 800-38D: Galois/Counter Mode (GCM) — NIST
- RFC 8439: ChaCha20 and Poly1305 — IETF
- FIPS 203: ML-KEM — NIST
- FIPS 180-4: Secure Hash Standard — NIST
- RFC 2104: HMAC — IETF
- RFC 7748: Elliptic Curves for Security (X25519) — IETF
- RFC 5869: HKDF — IETF
- FIPS 186-5: Digital Signature Standard — NIST
- Math.random() — MDN
- Crypto.getRandomValues() — MDN
- Crypto.randomUUID() — MDN
- SubtleCrypto — MDN
- Cryptographic Storage Cheat Sheet — OWASP
- SP 800-57 Part 1 Rev. 5: Recommendation for Key Management — NIST
- Timelines for migration to post-quantum cryptography — NCSC