如何合并多枚SecretKey生成需全部密钥才可解密的加密密钥?
Nice question—this is a common scenario for multi-party or split-key encryption where every key is mandatory to unlock the data. Using a Key Derivation Function (KDF) to combine your KeyGenerator-produced SecretKeys is absolutely the right call, and way more secure than naive methods like simple concatenation or XOR. Let's walk through how to implement this properly.
Core Concept
The goal is to derive a single encryption key only when all original SecretKeys are present. A KDF adds critical security layers like salt, iteration counts, and cryptographic hashing to ensure that missing even one key makes it computationally infeasible to recover the encryption key. We’ll use either HKDF (ideal for key-to-key derivation) or PBKDF2 (great for adding brute-force resistance) here.
Step-by-Step Implementation
1. Extract Raw Key Material
First, get the byte representation of each SecretKey you’ve generated with KeyGenerator. Use the getEncoded() method—this gives you the raw bytes we’ll feed into the KDF.
2. Combine Key Material (in a Fixed Order!)
Merge the byte arrays of all your keys in a strict, unchanging order. The order matters: if you shuffle keys during decryption, the KDF will spit out a completely different key, making decryption impossible. For example, always combine userKey first, then ourKey, then any additional keys you have.
3. Derive the Encryption Key with KDF
We’ll use HKDF for this example (it’s optimized for deriving keys from existing key material). You’ll need to generate a random salt (save this with your ciphertext!) and an optional "info" string to distinguish this key from others (e.g., "USER_DATA_ENCRYPTION" to avoid reusing keys for different purposes).
4. Encrypt/Decrypt with the Derived Key
Use a secure symmetric encryption algorithm like AES-GCM (it provides both confidentiality and integrity) with the derived key. For decryption, repeat the exact same KDF process with all original keys, salt, and info string to regenerate the encryption key.
Java Code Example
import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.HKDFParameterSpec; import java.security.SecureRandom; import java.util.Arrays; public class MultiKeyEncryption { // Configurable constants private static final String AES_GCM_ALGORITHM = "AES/GCM/NoPadding"; private static final String HKDF_ALGORITHM = "HKDF"; private static final int AES_KEY_SIZE = 256; private static final int GCM_TAG_LENGTH = 128; // 16 bytes for authentication private static final int HKDF_ITERATIONS = 10000; // Adjust based on security/performance needs public static void main(String[] args) throws Exception { // 1. Generate your SecretKeys via KeyGenerator KeyGenerator keyGen = KeyGenerator.getInstance("AES"); keyGen.init(AES_KEY_SIZE, new SecureRandom()); SecretKey userKey = keyGen.generateKey(); SecretKey ourKey = keyGen.generateKey(); // 2. Data to encrypt byte[] plaintext = "Sensitive user data here".getBytes("UTF-8"); // 3. Derive the shared encryption key SecretKey encryptionKey = deriveCombinedKey(userKey, ourKey); // 4. Encrypt the data byte[] ciphertextWithIv = encryptData(plaintext, encryptionKey); // 5. Decrypt (requires ALL original keys) byte[] decryptedText = decryptData(ciphertextWithIv, encryptionKey); System.out.println("Decrypted result: " + new String(decryptedText, "UTF-8")); } private static SecretKey deriveCombinedKey(SecretKey... keys) throws Exception { // Merge all key bytes in fixed order byte[] combinedMaterial = new byte[0]; for (SecretKey key : keys) { byte[] keyBytes = key.getEncoded(); combinedMaterial = Arrays.copyOf(combinedMaterial, combinedMaterial.length + keyBytes.length); System.arraycopy(keyBytes, 0, combinedMaterial, combinedMaterial.length - keyBytes.length, keyBytes.length); } // Generate random salt (save this with ciphertext!) byte[] salt = new byte[16]; new SecureRandom().nextBytes(salt); // Info string to scope the key to this use case byte[] info = "MULTI_KEY_USER_DATA".getBytes("UTF-8"); // Initialize HKDF SecretKeyFactory hkdfFactory = SecretKeyFactory.getInstance(HKDF_ALGORITHM); HKDFParameterSpec spec = new HKDFParameterSpec(combinedMaterial, salt, info, AES_KEY_SIZE); return hkdfFactory.generateSecret(spec); } private static byte[] encryptData(byte[] plaintext, SecretKey key) throws Exception { Cipher cipher = Cipher.getInstance(AES_GCM_ALGORITHM); // Generate 12-byte IV (recommended for GCM) byte[] iv = new byte[12]; new SecureRandom().nextBytes(iv); GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec); byte[] encrypted = cipher.doFinal(plaintext); // Combine IV and ciphertext (IV first, then encrypted data) byte[] result = new byte[iv.length + encrypted.length]; System.arraycopy(iv, 0, result, 0, iv.length); System.arraycopy(encrypted, 0, result, iv.length, encrypted.length); return result; } private static byte[] decryptData(byte[] ciphertextWithIv, SecretKey key) throws Exception { // Split IV and ciphertext byte[] iv = Arrays.copyOfRange(ciphertextWithIv, 0, 12); byte[] ciphertext = Arrays.copyOfRange(ciphertextWithIv, 12, ciphertextWithIv.length); Cipher cipher = Cipher.getInstance(AES_GCM_ALGORITHM); GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec); return cipher.doFinal(ciphertext); } }
Critical Security Notes
- Fixed Key Order: Never change the order of keys when combining them—decryption will fail if you do.
- Salt Storage: The salt used in HKDF must be stored alongside the ciphertext. Without it, you can’t regenerate the correct encryption key.
- Use Authenticated Encryption: AES-GCM is preferred over ECB/CBC because it detects tampering with the ciphertext.
- Secure Key Storage: Each original
SecretKeyshould be stored in a secure location (like a hardware security module or key management service) to prevent unauthorized access. - Tune KDF Parameters: Adjust the HKDF iteration count based on your security needs—higher counts make brute-force attacks harder but slow down key derivation.
内容的提问来源于stack exchange,提问作者Kevin Day

