我实现了一个简单的简单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/

10-10 15:37