当前位置: 首页 > 知识库问答 >
问题:

Java加密器aes/gcm/nopadding可以在Windows上工作,但不能在Docker上工作(AEADBadTagException)

丁翰海
2023-03-14

我实现了一个简单的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字节数组,key是32字节数组,SecureRandom使用种子生成。我知道在不同的OS上SecureRandom是不同的,但是加密和解密是在同一个OS上执行的,所以应该没有问题。

是不是很线性,对吧?它在Windows上工作得很好,加密和破译返回相同的文本。然而,在Docker映像上,相同的JAR不起作用:加密工作正常,但解密抛出“AeadBadTagException”。

共有1个答案

章安宜
2023-03-14

不要使用securerandom派生密钥。您可以使用键派生函数(KDF)来实现这一点,比如HKDF。但老实说,您必须想出一种方法来通信密钥--不使用securerandom

问题是-相对安全的-SHA1PRNG算法没有很好地定义。SUN提供者确实接受一个种子,然后将它用作唯一的种子,如果您在从中检索任何随机数据之前播种它。但是,其他提供程序只会将种子混合到底层CSPRNG的状态,这是有意义的。这也是大多数其他SecureRandom实现的默认值。

很少有SecureRandom实现完全指定随机比特的返回方式,即使指定了基础算法(使用DRBG)。如果您只是期望随机值,这通常不是问题。但是,如果您使用它作为确定性算法,例如KDF(或哈希函数),那么这就成为一个问题,您可能会为相同的输入获得不同的键。

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));
    }
}
 类似资料:
  • 我试图制作一个跨平台的JavaFX应用程序,它在Windows和OSX机器上工作得很好,但在Linux上不行。 jar是在Intellij思想中使用基本的JavaFX配置构建的。 有人帮忙吗?

  • 我是android新手,我能够在kotlin中为我的应用程序设置firebase。如果我在Nexus 5X API 27 emulator中运行该应用程序,我就能够获取数据库,但当我在实际设备三星S5(Google play Services V 12.5.29,android V 5.0)中运行该应用程序时,我无法获得addValueEventListener回调。 Gradle文件: 我知道这

  • 我有一段非常简单的Java代码,在那里我尝试从Java连接到我的Oracle DB。 在Windows下一切正常,但当我尝试在Ubuntu上运行时,我得到了一个错误。 我读了很多书,也试过很多解决方法。这是我的代码: 当我运行它时,我收到一个错误: 连接失败Java.sql.sqlRecoverable异常:IO错误:网络适配器无法在oracle.jdbc.driver.T4CConnection

  • 结果在Windows和Linux之间有所不同。 Linux:鼠标位置:0,0 Windows:鼠标位置:623.0,367.0 我不知道为什么它不能在windows上工作,甚至似乎与lwjgl版本完全无关,因为我尝试了3.1.6、3.2.1、3.2.2和3.2.3-snapshot,所有这些版本都是一样的。所以问题要么是我在创建窗口时忘记了一些东西,要么是windows在某个更新中损坏了一些东西,

  • 你好,我正在制作一个Java多人游戏,一切都很好。它具备了所有用户需要的功能,但我发现了一个问题,它使用ServerSocket作为服务器,Socket作为客户端,工作正常,但最大的问题是,它在全球范围内都不起作用。只有兰,我甚至试过Hamachi,但也没用。 你有什么想法吗? 更多信息:我使用服务器中的特定线程来接受、发送和接收套接字,还使用客户端中的特定线程来发送和接收套接字。它发送一个我制作

  • 与/user/login?_format=json完全相同的POST在Postman中不起作用(错误403禁止),但在curl中使用相同的参数可以正常工作。 这个Curl的工作原理是:Curl--header“content-type:application/json”--request POST--data“{”name“:”bobo“,”pass“:”xxx“}”http://[domain]