关闭 CipherInputStream 时出现运行时异常

lup*_*upz 3 android openssl cryptography android-6.0-marshmallow

我有点被这个异常困住了:

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)
 ...
Run Code Online (Sandbox Code Playgroud)

当我关闭 Android Marshmallow 上的 CipherInputStream 时会抛出它。一切似乎都适用于早期的 Android 版本。

什么DATA_LEN_NOT_EQUAL_TO_MOD_LEN意思?为什么RSA_private_decrypt它应该在应该释放资源句柄(关闭)时解密(调用)?

更新:

我设法用一些测试代码重现了错误。它加密和解密"foobar"。一次直接使用密码,一次通过 CipherInputStream(就像在原始应用程序中完成的那样)。

一切都在 android < 6 上工作,非流代码甚至在 android 6 上工作。当我将显式密码更改RSA/ECB/PKCS1Padding为 generic时,我能够让流代码在 android 6 上工作RSA。但我敢打赌它的存在是有原因的;)

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());
}
Run Code Online (Sandbox Code Playgroud)

但是,看起来修复有两个方向:

  • 摆脱 RSA 的流媒体
  • 删除显式 RSA 密码实例化

你怎么认为?

lup*_*upz 6

看起来 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());
Run Code Online (Sandbox Code Playgroud)

它似乎在所有经过测试的 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");
Run Code Online (Sandbox Code Playgroud)
  • 安卓 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);
Run Code Online (Sandbox Code Playgroud)