대칭키 암호화: 한 키로 잠그고 푸는 약속

대칭키 암호화: 한 키로 잠그고 푸는 약속

암호화라고 하면 가장 먼저 떠오르는 모습이 자물쇠와 열쇠입니다. 열쇠로 자물쇠를 잠그고, 같은 열쇠로 다시 열고. 이 직관 그대로 동작하는 방식이 바로 대칭키 암호화입니다. 같은 비밀 키 하나로 평문을 암호문으로 바꾸고, 같은 키로 다시 평문으로 되돌리는 약속이죠.

이번 글에서는 대칭키 암호화가 정확히 어떤 약속을 지키는지, AES와 GCM 같은 표준 알고리즘이 어떻게 동작하는지, 그리고 IV나 키 관리 같은 운영 포인트가 무엇인지를 풀어보겠습니다.

대칭키가 지키는 약속

대칭키 암호화는 두 가지 일을 약속합니다. 첫째는 비밀 키를 알지 못하는 사람은 암호문을 보고도 평문을 알아낼 수 없다는 것이고, 둘째는 같은 키를 가진 두 사람은 같은 알고리즘으로 평문과 암호문을 자유롭게 오갈 수 있다는 것이죠.

대칭키 암호화의 흐름
평문 ── (키로 암호화) ──▶ 암호문

                              ▼ (같은 키로 복호화)
                            평문

이 단순함이 대칭키의 가장 큰 장점입니다. 연산이 빠르고 구현이 단순해서, 큰 데이터를 다룰 때 비대칭키보다 훨씬 효율적이거든요. 실제로 HTTPS에서 데이터를 실어 나르는 본 통신은 대부분 대칭키로 처리되고, 비대칭키는 그 키를 안전하게 나누는 단계에만 쓰입니다.

대신 한 가지 어려운 문제가 따라옵니다. “비밀 키를 어떻게 양쪽이 안전하게 나눠 가질 것인가”인데요. 이 문제는 글의 후반부에서 다시 다루고, 먼저 대칭키 자체가 어떻게 동작하는지부터 살펴보겠습니다.

AES, 사실상 표준이 된 알고리즘

대칭키 알고리즘의 이름은 여럿 있지만, 현대에 사실상 표준으로 자리 잡은 것은 AES(Advanced Encryption Standard)입니다. 2001년 미국 NIST가 표준으로 채택한 이후, 거의 모든 운영 체제와 라이브러리가 기본으로 지원하고 있죠.

AES는 데이터를 128비트 블록 단위로 나눠 처리하는 블록 암호입니다. 키 길이는 128, 192, 256비트 중에서 고를 수 있는데, 일반적으로 AES-256이 가장 자주 쓰입니다. 숫자가 클수록 안전하지만, AES-128도 현재 알려진 공격으로는 깰 수 없는 수준이라 자원이 빠듯한 환경에서는 충분한 선택지가 됩니다.

흥미로운 점은 요즘 CPU 대부분이 AES 전용 명령어(예: AES-NI)를 가지고 있다는 사실입니다. 하드웨어가 한 사이클에 여러 라운드를 처리해 주기 때문에, 같은 데이터를 평문으로 옮길 때와 암호화해서 옮길 때의 성능 차이가 거의 느껴지지 않습니다. “HTTPS는 느리다”는 옛 인식이 사라진 데에는 이런 하드웨어 가속도 한몫했죠.

운영 모드: CBC, CTR, 그리고 GCM

블록 암호는 한 번에 한 블록(AES의 경우 128비트, 16바이트)만 처리할 수 있습니다. 실제 데이터는 그보다 훨씬 길어서, 여러 블록을 어떻게 이어 붙여 처리할지에 대한 약속이 필요한데요. 이 약속을 운영 모드(mode of operation)라고 부릅니다.

가장 단순한 ECB 모드는 각 블록을 독립적으로 암호화하는 방식인데, 같은 평문 블록은 항상 같은 암호문 블록이 되어 패턴이 그대로 드러납니다. 유명한 “ECB로 암호화한 펭귄 그림” 사례에서 보듯, 보안적으로는 절대 쓰면 안 되는 모드죠.

CBC(Cipher Block Chaining)는 이전 암호문 블록을 다음 평문에 XOR해서 패턴을 깨는 방식입니다. 오랫동안 표준처럼 쓰였지만, 패딩 오라클 공격 같은 미묘한 약점이 있고 무결성 검사를 직접 해야 한다는 부담이 있습니다.

CTR(Counter) 모드는 카운터 값을 암호화해서 그 결과를 평문에 XOR하는 방식입니다. 블록 사이의 의존성이 없어 병렬 처리가 가능하고 구현이 단순해서 성능이 좋지만, 무결성 검사는 별도로 챙겨야 합니다.

요즘 표준이 된 GCM(Galois/Counter Mode)은 CTR의 빠른 처리에 무결성 검사까지 한 번에 묶어 주는 모드입니다. 암호화와 동시에 인증 태그(authentication tag)를 만들어 주기 때문에, 누가 중간에서 암호문을 한 비트라도 바꾸면 복호화 단계에서 곧장 들통나는데요. 별도의 MAC을 붙일 필요가 없어 운영이 단순해지고, AES-NI 가속의 이점도 그대로 살릴 수 있어 새로 만드는 시스템에는 거의 모두 GCM을 권장합니다.

ChaCha20, AES가 아닌 또 다른 선택지

AES가 사실상 표준이지만 유일한 답은 아닙니다. 하드웨어 가속이 없는 환경(모바일 저사양 기기, 일부 임베디드 시스템)에서는 AES의 성능이 크게 떨어지는데, 이 자리에 잘 어울리는 알고리즘이 ChaCha20입니다.

ChaCha20은 블록 암호가 아니라 스트림 암호로 동작합니다. 키와 카운터, IV를 입력받아 의사 난수 흐름을 만들고, 그 흐름을 평문에 XOR해서 암호문을 만드는 방식인데요. 하드웨어 가속 없이도 일관되게 빠른 성능을 내고, 보안 강도도 AES-256과 비슷한 수준입니다.

GCM에 해당하는 인증 암호화 모드가 ChaCha20-Poly1305입니다. 구글이 모바일 환경의 TLS에 일찍 도입했고, 요즘은 TLS 1.3이 AES-GCM과 함께 표준 권장 알고리즘으로 두고 있죠. 하드웨어 환경을 가리지 않고 편하게 쓸 수 있어, 새 시스템을 만들 때 AES-GCM과 ChaCha20-Poly1305 중 하나를 골라 가는 식이 일반적입니다.

인증 암호화(AEAD)라는 새 묶음

CBC 같은 옛 모드를 쓸 때는 무결성을 따로 챙겨야 했습니다. “암호문을 누가 중간에서 살짝 바꿔도 복호화는 그냥 되어 버리는” 약점 때문에, 별도의 MAC을 붙여 검증하는 단계가 필요했던 거죠.

GCM이나 ChaCha20-Poly1305 같은 현대 모드는 이 두 일을 한 번에 묶어 처리합니다. 이런 묶음을 AEAD(Authenticated Encryption with Associated Data)라고 부르는데, 한 번의 호출로 암호화와 인증을 모두 처리해 주니 운영 실수도 줄어들고 코드도 간결해집니다.

AEAD에는 추가 데이터(associated data)를 함께 인증하는 슬롯도 있습니다. 암호화하지는 않지만 변조되어서는 안 되는 메타데이터(예: 패킷 헤더, 사용자 ID)를 이 슬롯에 넣으면, 본문과 함께 무결성이 검증되는데요. 프로토콜 헤더처럼 평문이지만 변조를 막아야 하는 자리에 잘 어울립니다.

IV(초기화 벡터)가 필요한 이유

CBC, CTR, GCM 모두에 공통으로 등장하는 친구가 IV(Initialization Vector)입니다. 같은 키와 같은 평문이라도 매번 다른 암호문이 나오게 만들어 주는 임의의 값인데요.

IV가 다르면 암호문도 다르다
평문 "Hello" + 키 K + IV1 ──▶ 암호문 X
평문 "Hello" + 키 K + IV2 ──▶ 암호문 Y  (X와 다름)

IV가 없거나 매번 같은 값을 쓰면, 같은 평문이 같은 암호문으로 나오게 되어 패턴이 노출됩니다. GCM 같은 모드에서는 IV가 단 한 번이라도 재사용되면 보안성이 완전히 무너지므로, 같은 키로 암호화할 때마다 새로운 IV를 만들어 쓰는 것이 절대적인 규칙입니다.

다행히 IV는 비밀이 아닙니다. 복호화에는 같은 IV가 필요하지만, 공격자가 보아도 의미가 없도록 설계되어 있어 암호문 앞에 그대로 붙여 보내거나 별도 필드로 저장해도 괜찮은데요. Web Crypto API 같은 라이브러리들은 12바이트 난수를 매번 새로 만들어 IV로 쓰는 것을 권장합니다.

키 관리라는 어려운 문제

대칭키 암호화 자체의 알고리즘은 잘 정리되어 있지만, 운영에서 가장 고민거리가 되는 부분은 키 관리입니다. “비밀 키 하나만 안전하면 모든 통신이 안전하다”는 말은 곧 “그 비밀 키가 새는 순간 모든 통신이 한 번에 무너진다”는 뜻이기도 하죠.

키를 어떻게 만들고, 어디에 보관하고, 어떻게 회전(rotation)할지가 모두 결정거리가 됩니다. 환경 변수에 평문으로 두면 코드 저장소에 실수로 올라갈 수 있고, 코드에 박아 두면 더 위험하고요. 요즘은 AWS KMS, GCP KMS, HashiCorp Vault 같은 전용 키 관리 서비스에 키를 두고 애플리케이션은 필요할 때만 빌려 쓰는 식이 표준에 가깝습니다.

키가 새거나 새었을 가능성이 있을 때를 대비해 정기적으로 키를 바꾸는 것도 중요합니다. 키 회전 주기를 짧게 잡으면 새었을 때의 영향이 한정되지만, 운영 부담이 늘어나는 트레이드오프가 따라오는데요. 중요한 자원에는 짧은 주기, 덜 민감한 자리에는 긴 주기로 가는 식의 차등 운영이 흔한 패턴입니다.

여기에 더해 “비밀 키를 처음에 어떻게 양쪽이 나눠 가질 것인가”라는 더 근본적인 문제가 있습니다. 이 문제가 바로 비대칭키가 등장한 이유이고, 다음 글에서 깊이 다룰 주제이기도 합니다.

비밀번호로부터 키를 만들 때: KDF

가끔은 비밀 키를 처음부터 임의의 바이트로 만들지 못하고, 사람이 외우는 비밀번호로부터 끌어내야 할 때가 있습니다. 사용자가 입력한 비밀번호로 파일을 암호화한다거나, 클라이언트에서 비밀번호로 파생한 키로 데이터를 보호하는 경우인데요. 이때 등장하는 도구가 키 유도 함수(KDF, Key Derivation Function)입니다.

대표적인 KDF로 PBKDF2, scrypt, argon2 같은 이름이 있습니다. 해시 글의 비밀번호 저장 부분에서 만난 알고리즘들과 같은 친구들로, 의도적으로 느리고 메모리도 많이 쓰도록 설계되어 있어 무차별 대입 공격을 어렵게 만듭니다. 사용자가 입력한 비밀번호를 그대로 키로 쓰면 위험하지만, KDF를 한 번 거치면 충분한 엔트로피를 가진 키로 변환할 수 있는 셈이죠.

KDF를 쓸 때 함께 챙겨야 하는 게 소금(salt)과 비용(iteration count)입니다. 같은 비밀번호라도 사용자마다 다른 소금을 붙이면 미리 계산된 무지개 테이블이 무력화되고, 비용을 충분히 높이면 공격자가 시도할 수 있는 횟수가 크게 줄어듭니다. Web Crypto API는 PBKDF2를 표준으로 지원하니, 클라이언트 측에서 비밀번호로부터 키를 끌어낼 때 곧장 활용할 수 있습니다.

비대칭키와 짝을 이루는 이유

대칭키는 빠르고 단순하지만, 키를 안전하게 나누기가 어렵습니다. 비대칭키는 반대로 키 교환이 우아하지만, 연산이 무거워 큰 데이터를 직접 암호화하기에는 비효율적이죠.

그래서 현실의 시스템은 거의 항상 두 방식을 섞어 씁니다. 이를 하이브리드 암호화라고 부르는데, HTTPS와 TLS가 가장 친숙한 예입니다. 세션을 시작할 때 비대칭키로 짧은 비밀 키 하나를 안전하게 합의하고, 그 뒤로는 그 비밀 키를 가지고 대칭키 암호화로 본 통신을 흘려보내는 식이죠.

이렇게 하면 비대칭키의 키 교환 우아함과 대칭키의 빠른 처리 속도를 모두 챙길 수 있습니다. 대칭키는 “친구가 누구인지 이미 아는 사이의 빠른 대화”, 비대칭키는 “처음 만나는 사람과 비밀 통로를 만드는 첫 인사”라고 비유하면 두 도구의 역할이 또렷해집니다.

마치며

대칭키 암호화는 한 줄로 보면 “같은 키로 잠그고 푸는 약속”이지만, AES 알고리즘과 GCM 같은 운영 모드, IV의 규칙, 키 관리까지 결정거리가 줄지어 자리하고 있습니다. 새 시스템을 설계한다면 AES-256-GCM에 매번 새 IV를 쓰는 조합부터 출발해, 키 보관과 회전 정책을 함께 잡아두는 흐름이 가장 안전하죠.

이어지는 비대칭키 글에서는 키 교환 문제와 디지털 서명, 인증서 같은 영역을 다뤄 보겠습니다. 대칭키 알고리즘의 자세한 명세가 궁금하다면 NIST의 AES 페이지를 출발점으로 추천합니다.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord