Java RSA encryption keeps generating output that cannot be decrypted? - java

I'm trying my darnedest to create a program that will generate a private/public RSA key set and use it to send messages that are secure from end to end. I am using the RSA private/public key pair to securely transmit an AES key, that will be used to send the messages.
When I use a 1024-bit key pair, the encrypted session key is 128 bytes. But attempting to decrypt it with the RSA private key, it complains that it can only decrypt 117 bytes or less.
When I use a 2048-bit key pair, the encrypted session key is 256 bytes (but it must be 245 or less), etc.
The result is always the same: I cannot pass a valid passphrase back and forth. :(
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
SecretKey sessionKey = keyGen.generateKey();
// Encrypt the session key with the RSA public key
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.ENCRYPT_MODE, keyPair.getPublic());
byte[] encryptedSessionKey = rsaCipher.doFinal(sessionKey.getEncoded());
// Simulating other end user: Receive encrypted session key,
// decrypt it using your private key.
Cipher rsaDecryptCipher = Cipher.getInstance("RSA");
rsaDecryptCipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
byte[] decryptedSessionKeyBytes = rsaCipher.doFinal(encryptedSessionKey);
System.out.println("Decrypted session key bytes");
System.out.println(decryptedSessionKeyBytes);
Edit: It looks like the key that is generated is being padded at some time or another, but I don't know how to stop or subvert it. I has teh dumb when it comes to encryption... up until now I was relying on openssl_seal.

You are using rsaDecryptCipher it for everything except the actual decryption.
It would be a good idea to always specify the mode of operation and padding mode, e.g. "RSA/ECB/OAEPWithSHA1AndMGF1Padding" for your Cipher instances.

Related

Java IllegalBlockSizeException for data longer than 512 bytes

I have written a little chat and messages are objects like
{type="message",sender="userA",content="plaintextmessage",recipient="userB"}
that are sent to the server who spread it to all enrolled users. I want to encrypt the plaintextmessage-part that the message object looks like
{type="message",sender="userA",content="bHJg67&GghjGZuf/zdu=",recipient="userB"}
I have build my RSA keypair on both - server and client.
KeyPair keyPair = buildKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
Then i encode the servers public key to a byte array and this array to a base64 encoded string and send it to the client.
byte[] encodedPublicKey = publicKey.getEncoded();
String b64PublicKey = Base64.getEncoder().encodeToString(encodedPublicKey);
Both, client and server, have implemented the functions
public static byte[] encrypt(PublicKey othersPubKey, String message) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, othersPubKey);
return cipher.doFinal(message.getBytes());
}
public static byte[] decrypt(PrivateKey privateKey, byte [] encrypted) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encrypted);
}
When i try to encrypt a message on the client, send it to the server and decrypt it there i get the error
javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes
Does that means that this encryption method is nout suitable for my messages? I found Java/JCE: Decrypting "long" message encrypted with RSA. Is that my new goal?
Yes, it is called a hybrid cryptosystem. Even then, you may want to understand about the Bleichenbacher attack, the use of authenticated encryption, how to gain trust in a public key etc.
So your goal is either to study the field in much more detail or to learn a lot less about deploying TLS 1.2 or 1.3. Because it takes a lot of details to implement transport mode security.
If you want to continue, at least take a look at RSA in OAEP mode and AES in GCM mode.

How to wrap secret key from KeyStore by public key?

I'm interested in Android programming. I'm working with symmetric and asymmetric encryption and I wanted to wrap secret key from Android Key Store by public key but I failed. I'm able to get secret key from Key Store but I can't wrap it.
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.WRAP_MODE, publicKey);
SecretKey secret = (SecretKey) keyStore.getKey("key",null);
byte[] encrypted = cipher.wrap(secret);
Error:
InvalidKeyException:Cannot wrap key,null encoding
I tried to work with this key pair and secret key in code above - I successfully encrypted and decrypted some String by symmetric and asymmetric encryption.
What's interesting? I tried to generate new secret key by KeyGenerator and wrapping was successful. I'm little bit confused. I can't wrap it by secret key, which is loaded from KeyStore.
Could you help me please?
I believe that you got this error while using BouncyCastle since I also just received this error.
I found that BouncyCastle is calling the following code which throws the error:
byte[] encoded = key.getEncoded();
if (encoded == null) {
throw new InvalidKeyException("Cannot wrap key, null encoding.");
}
The Android KeyStore won't allow you to access the value of the private keys and will instead return null when you try to do so, which is why the above error is thrown.

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.
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
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.
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.)

How to encrypt AES keys using RSA without running into "javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes"

I have to build a simple authorization server for a project. The server has to distribute AES keys to allow its clients to communicate with each other.
When encrypting the AES key using RSA, I run into this error: "javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes".
Which is weird, since the lenght of my AES key is 128 bits = 16 bytes.
Here is the code generating the error:
private void createAndSendAES() throws NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, IOException, InvalidKeyException, BadPaddingException {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
this.AESBlackboardKey = keyGen.generateKey(); // My AES key
byte[] raw = AESBlackboardKey.getEncoded();
System.out.println(raw.length); // Prints 16
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.clientPubKey);
SealedObject encryptedAESBlackboardKey = new SealedObject(this.AESBlackboardKey, cipher); // ERROR HERE
ObjectOutputStream outO = new ObjectOutputStream(this.clientSocket.getOutputStream());
outO.writeObject(encryptedAESBlackboardKey); //Transmitting the key over socket link
outO.flush();
System.out.println("AS: Blackboard AES key sent.");
}
Does someone know how the encryption of a 16 bytes long AES key makes me run into this kind of error and how to avoid it ?
Thanks in advance !
The reason why you are getting the error is that the whole object is saved, not just the 16 bytes that make up the key. So you will e.g. have the full class name in there, the serial number of the class etcetera.
If you want to keep using SealedObject then I would suggest encryption with a new random AES key and "AES/CBC/PKCS5Padding". You can then encrypt that key using the RSA algorithm (be sure to specify it fully, e.g. "RSA/NONE/OAEPPadding" or "RSA/NONE/PKCS1Padding") simply by using Cipher.doFinal().
You can also directly encrypt the generated data that way.
Another method is to simply increase the RSA key size; the RSA key size of 1024 is increasingly under threat, try to use a key size of 2048 at the bare minimum (allowing 256 - 11 = 245 bytes of storage).
Note that you can retrieve the 16 bytes from a previously created AES key by using key.getEncoded().

ArrayIndexOutOfBoundsException : too much data for RSA block

I have some problem with my android application. I am trying to an app related with RSA encryption/decryption.this is my problem:
I can encrypt short sentences clearly, but when i try to decrypt this message to orginal text I give an error ("too much data for RSA block"). And also if I want to encrypt a long sentences i have same error.I had some search for this problem, and found some solution in this sites:
Site 1
Site 2
Site 3
But i dont understand anything, these solutions are so complicated.How can i fixed this problem, Can anyone give me a more simple solution? Thank you.
EDİT:
These are the code blocks that i use for this project.
public String RSAEncrypt(String plain) throws NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException, UnsupportedEncodingException {
publicKey = getPublicKey();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] cipherData = cipher.doFinal(plain.getBytes());
return Base64.encodeToString(cipherData, Base64.DEFAULT);
}
public String RSADecrypt(byte[] encryptedBytes) throws NoSuchAlgorithmException, NoSuchPaddingException,InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException, UnsupportedEncodingException {
privateKey = getPrivateKey();
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] cipherData = cipher.doFinal(encryptedBytes);
return Base64.encodeToString(cipherData, Base64.DEFAULT);
}
RSA can only encrypt messages that are several bytes shorter than the modulus of the key pair. The extra bytes are for padding, and the exact number depends on the padding scheme you are using.
RSA is for key transport, not data encryption. If you have a long message, encrypt it with AES, using a random key. Then encrypt the AES key with RSA, using the public key of the message recipient. You should be using the Cipher class's wrap() and unwrap() methods.
This is how PGP, S/MIME, TLS (roughly), and any other correctly designed RSA encryption schemes work.
In my use case, I need to encrypt some request data to server, from my app.
If I encrypt the AES key with RSA, that's kind of meaningless because the point of using RSA instead of AES, is to prevent reverse-engineering to get the AES key (versus RSA needs a private key stored in server for decryption). Since the AES key needs to be stored in the app locally, I cannot use the approach as suggested by erickson.
Instead, I split the long string into List<String> (Each length suggested by server provider); and encrypt each String.
After that, the List<String> is passed to server in a json array, and let server decrypt each and re-join them.
Of course, this approach needs your server's support, and can potentially has performance issue.

Categories

Resources