Paghillect
Paghillect

Reputation: 848

Java decrypting RSA encrypted data ArrayIndexOutOfBoundsException: too much data for RSA block

I'm encrypting some data on a Phoenix webserver:

private_key = ExPublicKey.load!("private.pem")
token = %{username: user.username, mobile_phone: user.mobile_phone, email: user.email}
payload = Poison.encode!(token)
{:ok, signature} = ExPublicKey.encrypt_private(payload, private_key)

And decrypting it on the Java (actually Android) client as follows:

try {
    byte[] keyBytes = Base64.decode(Constants.RSA_PUBLIC_KEY.getBytes(), Base64.DEFAULT);
    X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PublicKey publicKey = keyFactory.generatePublic(encodedKeySpec) ;
    Cipher cipher = Cipher.getInstance("RSA") ;
    cipher.init(Cipher.DECRYPT_MODE, publicKey) ;
    //
    Log.e(DEBUG_TAG, jwt) ; // received token
    String payload = new String(Base64.decode(jwt, Base64.DEFAULT), "UTF-8") ; // java does UTF16, elixir does UTF8
    Log.e(DEBUG_TAG, payload) ; // base64 decoded token
    byte[] cipherText = cipher.doFinal(payload.getBytes("UTF-8")) ; // decrypt
    String token = new String(Base64.decode(cipherText, Base64.URL_SAFE), "UTF-8") ; // cipher text is urlencoded
    Log.e(DEBUG_TAG, token) ;
    return null ;
} catch (Exception e) {
    e.printStackTrace();
}

There are no exceptions on the Phoenix side but trying to decrypt the token on java results in the exception:

java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block
    at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(CipherSpi.java:459)
    at javax.crypto.Cipher.doFinal(Cipher.java:1502

If the input is too large for the RSA modulus it should have resulted in error on the webserver. So I'm wondering what is actually wrong.

UPDATE: seems like there was an issue with library. The output produced by signing the SHA256 digest of some data returns 344 bytes, whereas its supposed to be 256 bytes for the key length used. Reverted to using Erlang's public_key module and it works fine now.

Upvotes: 0

Views: 695

Answers (3)

Maarten Bodewes
Maarten Bodewes

Reputation: 94058

Signing is not the same as encrypting using a private key. Although both would be using modular exponentiation with the private exponent signing and encryption use different padding methods. More information here. You should basically not see hashing and signing as separate operations: the hashing is part of the signature generation and verification.

The reason why your code failed is however different: the signature is likely encoded using base64. Base64 will generate an output size of ceiling(256/3)×4. This of course equals 344 characters / bytes. So you first would have to decode the result before decrypting it.

Upvotes: 1

pedrofb
pedrofb

Reputation: 39281

Is not clear the real purpose and that makes things difficult, but if you are trying to issue JSON Web Tokens, as it seems, your implementation is completely wrong

  • JWT is digitally signed, not encrypted

  • encrypt with private key != Digital signature

  • you are "decrypting" the entire token instead of verifying the signature, which should be the last part of a JSON Web Token like this hhhh.pppp.ssss.

@zaph described the error, but it would not occur if you use digital signature. It is not possible to fix your code so consider to re-implement it

Upvotes: 1

Akash
Akash

Reputation: 593

The solution to this problem is to use hybrid encryption. Namely, this involves using RSA to asymmetrically encrypt a symmetric key.

Randomly generate a symmetric encryption (say AES) key and encrypt the plaintext message with it. Then, encrypt the symmetric key with RSA. Transmit both the symmetrically encrypted text as well as the asymmetrically encrypted symmetric key.

The receiver can then decrypt the RSA block, which will yield the symmetric key, allowing the symmetrically encrypted text to be decrypted.

This can be shown more formally as the following. Let MM be the plaintext, KAESKAES be the randomly chosen AES key, and KPuKPu be the receiver's public RSA key you already have.

C1=EAES(M,KAES)
C1=EAES(M,KAES)
C2=ERSA(KAES,KPu)
C2=ERSA(KAES,KPu)
Then, send both C1C1 and C2C2.

Let KPrKPr be the receiver's private RSA key. The receiver can then recover MM as

KAES=DRSA(C2,KPr)
KAES=DRSA(C2,KPr)
M=DAES(C1,KAES)
M=DAES(C1,KAES)

(To allow streaming decryption or large messages, you would usually send C2C2 first and then (the much larger) C1C1.)

Upvotes: 0

Related Questions