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.
Related
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.)
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.
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().
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.
I am trying to encrypt a string using the BouncyCastle API in Android to send off to a server.
I have the public key in plaintext (in memory, not in the filesystem, of course! no need to yell at me, cryptographers ;) ) and I need to use this plaintext public key to encrypt a string to an RSA encrypted string.
This is my class:
public class RSAEncryptor {
//Get certificate from base64 string
public static X509Certificate getCertificateFromBase64String(String string)
throws CertificateException, javax.security.cert.CertificateException
{
if(string.equals("") || string == null) {
return null;
}
byte[] certBytes = Base64.decode(string.getBytes(), Base64.DEFAULT);
X509Certificate cert = X509Certificate.getInstance(certBytes);
return cert;
}
//Get public key from base64 encoded string
public static PublicKey getPublicKeyFromEncodedCertData(String encodedCertData)
throws CertificateException, javax.security.cert.CertificateException
{
if(encodedCertData == null || encodedCertData.equals("")) return null;
X509Certificate cert = getCertificateFromBase64String(encodedCertData);
if(cert == null) return null;
return cert.getPublicKey();
}
public static String rsaEncrypt(String plainText, String keyFromResources)
throws NoSuchAlgorithmException, InvalidKeySpecException,
IOException, NoSuchPaddingException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException //
{
if(plainText == null || plainText.equals("")) return null;
if(keyFromResources == null || keyFromResources.equals("")) return null;
byte[] encryptedBytes;
Cipher cipher = Cipher.getInstance("RSA");
PublicKey publicKey = null;
try {
publicKey = getPublicKeyFromEncodedCertData(keyFromResources);
}
catch(Exception ex) {
Logger.LogError("getPublicKeyFromEncodedCertData()", ex);
}
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainText.getBytes());
String encrypted = new String(encryptedBytes);
return encrypted;
}
}
I'm currently not getting the properly encrypted string back out, just a garbled mess like this:
��RB��%����I��Q��F*�bd[#�y�_H]T{KƾuTN�Q�
��U�f��]�S
�q|.t�t�9�Rˇ�����)��{�},ޱ�ª�ǥ#���#k=�WO���f�7t"yP�z�
(The <?>'s are null chars)
I should be getting back an alphanumeric string similar to this:
2+tSXez8JrAIX+VJ2Dy4IsA56XhWpTwF8X2yGGaI6novucXknwykDyqJZICpmYcqx75qBRgxwrW2kY9LmQR2xU17PLqTukAu2Bna8WXYTmJJQ7CWsN3SdABlETRfsYA+g3A2rO2Qp6aR9OCBcFVJpnZJjb9kaOUj5Pcj0tNPFdM= (changed obviously from the actual response :D)
I'd appreciate any help!
Thanks!
Has anyone done this before? I'd love any suggestions you have as to how to fix this.
Problems in the code:
String encrypted = new String(encryptedBytes);
Bad idea! Cipher#doFinal returns a byte[] for a good reason. It looks like random data - turning this into a String will make a mess for sure, because the platform default encoding (UTF-8 in most cases) will interpret the random bytes wrong almost with certainty. So if you want to have a String from encrypted data, then you should Base64- or Hex-encode the byte array.
From what you said you were expecting I would say you want Base64-encoded data, so you should Base64-encode your Cipher's output.
Then, encrypting a string (is this real, human-readable text?) is also less than optimal. Highly vulnerable, the reduced entropy plus the characteristics of the ECB mode (this is used by the RSA cipher) lower the security of your solution drastically.
A plain RSA cipher should never be used to encrypt data that is larger than one block (i.e. larger than the key size of your RSA key) and also only if the data is cryptographically secure random. In 99% of all cases this is only given for symmetric keys used for symmetric Ciphers such as AES etc.
Use RSA for nothing else than symmetric key wrapping and digital signatures, in all remaining cases where you want to actually encrypt sensitive data, you use a symmetric cipher, AES is a good choice - 128 or 256 bits doesn't really matter.
The workflow would look like this:
Generate a symmetric key for AES (16/32 bytes if you use AES-128/256). Now you would RSA-encrypt this symmetric key and nothing else using the server's public key and send the key to the server, then encrypt your private data using AES and the symmetric key, the server would decrypt the symmetric key using its private RSA key and decrypt the packets you send to it.
Use TLS:
Note my use of would. The above is only part of the story. What you just invented there is a Key Transport Protocol. And unless you are designing those for a living chances are high that you won't get this secure on your first try (as in Man-In-The-Middle-Attacks, Reflection Attacks, Replay Attacks, ...).
That's why in my opinion the only widely available secure option to set up a secure channel between client device and server is to use TLS (the former SSL). The protocol was designed specifically for the purpose of exchanging private data with one-way (server only) or two-way authentication (client and server) (authentication is the part where you would use RSA for in your case - configuring the "server certificate").
It has been hardened for years and revised a couple of times to withstand all known attacks on such protocols. And I know, there are messages every other day about how "SSL" has been broken by this or that person, but still, if you set it up carefully it's as secure as it gets for mere mortals without extensive experience in protocol design.
And the nice thing is you only have to configure it on the server (which is quite easy compared to inventing a protocol from scratch) to be able to use fully encrypted, secure communication between both client and server. If you set up a certificate/key pair on the server that was bought from a "public CA" then using TSL is completely transparent to your clients - they merely change their access URL from 'http' to 'https' - the server's certificate will be automatically trusted by being able to identify it in a certificate path that leads to one of the root certificates kept in Java's default trust store cacerts.