我实现了一个简单的简单Java实用程序类,以使用AES / GCM / NoPadding进行加密和解密。我使用这段代码:
public byte[] encrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.ENCRYPT_MODE);
return cipher.doFinal(input);
}
public byte[] decrypt(byte[] input, byte[] key, byte[] iv) throws Exception{
Cipher cipher = initAES256GCMCipher(key, iv, Cipher.DECRYPT_MODE);
return cipher.doFinal(input);
}
private Cipher initAES256GCMCipher(byte[] key, byte[] iv, int encryptionMode) throws Exception{
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, iv);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(encryptionMode, secretKey, gcmParameterSpec);
return cipher;
}
IV始终是一个12字节的数组,密钥是32字节的数组,由SecureRandom生成并带有种子。我知道在不同的操作系统上,SecureRandom有所不同,但是加密和解密是在同一操作系统上执行的,因此应该没有问题。
它是线性的,对吗?它在Windows上完美地工作,加密和解密返回相同的文本。但是,在Docker镜像上,无法使用相同的JAR:加密工作正常,但解密会引发“AEADBadTagException”。
你能帮我吗?
最佳答案
不要使用SecureRandom
派生密钥。 您可以使用诸如HKDF之类的密钥派生功能(KDF)来实现。但老实说,您必须考虑一种无需使用SecureRandom
即可传达密钥的方法。
问题是,相对安全的SHA1PRNG算法定义不明确。 SUN提供程序确实接受种子,然后将其用作种子的唯一种子和,然后再从中检索任何随机数据。但是,其他提供者将混合种子到基础CSPRNG的状态是有意义的。这也是大多数其他SecureRandom
实现的默认设置。
很少有SecureRandom
实现完全指定返回随机位的方式,即使指定了基础算法(使用DRBG)也是如此。如果您只是期望随机值,通常这不是问题。但是,如果将其用作确定性算法(例如KDF(例如,哈希函数)),则这将成为问题,并且对于相同的输入,您可能会获得不同的键。
如今,您应该能够在密钥存储区中存储AES secret 密钥。不知道这是否是您的用例的解决方案,但是它应该可以解决您当前的问题。不幸的是,除了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));
}
}
关于java - Java Crypto AES/GCM/NoPadding在Windows上有效,但在Docker上无效(AEADBadTagException),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60118474/