2016-03-08 4 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) 
... 

Это брошено, когда я закрываю CipherInputStream на Android Зефир. Кажется, что все работает с более ранними версиями Android.

Что означает DATA_LEN_NOT_EQUAL_TO_MOD_LEN означает? Почему он, кажется, расшифровывает (вызывает RSA_private_decrypt), когда он должен освобождать дескрипторы ресурсов (закрыть)?

UPDATE:

мне удалось воспроизвести ошибку с некоторым тестовым кодом. Он шифрует и расшифровывает "foobar". Один раз использовать шифр напрямую и один раз через CipherInputStream (как это сделано в оригинальном приложении).

Все работает на андроид < 6 и коды непотоковых даже работая на андроид 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 не подходит для шифрования/дешифрования потоков данных. Эта проблема может быть своего рода изменением для поддержки аутентифицированных шифров. Аутентифицированные шифры не играют хорошо с входными потоками, а IMHO использует испорченный 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 5: BC 1.5 (BouncyCastle Security Provider v1.50)
  • Android 6: BC 1.52 (BouncyCastle Security Provider v1.52)

Однако, что-то изменилось с "явной" шифра:

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

Так окончательное решение является установка поставщика явно BouncyCastle который работает на всех тестируемых андроид версии, даже с потоковым:

Provider p; 
Cipher c; 

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

Мне интересно проверить это, когда Я получаю мои руки на предварительный просмотр android7.

Смежные вопросы