【问题标题】:CipherInputStream hangs while reading data读取数据时 CipherInputStream 挂起
【发布时间】:2020-06-19 21:18:03
【问题描述】:

我正在尝试加密/解密一些文件,我将使用通过CipherIn/OutputStreams 传输的FileIn/OutputStreams 来读取/写入这些文件。概念上相当简单,我已经使用原始字节数组和Cipher.doFinal 让它工作。所以我知道我的加密参数(位大小、iv 大小等)是正确的。 (或者至少是功能性的?)

我可以通过CipherOutputStream 写入数据就好了。但是,当我尝试通过CipherInputStream 读回该数据时,它会无限期挂起。

我发现的唯一related problem 仍未得到答复,并且可能与我的问题有根本的不同,因为我的问题将始终在磁盘上提供所有数据,而不是相关问题依赖于Sockets

我尝试了许多解决方案,最明显的一个是更改缓冲区大小 (data = new byte[4096];)。我尝试了许多值,包括明文的大小和加密数据的大小。这些值都不起作用。我发现的唯一解决方案是完全避免使用CipherInputStream,而是依赖Cipher.doFinalCipher.update

我错过了什么吗?能够使用CipherInputStream 会非常好,而不必使用Cipher.update 重新发明轮子。

SSCCE:

private static final String AES_ALG = "aes_256/gcm/nopadding";
private static final int GCM_TAG_SIZE = 128;

private static void doEncryptionTest() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
        InvalidAlgorithmParameterException, FileNotFoundException, IOException
{
    File f = new File("encrypted_random_data.dat");
    // 12-byte long iv
    byte[] iv = new byte[] {0x27, 0x51, 0x34, 0x14, -0x65, 0x4d, -0x67, 0x35, -0x63, 0x11, -0x02, -0x05};
    // 256-bit long key
    byte[] keyBytes = new byte[] {0x55, -0x7f, -0x17, -0x29, -0x68, 0x25, 0x29, 0x5f, -0x27, -0x2d, -0x4d, 0x1b,
            0x25, 0x74, 0x57, 0x35, -0x23, -0x1b, 0x12, 0x7c, 0x1, -0xf, -0x60, -0x42, 0x1c, 0x61, 0x3e, -0x5,
            -0x13, 0x31, -0x48, -0x6e};
    SecretKey key = new SecretKeySpec(keyBytes, "AES");

    OutputStream os = encryptStream(key, iv, f);

    System.out.println("generating random data...");
    // 24MB of random data
    byte[] data = new byte[25165824];
    new Random().nextBytes(data);

    System.out.println("encrypting and writing data...");
    os.write(data);

    os.close();

    InputStream is = decryptStream(key, iv, f);

    System.out.println("reading and decrypting data...");
    // read the data in 4096 byte packets
    int n;
    data = new byte[4096];
    while ((n = is.read(data)) > 0)
    {
        System.out.println("read " + n + " bytes.");
    }

    is.close();
}

private static OutputStream encryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
        NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
    GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
    Cipher enc = Cipher.getInstance(AES_ALG);
    enc.init(Cipher.ENCRYPT_MODE, key, spec);

    OutputStream os = new CipherOutputStream(new FileOutputStream(f), enc);
    return os;
}

private static InputStream decryptStream(SecretKey key, byte[] iv, File f) throws NoSuchAlgorithmException,
        NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, FileNotFoundException
{
    GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
    Cipher dec = Cipher.getInstance(AES_ALG);
    dec.init(Cipher.DECRYPT_MODE, key, spec);

    InputStream is = new CipherInputStream(new FileInputStream(f), dec);
    return is;
}

【问题讨论】:

    标签: java io aes aes-gcm


    【解决方案1】:

    它没有挂起,只是非常慢。 CipherInputStream 有一个大小为 512 的固定输入缓冲区,这意味着它一次最多调用 512 个字节的 Cipher#update(byte[], int, int) 方法。使用更大的缓冲区大小手动解密会更快。

    原因是使用 512 字节调用 update 50 000 次比使用 65 KB 调用 400 次需要更长的时间。我不确定究竟是什么原因,但无论您传递多少数据,您似乎都必须为每次调用 update 支付持续的开销。

    另外请注意,您不能使用 AES GCM 来解密大文件。按照设计,Sun 的密码实现在解密之前将整个密文缓存在内存中。您必须将明文分成足够小的块并单独加密每个块。

    另请参阅 https://crypto.stackexchange.com/questions/20333/encryption-of-big-files-in-java-with-aes-gcmHow come putting the GCM authentication tag at the end of a cipher stream require internal buffering during decryption?

    【讨论】:

    • 哦!你说得对。我很奇怪它只在第一次调用CipherInputStream.read 时挂起,阻塞了“读取 n 字节”行。就好像它忽略了我传递的缓冲区,选择在第一次调用read 时读取整个流。无论如何,我在我的大型项目中已经等待了足够长的时间,所以我一定有一个错误。感谢您的帮助!
    • 所以我挖得更深了,看起来 AES GCM 密码只有在执行doFinal 之后才会返回明文。所有中间更新都返回 0 字节的明文:github.com/JetBrains/jdk8u_jdk/blob/…
    • 因为 Sun 选择将身份验证标签放在流的末尾,update 基本上只是缓冲数据,doFinal 完成所有工作。
    • 感谢您提供更多信息。在自己研究了一下之后,我意识到 Java 的 GCM 库非常糟糕。所以,我决定使用bouncycastle,它实际上允许我在Java 1.8 中使用ChaCha20,这很令人兴奋!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多