이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.
기초개념 › 보안
암호화는 무엇이고, 열쇠가 없으면 어떻게 되는가
암호화는 글자를 열쇠와 함께 계산해서 알아볼 수 없는 값으로 바꾸는 것입니다. 코드에서는 encrypt("카드번호 1234", key)처럼 글자와 열쇠(key)를 넘기면 알아볼 수 없는 값이 돌아옵니다. 같은 열쇠를 가진 쪽이 decrypt(sent, key)처럼 그 값과 열쇠를 넘기면 원래 글자를 되찾습니다. 이것을 복호화라고 합니다. 열쇠가 없거나 다른 열쇠를 가진 쪽은 되찾지 못하므로, 지켜야 하는 것은 값이 아니라 열쇠입니다. 이 실습은 받는 쪽이 가진 열쇠를 같은 것, 다른 것, 없는 것으로 바꾸고 암호화 자체를 빼기도 하면서, 중간에서 보이는 값과 받는 쪽이 읽은 것을 실제로 실행해 확인합니다. (열쇠를 만드는 newKey와 계산하는 encrypt, decrypt의 본문은 화면에서 줄였습니다.)
실행으로 대조 · JDK 21에서 여섯 가지 조합을 javax.tools로 컴파일하고 실행해 대조, Node.js와 NIST 시험 벡터로 계산 확인
SecretKey key = newKey(); // 열쇠를 하나 만든다
// 보내는 쪽: 글자를 열쇠로 암호화한다
String sent = encrypt("카드번호 1234", key);
// 받는 쪽: 같은 열쇠로 푼다
String read = decrypt(sent, key);
System.out.println(sent);
System.out.println(read);
자바 JDK 21.0.12.1의 javax.crypto.Cipher("AES/GCM/NoPadding")로 실제 계산했고, 열쇠는 256비트, 태그는 128비트, 암호화마다 새로 만드는 12바이트 nonce를 결과 앞에 붙여 Base64로 적었습니다. 화면에서 줄인 것은 import, main의 선언, 도우미 메서드 newKey, encrypt, decrypt의 본문입니다. 여섯 가지 조합을 모두, 화면의 코드와 같은 줄을 main에 담은 소스를 javax.tools로 컴파일하고 클래스를 새 클래스 로더로 읽어 Main.main을 실행해, 표준 출력과 던져진 예외를 잡은 결과와 대조했습니다. 대조한 것은 중간에서 보이는 값(encrypt가 돌려준 값의 앞 12자와 전체 글자 수), 받는 쪽이 읽은 것, 원래 글자를 되찾았는지, 다른 열쇠로 풀 때 던져진 예외의 이름(javax.crypto.AEADBadTagException, 메시지 Tag mismatch)과 그때 아무것도 출력되지 않는 것입니다. 암호화한 값은 실행할 때마다 달라집니다. 대조를 위해 열쇠와 nonce를 정해진 값으로 고정해 같은 값이 나오게 했고(실제로는 둘 다 무작위로 만듭니다), 화면의 ih/DXie50GTx…(60자)는 그렇게 얻은 한 예입니다. 같은 글자를 같은 열쇠로 nonce를 새로 만들어 두 번 암호화하면 두 값이 서로 달랐고 둘 다 풀렸으며, 무작위로 만든 열쇠와 nonce로 여섯 조합을 다시 실행해도 결과의 종류(읽은 글자, 예외 이름, 값의 길이)는 같았습니다. 화면에 보이는 앞 12자는 값 앞에 붙은 nonce 부분(앞 9바이트)이고, 암호화된 글자 부분은 그 뒤에 이어집니다. 값의 길이는 글자의 길이에 따라 달라집니다. 계산이 표준대로인지는 두 가지로 확인했습니다. 같은 열쇠, nonce, 글자를 Node.js의 aes-256-gcm으로도 암호화해 암호문과 태그가 자바와 바이트까지 같았고, Node.js가 자바의 값을 같은 열쇠로 풀고 다른 열쇠로는 풀지 못했습니다. 또 NIST 암호 알고리즘 검증 프로그램(CAVP)이 공개한 GCM 시험 벡터(gcmtestvectors.zip의 gcmEncryptExtIV256.rsp와 gcmDecrypt256.rsp, 열쇠 256비트, IV 96비트, 글자 128비트, 추가 데이터 없음, 태그 128비트)에서 암호화 한 건의 암호문과 태그, 복호화 두 건(통과 한 건, FAIL 한 건)의 결과가 자바 계산과 같았습니다. 문서는 직접 열어 확인했습니다. NIST FIPS 197(2023년 갱신판) 머리말 3항은 AES를 암호화하고 복호화할 수 있는 대칭 블록 암호로 소개하고, 열쇠 길이는 128, 192, 256비트이며 데이터는 128비트 블록 단위라고 적습니다. NIST SP 800-38D(2007년판)는 3절에서 GCM이 데이터의 기밀성과 인증을 함께 보장한다고 하고, 5.2.2절과 7.2절에서 인증된 복호화는 확인 값(태그)이 맞으면 글자를, 맞지 않으면 글자 대신 FAIL을 내놓는다고 정하며, 8절에서 같은 열쇠와 같은 IV로 서로 다른 데이터를 암호화하는 일이 일어날 확률을 2의 -32제곱 이하로 하라고 요구하고, 5.2.1.1절에서 IV 길이는 96비트를 권장합니다. Java 21의 Cipher 문서는 AEAD 방식(GCM 등)에서 태그가 암호문 뒤에 붙고 복호화할 때 확인된다고 설명하고, 같은 열쇠로 암호화할 때마다 IV를 바꾸라고 안내하며, AEADBadTagException 문서는 AEAD 방식의 Cipher가 태그를 확인하지 못할 때 던져진다고 설명합니다. 이 실습은 보내는 쪽과 받는 쪽이 같은 열쇠를 쓰는 방식(대칭 키 암호화)만 다룹니다. 열쇠가 둘인 방식(공개 키)과 서명은 "세부 동작 실습: 해시·암호화·서명"에서 다루고, 받는 쪽이 같은 열쇠를 어떻게 안전하게 갖게 되는지(열쇠 전달)는 다루지 않습니다. 다른 열쇠로 풀 때 예외가 나는 것은 이 실습이 고른 방식(AES-GCM)이 값이 바뀌었거나 열쇠가 다른지를 태그로 함께 확인하기 때문입니다. 그런 확인이 없는 방식에서는 다를 수 있어서, 다른 열쇠로 AES/CTR/NoPadding을 풀어 보았더니 예외 없이 원래 글자가 아닌 다른 바이트가 나왔습니다. 열쇠를 안전하게 보관하는 방법은 다루지 않았습니다.
앞 실습: 해시 · 다음 실습: 출처(origin)
줄을 그은 곳은 처음 코드에서 지운 것이고, 색을 칠한 곳은 새로 쓴 것입니다.