Pycrypto AES GCM encryption and Java decryption
Asked Answered
C

2

5

I'm using Pycryptodome (a PyCrypto fork) to create AES-GCM ciphertexts. I use the following Python code to encrypt:

cek = os.urandom(16)
nonce = os.urandom(12)

cipher = AES.new(cek, AES.MODE_GCM, nonce=nonce, mac_len=16)
ciphertext = cipher.encrypt(message)

I then pass this to Java to decrypt:

byte[] nonce = new byte[12];

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv);
SecretKeySpec secretKeySpec = new SecretKeySpec(cek, "AES");

IvParameterSpec ivParameterSpec = new IvParameterSpec(nonce);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmSpec);

byte[] decBytes = mCipher.doFinal(cipherText);

However, I get the following error:

Exception in thread "main" javax.crypto.AEADBadTagException: Tag mismatch!
    at com.sun.crypto.provider.GaloisCounterMode.decryptFinal(GaloisCounterMode.java:524)
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1023)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
Ceil answered 17/5, 2018 at 16:20 Comment(0)
C
6

You're missing one thing: Pycryptodome does not add the hash tag to the message - you have to append it to the encrypted message:

E.g.

ciphertext, tag = cipher.encrypt_and_digest(message)
ciphertext = ciphertext + tag
Ceil answered 17/5, 2018 at 16:20 Comment(0)
C
1

Thanks to Alastair McCormack's answer above, here is what worked for me (Python code):

from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA256, SHA1
from Crypto.Signature import pss
from base64 import b64encode

data = 'hello world'.encode("utf-8")

with open("joe.pub", "rb") as f:
    encodedKey = f.read()
pubkey = RSA.importKey(encodedKey)
if pubkey.has_private():
    raise Exception('need a public key for encryption')

session_key = get_random_bytes(16)

# Encrypt the session key with the public RSA key
cipher_rsa = PKCS1_OAEP.new(pubkey, hashAlgo=SHA256, mgfunc=lambda x,y: pss.MGF1(x,y, SHA1))
enc_session_key = cipher_rsa.encrypt(session_key)

# Encrypt the data with the AES session key
cipher_aes = AES.new(session_key, AES.MODE_GCM)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
ciphertext = ciphertext + tag
mesg = ''.join([x for x in (enc_session_key, cipher_aes.nonce, tag, ciphertext)])
print b64encode(mesg)

And the associated Java code:

import java.io.FileReader;
import java.io.BufferedReader;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.crypto.spec.GCMParameterSpec;
import java.util.Base64;
import java.util.Arrays;

public class So
{
    static {
        try {
            @SuppressWarnings("unchecked")
            Class<Provider> c = (Class<Provider>)Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
            Security.addProvider(c.getDeclaredConstructor().newInstance());
        } catch(java.lang.ClassNotFoundException |
                java.lang.NoSuchMethodException |
                java.lang.InstantiationException |
                java.lang.IllegalAccessException |
                java.lang.reflect.InvocationTargetException ex) {
        System.err.println("BouncyCastle not found");
        }
    }

    static private byte[] loadPvtKey(String filePath) throws java.io.IOException
    {
    BufferedReader in = null;
    try {
        in = new BufferedReader(new FileReader(filePath));

        /* read, check and discard first line. */
        String line = in.readLine();
        if ( ! line.equals("-----BEGIN PRIVATE KEY-----") )
        throw new IllegalArgumentException(filePath + ": not a private key file");
        StringBuilder sbuf = new StringBuilder();
        while ((line = in.readLine()) != null) {
        if ( line.equals("-----END PRIVATE KEY-----") ) break;
        sbuf.append(line);
        }
        return Base64.getDecoder().decode(sbuf.toString());
    } finally {
        try { if ( in != null ) in.close(); }
        catch(java.io.IOException ex) {}
    }
    }

    static public void main(String[] args) throws Exception
    {
    if ( args.length != 2 ) {
        System.err.println("usage: java Decrypt pvtKeyFile encString64");
        System.exit(1);
    }

    int index = 0;
    String pvtKeyFile = args[index++];
    String encString64 = args[index++];
    byte[] encBytes = Base64.getDecoder().decode(encString64);
    System.err.println("encrypted bytes: " + encBytes.length);

    byte[] bytes = loadPvtKey(pvtKeyFile);
    PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec(bytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    PrivateKey pvt = kf.generatePrivate(ks);

    Base64.Encoder encoder = Base64.getEncoder();
    byte[] encSessionKey = Arrays.copyOfRange(encBytes, 0, 256);
    System.err.printf("encSessionKey -> %s\n", encoder.encodeToString(encSessionKey));
    Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    rsaCipher.init(Cipher.DECRYPT_MODE, pvt);
    byte[] sessionKey = rsaCipher.doFinal(encSessionKey);
    System.err.printf("sessionKey -> %s\n", encoder.encodeToString(sessionKey));

    byte[] iv = Arrays.copyOfRange(encBytes, 256, 256+16);
    System.err.printf("iv -> %s\n", encoder.encodeToString(iv));
    GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);

    Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
    SecretKeySpec aesKey = new SecretKeySpec(sessionKey, "AES");
    aesCipher.init(Cipher.DECRYPT_MODE, aesKey, ivSpec);

    byte[] tag = Arrays.copyOfRange(encBytes, 256+16, 256+32);
    System.err.printf("tag[%d] -> %s\n", tag.length, encoder.encodeToString(tag));

    byte[] cipherText = Arrays.copyOfRange(encBytes, 256+32, encBytes.length);
    System.err.printf("cipherText -> %s\n", encoder.encodeToString(cipherText));

    byte[] clearText = aesCipher.doFinal(cipherText);
    System.err.printf("clearText -> %s\n", new String(clearText, "UTF-8"));
    }
}
Cameral answered 31/7, 2018 at 13:52 Comment(3)
Cool. I'm glad it worked for you. To help others, you might want to consider creating your code on Github Gist and link to it there from the comments on the main question.Ceil
Will do that. I struggled with this for 3-4 days before I was finally able to get it with your help.Cameral
Good job. Is encrypt_and_digest equivalent to dofinal?Howlett

© 2022 - 2024 — McMap. All rights reserved.