不要使用SecureRandom 来派生密钥。您可以使用诸如HKDF 之类的密钥派生函数(KDF) 来执行此操作。但老实说,您必须想出一种方法来传达密钥 - 不使用SecureRandom。
问题在于 - 相对安全 - SHA1PRNG 算法没有很好地定义。 SUN 提供者确实接受一个种子,然后将其用作仅 种子iff 您在从中检索任何随机数据之前对其进行播种。然而,其他提供者只是将种子混入到底层 CSPRNG 的状态中,这是有道理的。这也是大多数其他SecureRandom 实现的默认设置。
即使指定了底层算法(使用 DRBG),也很少有SecureRandom 实现完全指定返回随机位的方式。如果您只是期望随机值,这通常不是问题。但是,如果您将其用作诸如 KDF(或哈希函数)之类的确定性算法,那么这将成为一个问题,并且您可能会为相同的输入获得不同的键。
现在您应该能够将 AES 密钥存储在密钥库中。不确定这是否适合您的用例,但它应该可以解决您当前的问题。不幸的是,除了 PBKDF1 和 PBKDF2 之外,Java 不包含任何官方 KDF,它们需要密码而不是密钥。仅在使用主密钥的某些密钥识别数据上使用 HMAC-SHA256 通常是一个很好的“可怜的 KDF”。
这是我刚刚创建的一个快速、初始(但未记录)的实现。它模仿了 Java JCA,但没有实际执行(如 no specific KDF class is defined for it, yet)。
只要大小低于 256 位(SHA-256 的输出)并且您请求的每个密钥的标签都不同,您就可以使用任何算法规范向它询问许多密钥。如果您需要 IV 等数据,请在密钥上调用 getEncoded()。
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import hex.Hex;
public class PoorMansKDF {
public interface KeyDerivationParameters extends AlgorithmParameterSpec {
String getDerivedKeyAlgorithm();
int getDerivedKeySize();
byte[] getCanonicalInfo();
}
public static class KeyDerivationParametersWithLabel implements KeyDerivationParameters {
private final String algorithm;
private final int keySize;
private final String label;
public KeyDerivationParametersWithLabel(String algorithm, int keySize, String label) {
this.algorithm = algorithm;
this.keySize = keySize;
this.label = label;
}
@Override
public byte[] getCanonicalInfo() {
if (label == null) {
// array without elements
return new byte[0];
}
return label.getBytes(StandardCharsets.UTF_8);
}
@Override
public String getDerivedKeyAlgorithm() {
return algorithm;
}
@Override
public int getDerivedKeySize() {
return keySize;
}
}
private enum State {
CONFIGURED,
INITIALIZED;
}
public static PoorMansKDF getInstance() throws NoSuchAlgorithmException {
return new PoorMansKDF();
}
private final Mac hmac;
private State state;
private PoorMansKDF() throws NoSuchAlgorithmException {
this.hmac = Mac.getInstance("HMACSHA256");
this.state = State.CONFIGURED;
}
public void init(Key key) throws InvalidKeyException {
if (key.getAlgorithm().equalsIgnoreCase("HMAC")) {
key = new SecretKeySpec(key.getEncoded(), "HMAC");
}
hmac.init(key);
this.state = State.INITIALIZED;
}
public Key deriveKey(KeyDerivationParameters info) {
if (state != State.INITIALIZED) {
throw new IllegalStateException("Not initialized");
}
final int keySize = info.getDerivedKeySize();
if (keySize < 0 || keySize % Byte.SIZE != 0 || keySize > hmac.getMacLength() * Byte.SIZE) {
throw new IllegalArgumentException("Required key incompatible with this KDF");
}
final int keySizeBytes = keySize / Byte.SIZE;
// we'll directly encode the info to bytes
byte[] infoData = info.getCanonicalInfo();
final byte[] fullHMAC = hmac.doFinal(infoData);
final byte[] derivedKeyData;
if (fullHMAC.length == keySizeBytes) {
derivedKeyData = fullHMAC;
} else {
derivedKeyData = Arrays.copyOf(fullHMAC, keySizeBytes);
}
SecretKey derivedKey = new SecretKeySpec(derivedKeyData, info.getDerivedKeyAlgorithm());
Arrays.fill(derivedKeyData, (byte) 0x00);
if (fullHMAC != derivedKeyData) {
Arrays.fill(fullHMAC, (byte) 0x00);
}
return derivedKey;
}
// test only
public static void main(String[] args) throws Exception {
var kdf = PoorMansKDF.getInstance();
// input key (zero byte key for testing purposes, use your own 16-32 byte key)
// do not use a password here!
var masterKey = new SecretKeySpec(new byte[32], "HMAC");
kdf.init(masterKey);
// here "enc" is a label, in this case for a derived key for ENCryption
var labeledParameters = new KeyDerivationParametersWithLabel("AES", 256, "enc");
var derivedKey = kdf.deriveKey(labeledParameters);
// use your own hex decoder, e.g. from Apache Commons
System.out.println(Hex.encode(derivedKey.getEncoded()));
var aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
var gcmParams = new GCMParameterSpec(128, new byte[12]);
aesCipher.init(Cipher.ENCRYPT_MODE, derivedKey, gcmParams);
var ct = aesCipher.doFinal();
System.out.println(Hex.encode(ct));
}
}