패스워드 기반 키 생성(Password-Based Key Derivation, PBKD)은 사용자가 제공한 패스워드(일반적으로 짧고 사람이 기억하기 쉬운 문자열)를 기반으로 암호화에 사용할 수 있는 안전하고 고정된 길이의 암호화 키를 생성하는 방법입니다. 자바에서는 JCE(Java Cryptography Extension)를 통해 이를 구현할 수 있으며, 대표적으로 **PBKDF2**(Password-Based Key Derivation Function 2)가 널리 사용됩니다. 아래에서 패스워드 기반 키 생성의 개념, 동작 원리, 주요 알고리즘, 자바에서의 구현 방법 등을 상세히 설명하겠습니다. --- ### **패스워드 기반 키 생성의 개념** - **문제점**: 패스워드는 보통 길이가 짧고 엔트로피(무작위성)가 낮아 직접 암호화 키로 사용하기에는 부적합합니다. 예를 들어, AES 키는 128비트(16바이트) 이상이어야 하지만, "password123" 같은 패스워드는 그보다 훨씬 짧고 예측 가능합니다. - **해결책**: PBKD는 패스워드에 **솔트(Salt)**와 반복적인 해시 연산을 적용하여 안전하고 고정된 길이의 키를 생성합니다. - **목적**: 1. 패스워드의 엔트로피를 높여 무차별 대입 공격(Brute Force)이나 사전 공격(Dictionary Attack)을 어렵게 만듦. 2. AES, RSA 등 암호화 알고리즘이 요구하는 특정 길이의 키(예: 256비트)를 생성. --- ### **주요 구성 요소** 1. **패스워드(Password)**: - 사용자가 제공한 문자열(예: "MySecretPass123"). - 보통 유니코드 문자로 표현되며, 직접 키로 사용되지 않음. 2. **솔트(Salt)**: - 무작위로 생성된 바이트 배열. - 동일한 패스워드라도 다른 키를 생성하도록 보장하며, 사전 공격(Precomputed Table, Rainbow Table)을 방지. - 일반적으로 8바이트 이상(16바이트 권장). 3. **반복 횟수(Iteration Count)**: - 해시 함수를 반복 적용하는 횟수. - 계산 비용을 높여 무차별 대입 공격을 느리게 만듦. - 예: 10,000회 이상(현대 하드웨어 기준으로는 100,000회 이상 권장). 4. **키 길이(Key Length)**: - 출력할 키의 길이(예: AES 256비트 = 32바이트). - 사용하려는 암호화 알고리즘에 맞게 지정. 5. **해시 알고리즘**: - 내부적으로 사용되는 암호화 해시 함수(예: SHA-256, SHA-512 등). --- ### **대표 알고리즘: PBKDF2** - **정의**: NIST와 RSA에서 표준화된 알고리즘으로, HMAC(Hash-based Message Authentication Code)을 기반으로 키를 생성. - **동작 방식**: 1. 패스워드와 솔트를 입력으로 HMAC 함수(예: HMAC-SHA256)에 넣음. 2. 지정된 반복 횟수만큼 HMAC 연산을 반복. 3. 결과로 고정된 길이의 키를 출력. - **표기**: 자바에서는 `"PBKDF2WithHmacSHA256"`처럼 HMAC와 결합된 해시 알고리즘을 지정. #### **지원되는 해시 알고리즘** - `PBKDF2WithHmacSHA1` (구형, 권장되지 않음). - `PBKDF2WithHmacSHA256` (권장). - `PBKDF2WithHmacSHA512` (더 높은 보안). --- ### **자바에서 PBKDF2 구현 예제** 자바에서는 `PBEKeySpec`와 `SecretKeyFactory`를 사용하여 PBKDF2 기반 키를 생성할 수 있습니다. ```java import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64; public class Pbkdf2Example { public static void main(String[] args) throws Exception { // 1. 패스워드 정의 String password = "MySecretPass123"; // 2. 솔트 생성 SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; // 16바이트 솔트 random.nextBytes(salt); // 3. PBKDF2 매개변수 설정 int iterationCount = 100000; // 반복 횟수 int keyLength = 256; // 출력 키 길이 (AES 256비트) KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength); // 4. PBKDF2 키 생성 SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); SecretKey key = factory.generateSecret(spec); // 5. 생성된 키 출력 byte[] keyBytes = key.getEncoded(); String keyBase64 = Base64.getEncoder().encodeToString(keyBytes); System.out.println("Generated Key: " + keyBase64); System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt)); } } ``` #### **출력 예시** ``` Generated Key: X7k... (32바이트 Base64 인코딩) Salt: abC... (16바이트 Base64 인코딩) ``` --- ### **AES와 결합한 예제** 생성된 키를 AES-GCM 암호화에 사용하는 예제입니다. ```java import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; public class Pbkdf2AesGcmExample { public static void main(String[] args) throws Exception { // 1. PBKDF2로 키 생성 (위 예제에서 가져옴) String password = "MySecretPass123"; byte[] salt = new byte[16]; SecureRandom random = new SecureRandom(); random.nextBytes(salt); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100000, 256); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); SecretKey pbkdf2Key = factory.generateSecret(spec); SecretKey key = new SecretKeySpec(pbkdf2Key.getEncoded(), "AES"); // 2. AES-GCM 설정 byte[] iv = new byte[12]; // 12바이트 Nonce random.nextBytes(iv); GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); // 128비트 태그 Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec); // 3. 암호화 byte[] plaintext = "Hello, PBKDF2!".getBytes(StandardCharsets.UTF_8); byte[] ciphertext = cipher.doFinal(plaintext); // 출력 System.out.println("Ciphertext: " + Base64.getEncoder().encodeToString(ciphertext)); } } ``` --- ### **장점** 1. **보안성**: 솔트와 반복 연산으로 패스워드의 약점을 보완. 2. **유연성**: 출력 키 길이를 자유롭게 조정 가능. 3. **표준화**: PBKDF2는 NIST SP 800-132 등에서 권장. ### **단점 및 주의사항** 1. **성능**: 반복 횟수가 많을수록 계산 비용 증가 (의도된 설계이나 최적화 필요). 2. **솔트 관리**: 솔트는 암호화된 데이터와 함께 저장해야 하며, 재사용 시 동일한 키를 생성해야 함. 3. **현대 대안**: Argon2, bcrypt, scrypt 등이 더 강력한 대안으로 주목받고 있음(자바 기본 JCE에서는 미지원, 외부 라이브러리 필요). --- ### **권장 설정** - **솔트**: 최소 16바이트, SecureRandom으로 생성. - **반복 횟수**: 하드웨어 성능에 따라 100,000회 이상(예: 2025년 기준 1초 이내 처리 목표). - **해시**: SHA-256 또는 SHA-512. - **키 길이**: AES 사용 시 256비트 권장. --- 패스워드 기반 키 생성에 대해 더 궁금한 점이나 특정 부분에 대한 추가 설명이 필요하면 말씀해주세요!