Thursday, November 21
Home>>Tech>>How to encrypt a string in Java using RSA and decrypt it in Python
encryption
Tech

How to encrypt a string in Java using RSA and decrypt it in Python

Recently at work, I was tasked to write a Java program which would encrypt a sensitive string using the RSA encryption algorithm. The encrypted string would then be passed on to a client over public internet. The client would then use the private key to decrypt the message. But the client is written in Python. So I have to make sure the encryption and decryption wok as expected. And as always, I wrote POCs for both. And here, I’m going to document that.

Creating the key pair

Before we can start the encryption, we need to have a key pair. A key pair will have a public key and a private key. The public key, as the name suggests, is public. You can share it with anybody who wishes to send you an encrypted text. They will encrypt the original text using this public key, and send over the encrypted text to you. You can then use the private key that only you have to decrypt the text. You’ll get the original message back this way.

So to start the process, we need to first generate the key pair. For this, we’ll use the very popular tool, openssh. You’ll need a terminal for this though. So open up your terminal and run the following command:

openssl genrsa -out privateKey.pem 2048

The command above will create a private key file – privateKey.pem. You can rename this to whatever you want, or you can change the value of the -out option in the command to create the file with any name you want.

Once you have this private key, we need to create a public key that goes with this. For this, we’ll run another command (given below), which will generate a public key. Again, you can change the value of the option -out to name the file whatever you want.

openssl rsa -in privateKey.pem -outform PEM -pubout -out public.pem

That’s it. You now have a key pair which we can use in our code.

Encryption with Java

Now that we have a key pair, let’s start encrypting our message. I have selected a very specific message to encrypt, and it makes a lot of sense:

String dataToBeEncrypted = "Some random words in no particular order.";

As you can see, I can’t really send out this very sensitive message over public internet. So let’s encrypt it. For that though, we need to first convert this string into a byte array:

byte[] bytesToBeEncrypted = dataToBeEncrypted.getBytes();

Next, we need to read the public key file into our Java code. We have to clean up the public key data though. Let’s see why that is. If you open up your public key file (cat it or open it in a text editor), you’ll see something like this:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut9/U5lR6UN/02YX79qv
iuKd2AQwEBiJMt15djesw6wgR/1jWJr/ZUM+XPIVkshHoPkhh2JhnqvEZt3VEYeY
xy88xRksZqqEmgCwEX4gVsAWrGCTJ7U+LyuSYpavbHGcUkA4rIh9XCkgphvXYod2
cnyU0XQJ1jRLvTD4EozTtyA1wKRxtATj/2o+swH3mnEW1y4weEoLmfcJ844tQU/l
3DIxQh+XWhzdsqo8kX+Za8RAFbH2xbK+yG6U3it5TrSwmsSSUh2ZGlcGiN76C/42
6rTWS0lj5kYEUYKqON782ui8K2hGj9ylpL6lohosH8lsTKZvRK0PCs698QKrlc/M
bwIDAQAB
-----END PUBLIC KEY-----

As you can see, there’s some text in there, and some new line characters, and some dashes. You need to remove all that and have only the key. For that, once we have the file’s content into a variable, we’ll replace all the unwanted text with some empty strings. For that, we’ll use the following code snippet:

public static final String NEW_LINE_CHARACTER = "\n";
public static final String PUBLIC_KEY_START_KEY_STRING = "-----BEGIN PUBLIC KEY-----";
public static final String PUBLIC_KEY_END_KEY_STRING = "-----END PUBLIC KEY-----";
public static final String EMPTY_STRING = "";

File keyFile = new File(publicKeyPath);
byte[] publicKey = Files.readAllBytes(keyFile.toPath());

String keyString = new String(publicKey);
keyString = keyString.replaceAll(NEW_LINE_CHARACTER, EMPTY_STRING)
    .replaceAll(PUBLIC_KEY_START_KEY_STRING, EMPTY_STRING)
    .replaceAll(PUBLIC_KEY_END_KEY_STRING, EMPTY_STRING);

publicKey = keyString.getBytes();

If you check the value of the publicKey variable now, you should see something like this:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut9/U5lR6UN/02YX79qviuKd2AQwEBiJMt15djesw6wgR/1jWJr/ZUM+XPIVkshHoPkhh2JhnqvEZt3VEYeYxy88xRksZqqEmgCwEX4gVsAWrGCTJ7U+LyuSYpavbHGcUkA4rIh9XCkgphvXYod2cnyU0XQJ1jRLvTD4EozTtyA1wKRxtATj/2o+swH3mnEW1y4weEoLmfcJ844tQU/l3DIxQh+XWhzdsqo8kX+Za8RAFbH2xbK+yG6U3it5TrSwmsSSUh2ZGlcGiN76C/426rTWS0lj5kYEUYKqON782ui8K2hGj9ylpL6lohosH8lsTKZvRK0PCs698QKrlc/MbwIDAQAB

Right, the public key is set. Next, we need to do some magic with the Java security package and generate an instance of the Cipher class. For this, we first need to create an instance of the RSA key using the KeyFactory class. Then, initialise a Cipher with that instance of the Key class. This is the code for that:

Key generatePublic = KeyFactory.getInstance(KEY_FACTORY_INSTANCE_TYPE).
        generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));

Cipher cipherInstance = Cipher.getInstance(CIPHER_INSTANCE_TYPE);
cipherInstance.init(1, generatePublic);

We now have everything we need to encrypt our super secret message. We now only have to call one method on the cipherInstance to encrypt our message:

cipherInstance.doFinal(bytesToBeEncrypted);

That’s it. You’ll have an encrypted byte array now. Here is the complete logic for encrypting a byte array:

private byte[] encrypt(byte[] inputByteArray) throws Throwable {

    File keyFile = new File(publicKeyPath);
    byte[] publicKey = Files.readAllBytes(keyFile.toPath());

    String keyString = new String(publicKey);
    keyString = keyString.replaceAll(NEW_LINE_CHARACTER, EMPTY_STRING)
            .replaceAll(PUBLIC_KEY_START_KEY_STRING, EMPTY_STRING)
            .replaceAll(PUBLIC_KEY_END_KEY_STRING, EMPTY_STRING);

    publicKey = keyString.getBytes();

    Key generatePublic = KeyFactory.getInstance(KEY_FACTORY_INSTANCE_TYPE).
            generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey)));

    Cipher cipherInstance = Cipher.getInstance(CIPHER_INSTANCE_TYPE);
    cipherInstance.init(1, generatePublic);

    return cipherInstance.doFinal(inputByteArray);
}

But we’re not done yet. We still need to encode this encrypted byte array to base64. For that, we’ll just the Base64 class that ships in the java.util.base64 package:

String encryptedString = Base64.getEncoder().encodeToString(encryptedByteArray);

Finally, we’re done with the encryption. The encryptedString variable is what you’re looking for. If you log the variable, you’ll see something like this:

s59uQfRCsCCQXi4mb02O1nfvFb0nvSulVP8Ve71rMHZoFYA0hXOEqVkgYvBT1ZWrfQhY2453B8eG929zqXWCRSMSAB+MbSQaun6rChuGAg8laxw89nN7/KoksuN45VvCFYxd18tAu915zOVG/yvYocpPW4xXcyAWDaD7j24XEwJFAU672haBaTPbEsoobfWWyQqfyUHDA+iCVSSMOl5zqx3MTj4vOG2SfCD25cxeH60AtI01OzMNW0XfAdQgegiQ27lKusMdK+7478g+n6gXSSzARatTotk7C5xR1DAzvIJvWLNIbKphTlykoB0u+/DXaeJQxD4/UCEbnwFoXnYVyQ==

This is your encrypted text. You can pass this text to anybody you want and it’ll not make any sense to anybody until they decryt it. So let’s see how we can do that in Python.

Decryption with Python

Now that we have the encrypted text, let’s move over to Python where we need to decrypt this. But before we can start the decryption, we need to import some stuff in our Python code:

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from base64 import b64decode

Once we have these packages imported, we need to read the private key from the file and create an RSA key instance. Once we read the file, we need to get rid of the extra text here as well, similar to what we did in Java. The following piece of code takes care of all that:

key = open("/path/to/keyPair/key.pem").read()
key = key.replace("-----BEGIN RSA PRIVATE KEY-----", "").replace("-----END RSA PRIVATE KEY-----", "").replace("\n", "")
key = b64decode(key)
key = RSA.importKey(key)

Make sure you change the path to your private key file in the first statement. Once we have this, we’ll store the encrypted text we got from Java into a variable. In the real world, you’d use HTTP or some sort of RPC to get the encrypted text to your Python code. We’ll just copy-paste it for now:

inputString = 'RyGR3vB6v8hl3ITN5H9tm3sxNQZnZGxOWMIL0V8s7VIQZgUhGonRAVnDKe5KHH9aB8KynoLaLUn5/baNqfC9EiynOLqS7CxNPTY28UT1kxchGQ/YX3yaw7AUBZeNmEKUBD5JOQD3VNaKbrgosnhaVK6bNzjlGyyhZrDpBlx2tX+h057b0ecZTPHHhJUwkjAmBMsSTwTUJqwzzCNARDpHCS4o2qt23XYJNmw5UidPJ2JURt45YUEUovPmzDSdmS/5V9fxbcCMpdwZJa5d2tLhzpcjdmUM6tiQNu4DUqwF4ICYxZmX9Za74Niu9fTTy4+C0jY1uUd8o8Y9g0tvamCBwQ=='

Next, we’ll create an instance of the Cipher class using the key, again similar to what we did in Java:

cipher = PKCS1_v1_5.new(key)

Next, we need to base64 decode the input string. If you remember, we had base64 encoded the encrypted text in Java. So we have to do the same thing here, but in the reverse order. Once we have the decoded string, we’ll use the Cipher instance we created to decrypt the message. We’ll use one statement to both decode the string, and then decrypt it:

plainText = cipher.decrypt(b64decode(inputString), "Error decrypting the input string!")

And that’s it. If you print the variable plainText now, you should get back your original message:

print(plainText)

And the output will be:

b'Some random words in no particular order.'

Let me know if you face any issues here or want any help with this stuff. And as always, you can checkout the complete project over at Github. The resources folder in the Java project in the repository has the Python code which you can use to decrypt the message.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: