简体   繁体   English

Java Crypto AES/GCM/NoPadding 适用于 Windows,但不适用于 Docker (AEADBadTagException)

[英]Java Crypto AES/GCM/NoPadding works on Windows but not on Docker (AEADBadTagException)

I implemented a straight simple Java utility class to encrypt and decrypt using AES/GCM/NoPadding.我实现了一个简单的 Java 实用程序类来使用 AES/GCM/NoPadding 进行加密和解密。 I use this piece of code:我使用这段代码:

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 is always a 12-byte array, key is 32-byte array generated with SecureRandom taking a seed . IV 始终是一个 12 字节的数组,密钥是使用 SecureRandom 生成的32 字节数组。 I knowthat on different OS SecureRandom is different, but encryption and decryption are performed on the same OS, so there should be no problem.我知道在不同的操作系统上SecureRandom是不同的,但是加密和解密是在同一个操作系统上进行的,所以应该没有问题。

Is it quite linear, right?是不是很线性,对吧? It works perfectly on Windows, ciphering and deciphering return the same text.它在 Windows 上完美运行,加密和解密返回相同的文本。 However, on a Docker image, the same JAR doesn not work: encryption works fine, but decryption throws "AEADBadTagException".但是,在 Docker 映像上,相同的 JAR 不起作用:加密工作正常,但解密会抛出“AEADBadTagException”。

Can you help me, please?你能帮我吗?

Do not use SecureRandom to derive a key.不要使用SecureRandom来派生密钥。 You'd use a Key Derivation Function (KDF) such as HKDF to do that.您将使用密钥派生函数 (KDF)(例如HKDF)来执行此操作。 But honestly, you have to think of a way to communicate the key - without using SecureRandom .但老实说,您必须想出一种方法来传达密钥 -使用SecureRandom

The problem is that the - relatively secure - SHA1PRNG algorithm is not well defined.问题是 - 相对安全 - SHA1PRNG 算法没有明确定义。 The SUN provider does accept a seed that is then used as an only seed iff you seed it before retrieving any random data from it. SUN 提供者确实接受一个种子,如果您在从中检索任何随机数据之前对其进行播种,则该种子将用作唯一的种子。 However, it makes sense that other providers will just mix in the seed to the state of the underlying CSPRNG.然而,其他提供者只是种子混合到底层 CSPRNG 的状态是有道理的。 This is also the default for most other SecureRandom implementations.这也是大多数其他SecureRandom实现的默认设置。

Few SecureRandom implementations fully specify the way that random bits are returned even if the underlying algorithm is specified (with the DRBG).即使指定了底层算法(使用 DRBG),也很少有SecureRandom实现完全指定返回随机位的方式。 That is usually not a problem if you are just expecting random values.如果您只是期望随机值,这通常不是问题。 However, if you are using it as a deterministic algorithm such as a KDF (or hash function, for instance) then this becomes a problem and you may get different keys for the same input.但是,如果您将它用作确定性算法,例如 KDF(或哈希函数),那么这就会成为一个问题,您可能会为相同的输入获得不同的密钥。

Nowadays you should be able to store AES secret keys within a key store.如今,您应该能够在密钥库中存储AES 密钥。 Not sure if that is a solution for your use case, but it should solve your current issue.不确定这是否是您的用例的解决方案,但它应该可以解决您当前的问题。 Unfortunately Java doesn't contain any official KDF's other than PBKDF1 and PBKDF2 which takes a password rather than a key.不幸的是,除了采用密码而不是密钥的 PBKDF1 和 PBKDF2 之外,Java 不包含任何官方 KDF。 Just using a HMAC-SHA256 over some key identifying data using a master key is commonly a good "poor mans KDF".使用主密钥在某些关键识别数据上使用 HMAC-SHA256 通常是一个很好的“穷人 KDF”。


Here is a quick, initial (but undocumented) implementation that I just created.这是我刚刚创建的一个快速、初始(但未记录)的实现。 It mimics the Java JCA without actually going into implementation (as no specific KDF class is defined for it, yet ).它模仿 Java JCA 而没有真正进入实现(因为还没有为它定义特定的 KDF 类)。

You can ask many keys from it with any algorithm specification, as long as the size is below 256 bits (the output of SHA-256) and the label is different for each key that you request.您可以使用任何算法规范从中请求许多密钥,只要大小低于 256 位(SHA-256 的输出)并且您请求的每个密钥的标签都不同。 Call getEncoded() on the key if you need data for, for instance, an IV.如果您需要数据(例如 IV getEncoded() ,请在键上调用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));
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 javax.crypto.AEADBadTagException - AES / GCM / NoPadding工作,然后没有 - javax.crypto.AEADBadTagException - AES/GCM/NoPadding works, then doesn't AES / GCM / NoPadding AEADBadTagException - AES/GCM/NoPadding AEADBadTagException Java AES GCM javax.crypto.AEADBadTagException:标签不匹配 - Java AES GCM javax.crypto.AEADBadTagException: Tag mismatch AES/GCM/NoPadding 从节点加密并在 java 上解密,抛出 AEADBadTagException:标签不匹配 - AES/GCM/NoPadding encrypt from node and decrypt on java, throw AEADBadTagException: Tag mismatch AES/GCM/NoPadding加密JAVA&解密JavaScript - AES/GCM/NoPadding encryption in JAVA & decryption in JavaScript JS 中 Java 的 AES/GCM/NoPadding 等价物 - Java's AES/GCM/NoPadding equivalent in JS 没有这样的算法:AES/GCM/NoPadding - No Such Algorithm : AES/GCM/NoPadding Spark Scala:出现错误 javax.crypto.AEADBadTagException:标签不匹配! 从 AES GCM 二进制文件解密 - Spark Scala : Got error javax.crypto.AEADBadTagException: Tag mismatch! from AES GCM binary file decryption javax.crypto.AEADBadTagException:AES/GCM/无填充加密器/解密器的标签不匹配 - javax.crypto.AEADBadTagException: Tag mismatch for AES/GCM/No Padding encryptor/decryptor Java AES / GCM / NoPadding - 什么是cipher.getIV()给我的? - Java AES/GCM/NoPadding - What is cipher.getIV() giving me?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM