chronodekar
chronodekar

Reputation: 2746

Encrypt with AES/ECB/PKSC5, migrating Java to Python

I'd been tasked with implementing AES encryption on a project. The reference code had been written in Java - it needed to be converted to Python. While organizing my notes to write a SO question, I accidentally stumbled across the answer! In the hopes that someone else finds this useful, I'm going to mention my notes here as a 'share your knowledge' kind of question.

The requirement was to encrypt a message using AES with a given key. Here is a simplified look at the reference code (in Java),

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Encoder;


public class EncryptAES {

    private static String toHexString(byte[] data) {        
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < data.length; ++i) {
            String s = Integer.toHexString(data[i] & 0XFF);
            buf.append((s.length() == 1) ? ("0" + s) : s);
        }
        return buf.toString();
    }

    public static String encrypt(String input, String key) {
        byte[] crypted = null;
        try {
            SecretKeySpec skey = new SecretKeySpec(key.getBytes(), "AES");
            Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, skey);
            crypted = cipher.doFinal(input.getBytes());
            final String encryptedString = toHexString(Base64.encodeBase64(crypted));
            return encryptedString;
        } catch (Exception e) {
            System.out.println(e.toString());
        }
        return new String(new BASE64Encoder().encode(crypted));
    }

    public static void main(String[] args) {
        String key = args[0];
        String plaintext = args[1];
        System.out.println("KEY = " + key);
        System.out.println("PLAINTEXT = " + plaintext);
        System.out.println("CIPHER = " + EncryptAES.encrypt(plaintext, key));
    }
}

If you save the above as 'EncryptAES.java' and keep the library file commons-codec-1.7.jar in the same directory, you can compile it with the following command,

$ javac EncryptAES.java -cp commons-codec-1.7.jar

Here is the output when running the program a few times,

$ java -cp "commons-codec-1.7.jar:." EncryptAES ddddffffeeeerrrr message
KEY = ddddffffeeeerrrr
MESSAGE = message
CRYPTO = 397a59594d35524e6b6a463253706f41467668646b773d3d
$

$ java -cp "commons-codec-1.7.jar:." EncryptAES qqqqwwwweeeerrrr ThisIsAVeryImportantMessage
KEY = qqqqwwwweeeerrrr
PLAINTEXT = ThisIsAVeryImportantMessage
CIPHER = 56536a384d667736756b595a394e396b6d504d736231444673375250736d5639596f637072792f6e4b424d3d
$

Looking around, I found the Python Crypto library. Here is one of the early attempts I had to replicate the above output,

#!/usr/bin/python

import sys
from Crypto.Cipher import AES

if __name__ == '__main__':
    key = sys.argv[1]
    plaintext = sys.argv[2]
    print 'KEY = ' + key
    print 'PLAINTEXT = ' + plaintext

    encobj = AES.new(key, AES.MODE_ECB)
    ciphertext = encobj.encrypt(plaintext)
    print 'CIPHER = ' + ciphertext.encode('hex') 

This doesn't quite get what I need. Instead, I get an error message about the input string needing to be a multiple of 16 in length. Which brings me to my next attempt,

#!/usr/bin/python

import sys
from Crypto.Cipher import AES

# ref: https://gist.github.com/crmccreary/5610068
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 
unpad = lambda s : s[0:-ord(s[-1])]

class AESCipher:
    def __init__( self, key ):
        """
        Requires hex encoded param as a key
        """
        self.key = key.decode("hex")

    def encrypt( self, raw ):
        """
        Returns hex encoded encrypted value!
        """
        raw = pad(raw)
        cipher = AES.new(self.key, AES.MODE_ECB)
        return cipher.encrypt(raw).encode("hex")


if __name__ == '__main__':
    key = sys.argv[1]
    plaintext = sys.argv[2]
    print 'KEY = ' + key
    print 'PLAINTEXT = ' + plaintext

    # ref: http://stackoverflow.com/a/16882092
    hex_key = "".join("{:02x}".format(ord(c)) for c in key)

    encryptor = AESCipher(hex_key)
    ciphertext = encryptor.encrypt(plaintext)
    print 'CIPHER = ' + ciphertext

I'm not really sure what to make of the output, to be honest,

$ python EncryptAES2.py ddddffffeeeerrrr message
KEY = ddddffffeeeerrrr
PLAINTEXT = message
CIPHER = f7361833944d9231764a9a0016f85d93
$

I tried a lot of things - different encryption modes, blogs, SO questions, and had given up on finding a solution on my own. It was at this point that I decided to collect my notes and ask a question here. Now, there wouldn't be much of a point if I didn't list my attempts, so I started organizing them in a folder and labelled them EncryptAES.py, EncryptAES2.py .. etc.

Upvotes: 3

Views: 7439

Answers (3)

gormac23
gormac23

Reputation: 21

Taking @chronodekar's answer and changing it to work for Python 3.9.

PyCryptoDome Version for Python 3 raises a TypeError: Object type <class 'str'> cannot be passed to C code. To solve this issue, I converted the key and the raw plaintext into a byte array.

    class simple_AES:

    def __init__(self, key):
        self.key = bytearray(key.encode())

    def encrypt_AES(self, raw):
        raw = bytearray(pad(raw).encode())
        cipher = AES.new(self.key, AES.MODE_ECB)
        return base64.b64encode(cipher.encrypt(raw))

However, I also ran into a problem with the output. I found this answer to convert the output into hex python base64 to hex.

    hex_ciphertext = base64.b64decode(ciphertext).hex()
    print('CIPHER = ' + hex_ciphertext)

Upvotes: 2

rupini chakka
rupini chakka

Reputation: 19

import base64
from Crypto.Cipher import AES

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s: s[0:-ord(s[-1])]


class AESCipher:
    def __init__(self, key):
        self.key = key

    def encrypt(self, raw):
        raw = pad(raw)
        cipher = AES.new(self.key, AES.MODE_ECB)
        raw = cipher.encrypt(raw)
        encrypt_val = base64.b64encode(raw)
        return encrypt_val

    def decrypt(self, raw):
        raw = raw.decode('base64')
        cipher = AES.new(self.key, AES.MODE_ECB)
        raw = cipher.decrypt(raw)
        raw = unpad(raw)
        return raw


if __name__ == '__main__':
    key = '123456789012345678901234'
    plaintext = '111122223333'
    print 'KEY = ' + key
    print 'PLAINTEXT = ' + plaintext

    ## Encrptor logic
    encryptor = AESCipher(key)
    ciphertext = encryptor.encrypt(plaintext)

    hex_ciphertext = "".join("{:02x}".format(ord(c)) for c in ciphertext)
    print 'Encrypted value : ' + hex_ciphertext

    bytesarray = []
    hexstr = ''.join(hex_ciphertext.split(" "))
    for i in range(0, len(hexstr), 2):
        bytesarray.append(chr(int(hexstr[i:i+2], 16)))

    val = ''.join(bytesarray)
    decrypt_ciphertxt = encryptor.decrypt(val)
    print 'Decrypted value  : ' + decrypt_ciphertxt

Upvotes: 1

chronodekar
chronodekar

Reputation: 2746

As I was preparing the list, inspiration struck me and for my last attempt, I decided to reformat the output in hex. To my pleasant surprise, it worked! Here's the winning code,

#!/usr/bin/python

import sys
import base64
from Crypto.Cipher import AES

# ref: http://stackoverflow.com/a/12525165
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) 

class AESCipher:
    def __init__( self, key ):
        self.key = key

    def encrypt( self, raw ):
        raw = pad(raw)
        cipher = AES.new(self.key, AES.MODE_ECB)
        return base64.b64encode(cipher.encrypt(raw)) 


if __name__ == '__main__':
    key = sys.argv[1]
    plaintext = sys.argv[2]
    print 'KEY = ' + key
    print 'PLAINTEXT = ' + plaintext

    encryptor = AESCipher(key)
    ciphertext = encryptor.encrypt(plaintext)

    hex_ciphertext = "".join("{:02x}".format(ord(c)) for c in ciphertext)

    print 'CIPHER = ' + hex_ciphertext

For reference here is the output with the earlier inputs I used for the Java example,

$ python EncryptAES3.py ddddffffeeeerrrr message
KEY = ddddffffeeeerrrr
PLAINTEXT = message
CIPHER = 397a59594d35524e6b6a463253706f41467668646b773d3d
$

$ python EncryptAES3.py qqqqwwwweeeerrrr ThisIsAVeryImportantMessage
KEY = qqqqwwwweeeerrrr
PLAINTEXT = ThisIsAVeryImportantMessage
CIPHER = 56536a384d667736756b595a394e396b6d504d736231444673375250736d5639596f637072792f6e4b424d3d
$

Getting to this solution took me A LOT of trial-and-error. If there is a more disciplined approach to converting Java to Python, I'd love to hear about it!

Upvotes: 6

Related Questions