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

使用AES/GCM(Android 9)时,Java Cipher.update不会写入缓冲区,java,android,encryption,kotlin,aes-gcm,Java,Android,Encryption,Kotlin,Aes Gcm,我试图在Android上使用javax.crypto.Cipher,使用AES-GCM对数据流进行分块加密。据我所知,可以对多部分加密操作多次使用Cipher.update,并使用Cipher.doFinal完成。但是,当使用AES/GCM/NoPadding转换时,Cipher.update拒绝将数据输出到提供的缓冲区,并返回写入的0字节。在我调用.doFinal之前,缓冲区在密码中积累。这似乎也发生在CCM中(我假设其他经过身份验证的模式),但也适用于其他模式,如CBC 我认为GCM可以在加

我试图在Android上使用javax.crypto.Cipher,使用AES-GCM对数据流进行分块加密。据我所知,可以对多部分加密操作多次使用Cipher.update,并使用Cipher.doFinal完成。但是,当使用AES/GCM/NoPadding转换时,Cipher.update拒绝将数据输出到提供的缓冲区,并返回写入的0字节。在我调用.doFinal之前,缓冲区在密码中积累。这似乎也发生在CCM中(我假设其他经过身份验证的模式),但也适用于其他模式,如CBC

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

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

val secretKey=KeyGenerator.getInstance(“AES”)。运行{
初始化(256)
generateKey()
}
val iv=ByteArray(12)
SecureRandom().nextBytes(iv)
val cipher=cipher.getInstance(“AES/GCM/NoPadding”)
cipher.init(cipher.ENCRYPT_模式,secretKey,IvParameterSpec(iv))
//假设这是我想要读取和加密的文件
val inputBuffer=Random.nextBytes(1024000)
val outputBuffer=ByteArray(cipher.getOutputSize(512))
val read=cipher.update(inputBuffer,0,512,outputBuffer,0)
//^此时,read=0,outputBuffer为[0,0,0,…]
//将来对cipher.update和cipher.getOutputSize的调用表明
//内部缓冲区正在增长。但我想把它消耗掉
//输出缓冲区
// ...
cipher.doFinal(输出缓冲区,0)
//现在outputBuffer已填充
我想做的是从磁盘流式传输一个大文件,加密它并通过网络逐块发送,而不必将整个文件数据加载到内存中。我曾尝试使用CipherInputStream,但它也遇到同样的问题


AES/GCM有可能实现这一点吗?

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

import org.bouncycastle.jce.provider.BouncyCastleProvider;
导入org.conscrypt.conscrypt;
导入javax.crypto.Cipher;
导入javax.crypto.KeyGenerator;
导入javax.crypto.SecretKey;
导入javax.crypto.spec.gcmpareterspec;
导入java.security.GeneralSecurityException;
导入java.security.Provider;
导入java.security.SecureRandom;
导入java.security.security;
公共类密码问题1{
私有最终静态提供程序CONSCRYPT=CONSCRYPT.newProvider();
私有最终静态提供程序BC=新的BouncyCastleProvider();
公共静态void main(字符串[]args)引发GeneralSecurityException{
Security.addProvider(CONSCRYPT);
doExample();
}
私有静态void doExample()引发GeneralSecurityException{
最终SecureRandom SecureRandom=新SecureRandom();
{
//首先,尝试使用Conscrypt
KeyGenerator KeyGenerator=KeyGenerator.getInstance(“AES”);
init(256,secureRandom);
SecretKey aesKey=keyGenerator.generateKey();
字节[]明文=新字节[10000];//明文为全零
字节[]nonce=新字节[12];
secureRandom.nextBytes(nonce);
Cipher c=Cipher.getInstance(“AES/GCM/NoPadding”,CONSCRYPT);//显式指定提供程序
GCMParameterSpec=新的GCMParameterSpec(128,nonce);//标记长度以位为单位指定。
c、 init(Cipher.ENCRYPT_模式,aesKey,spec);
byte[]exputf=新字节[c.getOutputSize(512)];
int numProduced=c.update(纯文本,0,512,突发,0);
系统输出打印项次(numProduced);
final int finalProduced=c.doFinal(突发,numProduced);
系统输出打印(最终生成);
}
{
//接下来,尝试使用Bouncycastle
KeyGenerator KeyGenerator=KeyGenerator.getInstance(“AES”);
init(256,secureRandom);
SecretKey aesKey=keyGenerator.generateKey();
字节[]明文=新字节[10000];//明文为全零
字节[]nonce=新字节[12];
secureRandom.nextBytes(nonce);
Cipher c=Cipher.getInstance(“AES/GCM/NoPadding”,BC);//显式指定提供程序
GCMParameterSpec=新的GCMParameterSpec(128,nonce);//标记长度以位为单位指定。
c、 init(Cipher.ENCRYPT_模式,aesKey,spec);
byte[]exputf=新字节[c.getOutputSize(512)];
int numProduced=c.update(纯文本,0,512,突发,0);
系统输出打印项次(numProduced);
final int finalProduced=c.doFinal(突发,numProduced);
系统输出打印(最终生成);
}
}
}

我无法复制。调用cipher.update()后立即打印
read
的值将打印512。将数据块写入ByteArrayOutputStream并打印baos.toByteArray()的长度也会显示每次迭代的大小都在增加。但是我不得不删除IvParameterSpec,因为它会导致java.security.InvalidalgorithParameterException.Interest。。。也许我应该澄清一下这是在Android上的,如果这可能会改变它的实现方式。将更新我的问题以反映这一点。这是GCM模式的“功能”。解密时,明文被“禁止”,直到
doFinal