【问题标题】:Java Crypto AES/GCM/NoPadding works on Windows but not on Docker (AEADBadTagException)Java Crypto AES/GCM/NoPadding 适用于 Windows 但不适用于 Docker (AEADBadTagException)
【发布时间】:2020-02-08 04:30:06
【问题描述】:

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

你能帮帮我吗?

【问题讨论】:

    标签: java docker encryption


    【解决方案1】:

    不要使用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));
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多