本文介绍了使用AES/GCM(Android 9)时Java Cipher.update不写入缓冲区的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Android上使用javax.crypto.Cipher来通过AES-GCM对数据流进行大块加密.据我了解,一个人可以多次使用Cipher.update进行多部分加密操作,并通过Cipher.doFinal完成.但是,当使用AES/GCM/NoPadding转换时,Cipher.update拒绝将数据输出到提供的缓冲区,并返回写入的0字节.直到我调用.doFinal为止,缓冲区才会在Cipher中建立.CCM似乎也发生了这种情况(我假设采用其他身份验证模式),但适用于CBC等其他模式.

I'm trying to use javax.crypto.Cipher on Android to encrypt a stream of data in chunks using AES-GCM. As I understand, one can use Cipher.update multiple times for a multi-part encryption operation, and finalize with Cipher.doFinal. However when using the AES/GCM/NoPadding transformation, Cipher.update refuses to output data to the provided buffer, and returns 0 bytes written. The buffer builds up inside the Cipher until I call .doFinal. This also appears to happen with CCM (and I assume other authenticated modes), but works for other modes like CBC.

我认为GCM可以在加密时计算身份验证标签,因此我不确定为什么不允许使用Cipher中的缓冲区.

I figured GCM can compute the authentication tag while encrypting, so I'm not sure why I'm not allowed to consume the buffer in the Cipher.

我举了一个例子,只调用了.update:(kotlin)

I've made an example with just one call to .update: (kotlin)

val secretKey = KeyGenerator.getInstance("AES").run {
    init(256)
    generateKey()
}

val iv = ByteArray(12)
SecureRandom().nextBytes(iv)

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))

// Pretend this is some file I want to read and encrypt
val inputBuffer = Random.nextBytes(1024000)

val outputBuffer = ByteArray(cipher.getOutputSize(512))

val read = cipher.update(inputBuffer, 0, 512, outputBuffer, 0)
//   ^  at this point, read = 0 and outputBuffer is [0, 0, 0, ...]
// Future calls to cipher.update and cipher.getOutputSize indicate that
// the internal buffer is growing. But I would like to consume it through
// outputBuffer

// ...

cipher.doFinal(outputBuffer, 0)
// Now outputBuffer is populated

我想做的是从磁盘流式传输一个大文件,对其进行加密并通过网络逐块发送,而不必将整个文件数据加载到内存中.我尝试使用CipherInputStream,但是它也遇到了同样的问题.

What I would like to do is stream a large file from disk, encrypt it and send it over the network chunk by chunk, without having to load the entire file data into memory. I've tried to use CipherInputStream but it suffers from the same problem.

使用AES/GCM可以吗?

Is this possible with AES/GCM?

推荐答案

这是由Android现在默认使用的Conscrypt提供程序中的限制引起的.这是一个代码示例,我不是在Android上运行,而是在Mac上运行,显式使用Conscrypt提供程序,然后使用Bouncycastle(BC)提供程序来显示差异.因此,一种解决方法是将BC提供程序添加到您的Android项目中,并在调用 Cipher.getInstance()时显式指定它.当然,需要进行权衡.尽管BC提供程序会在每次调用 update()时将密文返回给您,但由于Conscrypt使用本机库且BC是纯Java,因此总体吞吐量可能会大大降低.

This is caused by a limitation in the Conscrypt provider that Android now uses by default. Here is an example of code that I'm running not an Android but rather on my Mac that explicitly uses the Conscrypt provider, and next uses the Bouncycastle (BC) provider to show the difference. Therefore a work around is to add the BC provider to your Android project and specify it explicitly when calling Cipher.getInstance(). There is a tradeoff, of course. While the BC provider will return ciphertext to you for every call to update() the overall throughput will probably be substantially less since Conscrypt uses native libraries and BC is pure Java.

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.conscrypt.Conscrypt;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.GeneralSecurityException;
import java.security.Provider;
import java.security.SecureRandom;
import java.security.Security;

public class ConscryptIssue1 {

    private final static Provider CONSCRYPT = Conscrypt.newProvider();
    private final static Provider BC = new BouncyCastleProvider();

    public static void main(String[] args) throws GeneralSecurityException {
        Security.addProvider(CONSCRYPT);
        doExample();
    }

    private static void doExample() throws GeneralSecurityException {
        final SecureRandom secureRandom = new SecureRandom();
        {
            // first, try with Conscrypt
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256, secureRandom);
            SecretKey aesKey = keyGenerator.generateKey();
            byte[] plaintext = new byte[10000]; // plaintext is all zeros
            byte[] nonce = new byte[12];
            secureRandom.nextBytes(nonce);
            Cipher c = Cipher.getInstance("AES/GCM/NoPadding", CONSCRYPT);// specify the provider explicitly
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);// tag length is specified in bits.
            c.init(Cipher.ENCRYPT_MODE, aesKey, spec);
            byte[] outBuf = new byte[c.getOutputSize(512)];
            int numProduced = c.update(plaintext, 0, 512, outBuf, 0);
            System.out.println(numProduced);
            final int finalProduced = c.doFinal(outBuf, numProduced);
            System.out.println(finalProduced);
        }

        {
            // Next, try with Bouncycastle
            KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
            keyGenerator.init(256, secureRandom);
            SecretKey aesKey = keyGenerator.generateKey();
            byte[] plaintext = new byte[10000]; // plaintext is all zeros
            byte[] nonce = new byte[12];
            secureRandom.nextBytes(nonce);
            Cipher c = Cipher.getInstance("AES/GCM/NoPadding", BC);// specify the provider explicitly
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce);// tag length is specified in bits.
            c.init(Cipher.ENCRYPT_MODE, aesKey, spec);
            byte[] outBuf = new byte[c.getOutputSize(512)];
            int numProduced = c.update(plaintext, 0, 512, outBuf, 0);
            System.out.println(numProduced);
            final int finalProduced = c.doFinal(outBuf, numProduced);
            System.out.println(finalProduced);
        }

    }
}

这篇关于使用AES/GCM(Android 9)时Java Cipher.update不写入缓冲区的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-27 21:33