2016-03-08 157 views
2

時那種我卡住了與此異常:RuntimeException的收盤CipherInputStream

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)何時應該釋放資源句柄(關閉)?

更新:

我設法用一些測試代碼重現錯誤。它加密和解密"foobar"。有一次直接使用密碼,一次使用CipherInputStream(就像在原始應用程序中完成的那樣)。

一切工作在Android < 6和非流代碼,即使是在Android 6 工作,我能得到的流碼,即可在Android 6工作時,我改變了明確的密碼RSA/ECB/PKCS1Padding仿製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()); 
} 

但是,它看起來像有兩個方向的修正是:

  • 擺脫流爲RSA
  • 刪除顯式RSA密碼實例化

您認爲如何?

+0

更重要的是,您爲什麼要將RSA密碼和InputStream一起使用? RSA不適合加密/解密數據流。這個問題可能是支持經過身份驗證的密碼的某種更改。經過驗證的密碼對輸入流並不滿意,恕我直言,使用了一個拙劣的API。從流中讀取數據後,可能需要驗證認證標記和/或填充。這將是一個很好的理由,稱'doFinal' –

+0

@MaartenBodewes謝謝你。它只是一個帶AES加密密鑰的字節流,因此它不是數據量的問題。但暗示可能是正確的。當只有一個「單詞」解密時,沒有用的流。我用一些測試代碼更新答案。 – lupz

回答

4

它看起來像是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()); 

看來使用一個版本BouncyCastle的對所有測試的Android版本(我測試到2.3):

  • Android的5BC 1.5 (BouncyCastle Security Provider v1.50)
  • 的Android 6BC 1.52 (BouncyCastle Security Provider v1.52)

然而,一些與 「顯式」 密碼修改:

c = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 
  • 的Android 4.1.2BC 1.46 (BouncyCastle Security Provider v1.46)
  • 的是Android 4.4.2AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
  • 的Android 5.1.1AndroidOpenSSL 1.0 (Android's OpenSSL-backed security provider)
  • Android 6.0。1AndroidKeyStoreBCWorkaround 1.0 (Android KeyStore security provider to work around Bouncy Castle)

所以最終的解決方案提供商設定明確到正在測試所有的Android版本,甚至流BouncyCastle的:

Provider p; 
Cipher c; 

p = Security.getProvider("BC"); 
c = Cipher.getInstance("RSA/ECB/PKCS1Padding", p); 

我很好奇,當測試這個我得到了我的手在android7預覽。