Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RSA encryption: Encrypt in android / java, decrypt in python (cryptography)

The problem

I'm trying to configure asymmetric encryption between python and java / android. The use case is storing user passwords securely (using the public key), enabling re-authentication to the server (which has the private key).

I've got the crypto algorithms working separately in Python and Java, but can't get my Python side to decrypt ciphertext generated in Java. I think the two most likely issues are either

  1. Issues with base64 encoded strings (have been struggling with these plenty!), or
  2. Mismatched padding and other encryption specifications between client / server

To create a complete example here i'll reproduce both private and public keys, and then will re-generate a new set for actual use.

Java (client) side code:

Java imports

// Java imports
import android.util.Base64;
import android.util.Log;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;

Java encryption code - I apologise for the code formatting / width...

public class RSAEncryption {

    public void encrypt (String ... strings) throws Exception {

        // Load in public key, remove escape characters, headers, footers:     
        String public_key_string = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6kTFJl+6jG2rfYlZxtFi\nzajOgvKgInJHOa4i3G5vB9c7f7kzsTMOmeg5YHn3LNndg4Wx4AyfN5fbcNGg+KmJ\nK91b2lkgFy7pVEhWfzK4/yqk0liG7MwuN0G8GqUjIqJOXPS6lXB9Zr3n9QyTkKGV\n9cnNVPV1CNuN3bOu0t8Mu3fvZ+z8edq/cfUpTXwDdfRmZ6WeWxxqogK2uCwmneEN\n8kqyWE4OxhyqLJMw9mCGHOqTVgJUnvjMBezywr6s3vIcs2Q7CnxQx/g/GTqhUxLS\nHDlyAFDbhU4BWkCHrCa/nTyIFgXC9X4YRpQd24xfGXJjB2qUVv2H0O5FoRJAEYvS\nfQIDAQAB\n-----END PUBLIC KEY-----\n";

        String publicKeyContent = public_key_string.replaceAll("\\n", "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", "");

        // Encrypt input using OAEP / SHA-256 /MGF1
        byte[] input = strings[0].getBytes();
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding");

        KeyFactory keyFactory = KeyFactory.getInstance("RSA");

        byte[] publicBytes = Base64.decode(publicKeyContent, Base64.DEFAULT);
        X509EncodedKeySpec keySpecX509 = new X509EncodedKeySpec(publicBytes);
        PublicKey pubKey = keyFactory.generatePublic(keySpecX509);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey, new OAEPParameterSpec("SHA-256",
                "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
        byte[] cipherText = cipher.doFinal(input);

        return cipherText;

    }

}

If i run those code with the input string = "test text", i get encrypted output of:

BoOymgUGnB/LIqYOhBmW60HCL4s6FQFlNpRmhqm63AGq7gud4g96og6f+5yHUVHktAT0pl93UpAz//aae4esTseR6/o7jD/NGS+gZlNRHsSuNNstVZ8wOp1S03YcHYEpXtTdElMiKso4cbZmO20SoUvyzjI5e7RM1aWKCebqCvG7RYneCgoMi611AY2cVSwMYOENcRwqXeB3sFMrjoNQ1TkkgHU5MSxDbVLbNO72AYYQi/TkwiQ57qnjgk3KHXeatJv/5hWqa5ooBCy6aLDFux6X2721wTVa2/RSiZKG7DJHObV5pQNw4zjGWc4d5IJ+P/o6GKHY4fTj8ujqS6A44Q==

I'm confident that this works, as I can decrypt it successfully with the private key (with another code block, not reproduced here).

This is then used as the input for the python code.

Python (server side) code

Python imports

from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backendb

Python decryption code

PRIVATE_KEY_STRING='-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDqRMUmX7qMbat9\niVnG0WLNqM6C8qAickc5riLcbm8H1zt/uTOxMw6Z6Dlgefcs2d2DhbHgDJ83l9tw\n0aD4qYkr3VvaWSAXLulUSFZ/Mrj/KqTSWIbszC43QbwapSMiok5c9LqVcH1mvef1\nDJOQoZX1yc1U9XUI243ds67S3wy7d+9n7Px52r9x9SlNfAN19GZnpZ5bHGqiAra4\nLCad4Q3ySrJYTg7GHKoskzD2YIYc6pNWAlSe+MwF7PLCvqze8hyzZDsKfFDH+D8Z\nOqFTEtIcOXIAUNuFTgFaQIesJr+dPIgWBcL1fhhGlB3bjF8ZcmMHapRW/YfQ7kWh\nEkARi9J9AgMBAAECggEARuUi6JcFxGOYBzies51AEk7omBZGwcXlqh35rM26yhun\nhOKOMyzpWUg+vOSMGcWg1KGMD+qh8FgDb6Pw2++qdFzb5DsejAWFVR1DF+FIvOex\n03o48sZjohNBkqqw9FU788OYB4twV7xWywDQU2+jCyvT+McDcPfIefRbjrMzjjOM\nOhMY0F6OXeDpuZttgqZsP65aA7yaIfjuk5tuYbpZvNmV1DAlqfGmxt2PO1laobGQ\nS7Vx18zpWWdh/GgnbZDD3SG69AmAWIdOZNV6SuUC5lgOqw2O+8TB7MXFbiCy7v5J\n1tMap6OCRDm5jLsc6ZFP5Zs1s9/FnW9/jCxw4kFxPQKBgQD7DcVUaW7rKUmYORAA\nqY6uAIu+Lo+607G4Nva52Rotj0vdiIsAYxJPP2dxKTjL0GLbKIsOcmd2xD0e1/yu\nlAu6koCyC5cT5XwNJ388XDwtWPU2J7mC1TKq0TJUZ56uzdZxNKOWQ7Upq3901zbQ\nQhssjJpgX09CPzlfpAc9NHhchwKBgQDu4lc5HaJNsFeEMOuaUmTUu+W+veIZZaV3\n7yTnLxoJCyqu5DVKvhMwDZP3I2JuvHbCPf3AzshvNcbCiG/6u9CQSbv4wRZO6p8c\nlIAymy0neaq7+uxHdvem58A4xIlKhlnNpzIqE9fay7ejAklGPTFs/YdsyVXwADVr\n30cfOdO92wKBgCCrGhJx5c0UAk+cnUh4x+g8ifKlfG6DPY0LGe/1IELtcqHRMsVK\nHwfQ6FUBWDKtWy/Jhs7KdEwwHQP2dxsAiMYuajDA8VfVdN8BVL02A16jRMVXRfyQ\nYZd4wWPaV/vHLTBt+RuEk/5oIp3Bo5BWCdMyOKRxwo6MS5r2bTq5qS/hAoGAKezT\nfhSzXYsrcOndD7KSO7vWcImG2wo55ji0c1aS7S9miFdI+xss5uwbIe614dV1ylVy\n6ZnhF5OKlK25aXn4+rnWIaxRq/wFfNCbR0ZwwFLcIi3BtjEs+cAGvm/P4KJ/tFY5\nuaTN53qFejh2f7tRp10/nVogmQSQW6ROKS7O+K0CgYA03s/ajxaYTLzBHpIdqisl\nsGaen4IBibB3OZs/vplBZ0xG34B8+MC8qgvC6DFVR4Y3XMol8lOGiS6OQVvIRjnC\nhqd/QobpH0j5Wy2cM+6yXVvj95dwif7QjaqDydniRhGbxa6iJ8vTYqyl9yNjWwX/\n72JRzDNB+9De2LntVwzBwA==\n-----END PRIVATE KEY-----\n'

#Load the java output into a bytes object to decrypt
java_out = b'BoOymgUGnB/LIqYOhBmW60HCL4s6FQFlNpRmhqm63AGq7gud4g96og6f+5yHUVHktAT0pl93UpAz//aae4esTseR6/o7jD/NGS+gZlNRHsSuNNstVZ8wOp1S03YcHYEpXtTdElMiKso4cbZmO20SoUvyzjI5e7RM1aWKCebqCvG7RYneCgoMi611AY2cVSwMYOENcRwqXeB3sFMrjoNQ1TkkgHU5MSxDbVLbNO72AYYQi/TkwiQ57qnjgk3KHXeatJv/5hWqa5ooBCy6aLDFux6X2721wTVa2/RSiZKG7DJHObV5pQNw4zjGWc4d5IJ+P/o6GKHY4fTj8ujqS6A44Q=='

#Load the private key string into a functioning key object
private_key = serialization.load_pem_private_key(
         PRIVATE_KEY_STRING.encode('UTF-8'),
         password=None,
         backend=default_backend()
         )

#Decrypt the input
private_key.decrypt(
    java_out,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

Using this same code (and another block to encrypt) I can successfully encrypt / decrpyt in Python. However, when using the output from Java as the input, this code returns the following error:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-71-9af0738427f1> in <module>()
      4         mgf=padding.MGF1(algorithm=hashes.SHA256()),
      5         algorithm=hashes.SHA256(),
----> 6         label=None
      7     )
      8 )

C:\ProgramData\OWTools\miniconda\lib\site-packages\cryptography\hazmat\backends\openssl\rsa.py in decrypt(self, ciphertext, padding)
    384         key_size_bytes = int(math.ceil(self.key_size / 8.0))
    385         if key_size_bytes != len(ciphertext):
--> 386             raise ValueError("Ciphertext length must be equal to key size.")
    387 
    388         return _enc_dec_rsa(self._backend, self, ciphertext, padding)

ValueError: Ciphertext length must be equal to key size.

One thought which might be useful is that in the python code to encrypt, the cipher text comes out in the format:

b'N\xf2\xa7\xfaW6[Z\x0c\x14\xe7\xc8\xcb\xed\xa2xM\xea\x83\xa3\xb7\x05few!\x0e\\zd\xfe g\xbbO1\xfe\n\xa0Q\xab\xb8e0\xe9R^\xc8\xbbU\xfbuxp\x00\xf7>\xf8\x14\x1c\xd7\xca}\xd3\n{\xc9\xa2^\x8c0.\x9a\\\xe2\x89\xfd\xbf\xabw\x03\xc4\xdc\xfdX\xcf\xcc\x01\xc7\xc8\\j\xa9\xd7U\xfbP\xc2$\xd9^\x9e1\x9d&\xf1\x1b\x08%\xb3kF`\x19\xeb\xc8\xf0\x9f\xb1\x13\xaa\xa1\xed+X\xdc\x10!Fw\xae\x93\xb0\x80\xf3}\x98nz^\x04\xfe\x08T\x98d>[\xa7\x92\x08\x8a\r\xbc\x85\xf8\xcb\xce\xcc\x94\x95V\xf0\xad\x8d\x94N\x0c\x03\xf0\xbcT\x06%\x0b\x06\xcb_\xcauY$\n\xbc\xea\xde\xe4G\x05F\xf5\xef\x10\x83w"f5*&?\\9\x08\xd3}\xf0X\xb7\x06F\x99\xe1\xf3\x02\x14a\x7f\x1a0,?{\x12^|\xba\xed\xf6s\xed\xe6@m\xc7ex\x86\xf1\xae\xeec=\x92\x7fZ\x19\xee\x0f\x86\xb8\xf5T_\x9f\x1c'

which is obviously quite different to the java output - though i'm not entirely sure why (or what that means!)

Any help much appreciated! Thanks

like image 384
phd Avatar asked Oct 17 '22 06:10

phd


1 Answers

Based on the comment from Kelakela, i realised that the problem was that the output of the Java code is in Base64, while the python code requires Binary input.

The solution was as simple as:

from codecs import decode 
ciphertext = base64.decode(java_out, 'base64')

and then running private_key.decrypt(ciphertext, ... ) on the output in binary format.

The rest of the code works fine for me.

like image 132
phd Avatar answered Oct 20 '22 11:10

phd