我有点被这个例外困住了:
java.lang.RuntimeException: error:0407806d:RSA routines:decrypt:DATA_LEN_NOT_EQUAL_TO_MOD_LEN
at com.android.org.conscrypt.NativeCrypto.RSA_private_decrypt(Native Method)
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:274)
at javax.crypto.Cipher.doFinal(Cipher.java:1440)
at javax.crypto.CipherInputStream.close(CipherInputStream.java:190)
...
当我关闭android棉花糖上的cipherinputstream时抛出。一切似乎都适用于早期的android版本。
什么意思?当它应该释放资源句柄(关闭)时,为什么它似乎要解密(调用
DATA_LEN_NOT_EQUAL_TO_MOD_LEN
)?更新:
我设法用一些测试代码重现了这个错误。它加密和解密
RSA_private_decrypt
。一次直接使用密码,一次通过CipherPutStream(就像在原始应用程序中那样)。一切都在android当我将显式密码
"foobar"
更改为genericRSA/ECB/PKCS1Padding
时,我能够让流代码在android 6上运行。但我敢打赌这是有原因的;)
static final String RSA_ALGO = "RSA/ECB/PKCS1Padding";
// static final String RSA_ALGO = "RSA";
private void _testCrypto2() throws Exception {
KeyPairGenerator keyGen;
KeyPair keys;
byte[] encrypted;
byte[] decrypted;
String input;
String output;
keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
keys = keyGen.generateKeyPair();
input = "foobar";
// Plain crypto.
encrypted = this.RSAEncrypt(input, keys.getPublic());
output = this.RSADecrypt(encrypted, keys.getPrivate());
// Streaming crypto.
encrypted = this.RSAEncryptStream(input, keys.getPublic());
output = this.RSADecryptStream(encrypted, keys.getPrivate());
}
public byte[] RSAEncrypt(final String plain, PublicKey _publicKey) throws Exception {
byte[] encryptedBytes;
Cipher cipher;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
encryptedBytes = cipher.doFinal(plain.getBytes());
return encryptedBytes;
}
public String RSADecrypt(final byte[] encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
byte[] decryptedBytes;
String decrypted;
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
decryptedBytes = cipher.doFinal(encryptedBytes);
decrypted = new String(decryptedBytes);
return decrypted;
}
public byte[] RSAEncryptStream(final String _plain, PublicKey _publicKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_plain.getBytes());
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.ENCRYPT_MODE, _publicKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return out.toByteArray();
}
public String RSADecryptStream(final byte[] _encryptedBytes, PrivateKey _privateKey) throws Exception {
Cipher cipher;
InputStream in;
ByteArrayOutputStream out;
int numBytes;
byte buffer[] = new byte[0xffff];
in = new ByteArrayInputStream(_encryptedBytes);
out = new ByteArrayOutputStream();
cipher = Cipher.getInstance(RSA_ALGO);
cipher.init(Cipher.DECRYPT_MODE, _privateKey);
try {
in = new CipherInputStream(in, cipher);
while ((numBytes = in.read(buffer)) != -1) {
out.write(buffer, 0, numBytes);
}
}
finally {
in.close();
}
return new String(out.toByteArray());
}
但是,看起来有两个修复方向:
消除rsa的流
删除显式rsa密码实例化
你怎么认为?
最佳答案
看起来android的默认安全提供者有一些变化。
Cipher c;
Provider p;
StringBuilder bldr;
c = Cipher.getInstance("RSA");
p = cipher.getProvider();
bldr = new StringBuilder();
bldr.append(_p.getName())
.append(" ").append(_p.getVersion())
.append(" (").append(_p.getInfo()).append(")");
Log.i("test", bldr.toString());
它似乎在所有测试过的Android版本上都使用了BouncyCastle版本(我测试到了2.3版本):
安卓5:
BC 1.5 (BouncyCastle Security Provider v1.50)
安卓6:
BC 1.52 (BouncyCastle Security Provider v1.52)
然而,“显式”密码改变了一些东西:
c = Cipher.getInstance("RSA/ECB/PKCS1Padding");
安卓4.1.2:
BC 1.46 (BouncyCastle Security Provider v1.46)
安卓4.4.2:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
安卓5.1.1:
AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
安卓6.0.1:
AndroidKeyStoreBCWorkaround 1.0 (Android KeyStore security provider to work around Bouncy Castle)
因此,最终的解决方案是将提供商显式地设置为BouncyCastle,它可以在所有经过测试的Android版本上工作,即使是流媒体:
Provider p;
Cipher c;
p = Security.getProvider("BC");
c = Cipher.getInstance("RSA/ECB/PKCS1Padding", p);