Android RSA encryption from public string - java

I'm working on an android application where I'd like the user to be able to encrypt messages using other's public keys. The system would generate a public/private keypair and then messages can be sent to other users secretly.
I'm creating an Encryption class which will handle the encryption/decryption of messages. Unfortunately I'm having some problems.
In this method, I'd like to pass the user's secret (private key) as well as the message they want to encrypt. I'd like the secret to be user-defined (like "MySecretPassword").
public static void lock(String secret, String textToEncrypt) {
try {
//Convert the public key string into a key
byte[] encodedPublicKey = Base64.decode(secret.getBytes("utf-8"),Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publickey = keyFactory.generatePublic(spec); //Crash Here
PrivateKey privateKey = keyFactory.generatePrivate(spec);
//Encrypt Message
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publickey);
byte[] encryptedBytes = cipher.doFinal(textToEncrypt.getBytes());
Log.d(TAG,"Encrypted: "+new String(encryptedBytes));
} catch (Exception e) {
e.printStackTrace();
}
}
The exception is as follows:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0740b0:ASN.1 encoding routines:ASN1_get_object:TOO_LONG
What am I missing here? Am I missing something obvious or am I misunderstanding how these tools work? I've used this javascript library for public/private key encryption before and am trying to do something similar here. I'd appreciate it if somebody could point me in the right direction :)

A secret is not a public key.
You encrypt with the public key of the recipient. That value is public, which means that anybody can look it up. You need to get the value of the other party's public key and feed it into your code, not send in your own private key. The proper way to do this does not involve any secrets!
Normally one does not directly encrypt a message with RSA, instead they encrypt an AES key (or other symmetric key) with RSA and use the AES key to encrypt the message. If your messages are really short, you could use RSA directly, but it won't work for long messages.
Here are a couple links showing how to implement RSA on Android:
RSA using SpongyCastle
RSA encryption in Android and Java

Related

RSA public key to base64 String

I have written a socket server. The server-side is Java and the client-side is in Python. The server uses TCP communication and since TCP is not safe I use asymmetric encryption. The server has a private-public key pair and every time a client connects it sends the public key and requests clients public key.
In order to send the PublicKey object over TCP, I convert it to string with this function.
public String publicKeyToString(PublicKey publicKey){
byte[] pKBytes = publicKey.getEncoded(); // Get the public key to bytes
return Base64.getEncoder().encodeToString(pKBytes); // Convert the public key to String
}
On the other side to create a PublicKey object from string I use this function
public PublicKey publicKeyFromString(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = Base64.getDecoder().decode(key);
KeyFactory kf = KeyFactory.getInstance("RSA");
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
return kf.generatePublic(spec);
}
The above code works very well with java clients. Now I would like to do the same thing in python. I create the private-public key with this block of code
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
client_private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
client_public_key = client_private_key.public_key()
The problem now is that I don't know how to extract a string from the public key so I can send it to the server. Also, the string key will be decoded from public String publicKeyToString(PublicKey publicKey) so it should be encoded with base64.
"Specs":
Java 8 with java.security library
Python 3 with cryptography library
Encryption algorithm RSA
If you have a better library for python I am open to suggestions, java is somewhat fixed because by this point to much code is written.

Cipher decryption / encryption changing results

I'm reverse engineering some code which is decrypting data, hoping I'll be able to encrypt it back and obtain the same data it started with, for reasons that would make this question too long and off-topic.
public void Test() throws Exception {
String pk_enc = //...
String hashStr_64 = //...
byte[] hashStr_encrypted = Base64.decode(hashStr_64);
X509EncodedKeySpec e = new X509EncodedKeySpec(Base64.decode(pk_enc));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey RSApublicKey = (RSAPublicKey) keyFactory.generatePublic(e);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(2, RSApublicKey); // '2' means decrypt
byte[] hashStr_decrypted = cipher.doFinal(hashStr_encrypted);
String hashStr_result = new String(hashStr_decrypted);
// Now in reverse...
Cipher cipher1 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
// instantiating a new cipher or using the original one makes no difference
cipher1.init(1, RSApublicKey); // '1' means encrypt
byte[] hashStr_encrypted_reverse = cipher1.doFinal(hashStr_decrypted);
String hashStr_64_reverse = Base64.encode(hashStr_encrypted_reverse);
}
All the code before // Now in reverse... cannot be changed, but that doesn't mean it's impossible to convert hashStr_result back to hashStr_64, right?
However, the code I've wrote after, that should do just that, doesn't work.
hashStr_encrypted_reverse is different from hashStr_encrypted. Why is that and how can I fix it?
Another sign that something went wrong in the encryption is what happens if I try to decrypt again...
// Decrypt again
Cipher cipher2 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher2.init(2, RSApublicKey);
byte[] hashStr_decrypted_again = cipher.doFinal(hashStr_encrypted_reverse);
This throws:
javax.crypto.BadPaddingException
I don't really care, but maybe it could help answer the question.
Terminology will be confusing. There are 4 RSA operations, best described as: signing, verifying, encrypting, decrypting. Mapping these to a lower-level and using only the language of encryption and decryption, these map as follows:
sign-verify pair
signing -> encrypt with private key
verifying -> decrypt with public key
encrypt-decrypt pair
encrypting -> encrypt with public key
decrypting -> decrypt with private key.
As you can see, each pair of operations has the private key on one side and the public key on the other.
As #JamesKPolk said in his comment, this isn't how RSA works. RSA is an asymmetric encryption algorithm: there are two keys, public and private. A symmetric algorithm (e.g., AES) has a single key, which is used for both encryption and decryption, and that key must be kept safe, except to the sending and receiving parties.
Why asymmetric encryption?
You can encrypt with a public key (typically someone else's key that they've shared with you), and they must use their private key to decrypt it. Anyone can have the public key (that's why it's public), but it cannot be used to read the encrypted message. This is where you are having your problem.
Also, you can encrypt a message with a private key (typically your own), and anyone else can use your public key to decrypt it. This is how digital signatures are implemented: for example, you would encrypt a digest of a document, and anyone can verify your signature if they have your public key, but no one else could have signed it.

RSA Java encryption and Node.js decryption is not working

I have a system that requires a RSA keypair to be generated in javascript, have the public key then stored in a database at the server side (as a string), then the server side which is in Java will encrypt a string with the stored public key and send it to the client side which will decrypt the string with the private key.
I'm using a browsified version of node-rsa on my client browser.
First at the client i generate a keypair and export the keys, storing them as strings
var NodeRSA = require('node-rsa');
var key = new NodeRSA({b: 1024});
key.exportKey("pkcs8-private");
key.exportKey("pkcs8-public-pem");
The exported private key is stored at the client and the public at the server
Next i used java to encrypt a string with the public key received, so i parse the pkcs8 public key into a Java PublicKey object.
String pubKey = "<Retrieved pkcs8 public key>";
pubKey = pubKey.replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?)", "");
byte[] keyBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(spec);
And encrypt a text with it
byte[] cipherText;
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pk);
cipherText = cipher.doFinal("Hello World!".getBytes());
return Base64.encodeBase64String(cipherText);
Which works nicely and returns me a Base64 encoded encrypted string like this
WTS1J2f4w5icsUOCtulyHDaBmB5lN7D8mnj0QWMDBkUGiPHkM8nHVx9pd0MtbQAQNasQS2X8kisLMYyEMPasFZtDH0zX1e8lNYaW0xMKsg++ge87f+95nl+TmxDy6S1m7Ce/n0wXno+0MbSv8YsJtsUcAleyyfQX2bxqX8u7Gjs=
Then i try to decrypt it the string at the client side
First i reimport the stored keys in node-rsa
var NodeRSA = require('node-rsa');
var key = new NodeRSA();
key.importKey("<exported private key string>","pkcs8-private");
key.importKey("<exported public key string>","pkcs8-public-pem");
Then i try to decrypt the Base64 encoded encrypted string
key.decrypt("<Base64 Encoded Encrypted>", 'utf-8');
This is where the problem happens, javascript throws this error
Uncaught Error: Error during decryption (probably incorrect key). Original error: Error: Error decoding message, the lHash calculated from the label provided and the lHash in the encrypted data do not match.(…)
However i have tested that if i encrypt and decrypt the text just within javascript, it works just fine. This makes me think that it's some difference between the way i encrypted it at java and how it's done at javascript
Could anyone point out the mistake that I've made here please?
Oh i found the solution. It was a difference in the encryption method.
I just had to initialize Cipher with
Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
instead of
Cipher.getInstance("RSA");
to match node-rsa
Instead of change the encryption mode on my backend, I change it on front.
const rsa = new NodeRSA({ b: 2048 });
rsa.setOptions({ encryptionScheme: 'pkcs1' });
rsa.importKey(
'key',
'pkcs8-public',
);
and keep using Cipher.getInstance("RSA"); to encrypt and decrypt on backend.

IllegalBlockSizeException in RSA encryption on Android only

I'm currently working on an Java-Cyrpto-API which I want to include in an Android app later on. I tested every function of my Crypto-API and after all unit test succeeded I decided to include my jar into an Android project.
In the project I started generating a 4096-bit key pair in order to add it to an object in my class.
RSA.RSAKeyPair keyPair = null;
try {
keyPair = RSA.generateKeyPair(4096);
} catch (IOException e) {
e.printStackTrace();
}
self.setPrivateKey(keyPair.getPrivateKey());
self.setPublicKey(keyPair.getPublicKey());
Afterwards I call a function in my API which uses data from the "self" object to encrypt some data.
The app throws me the following exception when it tries to encrypt some data with RSA.
03-15 02:39:16.769 2394-2414/de.ifasec.instari E/Test﹕ javax.crypto.IllegalBlockSizeException: input must be under 512 bytes
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:245)
at javax.crypto.Cipher.doFinal(Cipher.java:1340)
at com.instari.encryption.RSA.encryptWithPublic(RSA.java:91)
I used Google to find out whats going wrong here an only found posts about invalid key lengths. I used the debugger to get all keys and values I generated in the app to test them directly in my API. My API tests succeeded without any errors.
Does Android have any restrictions or problems with RSA-Encryption?
Edit:
private static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";
This is my encryptWithPublic() Method:
// initialize
byte[] byteData = data.getBytes(); // convert string to byte array
PublicKey keyObject = extractPublicKey(publicKey);
// encrypt
Cipher cipher = null; // create conversion processing object
try {
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
return null;
}
cipher.init(Cipher.ENCRYPT_MODE, keyObject); // initialize object's mode and key
byte[] encryptedByteData = cipher.doFinal(byteData); // use object for encryption
return Base64.encode(encryptedByteData);
RSA.RSAKeyPair is a simple class i added to store the key:
public static class RSAKeyPair{
private String privateKey;
private String publicKey;
private RSAKeyPair(String privateKey, String publicKey) {
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public String getPublicKey() {
return publicKey;
}
}
The object self is similar to this. It just returns the keys I added before.
It seems you are just trying to encrypt too much data. The amount of data that can be encrypted using RSA with PKCS#1 padding is the key size (512 bytes) minus the padding overhead of 11 bytes, making for a total of 501 bytes. This is true for both Android, but also for Java SE.
With Java, the "ECB" part is a bit of a misnomer. RSA doesn't use any mode of operation, so it should have been "None". Only one block of plaintext will be encrypted. If you want to encrypt more, you can first generate a random AES key, use that to encrypt the message, and subsequently encrypt the random key using RSA. This is called hybrid encryption.

How to do decryption of a file in Java/Android which is encrypted using RSACertificate.der and RSAPrivatekey.p12

I am working on Android application for Encryption and decryption using RSA algorithm.
My intention is to decry-pt a file which is encrypted by server using RSACertificate.der and RSAPrivatekey.p12 files.
Now I have a Example.encriptedfile, RSACertificat.der and RSAPrivatekey.p12 files
I would like to decrypt the above example.encrypted file using above keys in JAVA
The implementation for getting Privatekey
And Decryption code using Cipher is
The file is example.encrypted file.
byte[] descryptedData = null;
try {
byte[] data = new byte[(int) file.length()];
new FileInputStream(file).read(data)
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(con.getAssets().open("rsaPrivate.p12"), "password".toCharArray());
pk = (PrivateKey)keystore.getKey("1", "password".toCharArray());
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, pk );
descryptedData = cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
}
return new String(descryptedData);
The exception getting for the fallowing code is
java.security.InvalidKeyException: unknown key type passed to RSA
at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineInit(CipherSpi.java:277)
at com.android.org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineInit(CipherSpi.java:381)
at javax.crypto.Cipher.init(Cipher.java:519)
at javax.crypto.Cipher.init(Cipher.java:479)
So can any one provide the suggestions and solutions to implement this
Thanks in advance.
But same
If it's encrypted with the private key it isn't encrypted at all, as anyone who can get the public key can decrypt it, which could be anybody.
If the intention was really to encrypt it, it should have been encrypted with the public key.
Possibly however the intention was to sign it, in which case what you have to do is verify it against the public key. For which you need the public key, which is in the certificate. So you load the certificate into a Certificate, using the java.security.cert API and then use a java.security.Signature object to verify the signature with, using the public key obtained from the Certificate.

Categories