添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

使用AES/GCM时,Java Cipher.update不能写入缓冲区(Android 9)。

3 人关注

我试图在Android上使用javax.crypto.Cipher,用AES-GCM对一个数据流进行分块加密。据我所知,人们可以多次使用Cipher.update来进行多部分的加密操作,并用Cipher.doFinal来最终完成。然而,当使用AES/GCM/NoPadding转换时,Cipher.update拒绝向提供的缓冲区输出数据,并返回0字节的写入量。缓冲区在Cipher内部不断增加,直到我调用.doFinal。这似乎也发生在CCM(和我假设的其他认证模式),但对于其他模式,如CBC的工作。

我想GCM可以在加密的同时计算认证标签,所以我不清楚为什么不允许我在Cipher中消耗缓冲区。

我做了一个例子,只有一个对.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,但它也有同样的问题。

用AES/GCM是否可以这样做?

5 个评论
我无法重现。在调用cipher.update()后立即打印 read 的值,打印出512。将大块数据写入ByteArrayOutputStream,并打印baos.toByteArray()的长度,也显示出每次迭代时的大小都在增加。但我不得不删除IvParameterSpec,因为它导致了java.security.InvalidAlgorithmParameterException。
Will
有趣的是......也许我应该澄清这是在安卓系统上,如果这可能会对它的实现方式产生影响。我将更新我的问题以反映这一点。
这是GCM模式的一个 "特点"。在解密时,明文被 "禁运",直到 doFinal 被调用并且标签可以被验证。原因很简单:如果 update() 一产生就把明文交还给你,而最后标签未能验证,那么你拿到的明文就会无效。 doFinal() 将没有办法召回有缺陷的明文,而你也没有办法召回你刚刚基于该有缺陷的明文发射的核导弹。
不幸的是,这是图书馆设计师的选择。请看Maarten在这篇文章中的评论 答案 .如果你想的话,你可以将你的数据进行分割和连锁。
我将继续前进,并在以下网站开辟一个问题 conscrypt issues .但几乎可以肯定的是,它将被忽略。同时,我怀疑不同的提供者,如bouncycastle会产生预期的结果。然而,默认的Conscrypt提供者的总吞吐量可能仍然会更快,因为它使用了本地代码。这是一个你必须决定的权衡因素。
java
android
encryption
kotlin
aes-gcm
Will
Will
发布于 2019-08-07
2 个回答
President James K. Polk
President James K. Polk
发布于 2021-08-17
已采纳
0 人赞同

这是由Android现在默认使用的Conscrypt提供者的一个限制造成的。下面是我正在运行的一个代码例子 not 在我的Mac上,我明确使用了Conscrypt提供者,接下来使用Bouncycastle(BC)提供者来显示差异。因此,一个解决方法是将BC提供者添加到你的Android项目中,并在调用 Cipher.getInstance() 时明确指定它。当然,这是有代价的。虽然BC提供者会在每次调用 update() 时向你返回密码文本,但由于Conscrypt使用本地库,而BC是纯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);