3

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.

2
  • 1
    I'm facing an almost identical problem and was quite happy to find this. I am a bit confused that the encoded string is output in hex, since the entire point of base64 encoding is to turn binary data into printable text, but I assume that the original Java author did tat and you are merely maintaining compatibility with that code. Commented Aug 10, 2020 at 2:25
  • This might be helpful. It's not the same thing but something similar. Commented Jun 7, 2021 at 17:41

3 Answers 3

6

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Can you provide the decryption logic of the same using python.
2

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) 

Comments

1
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 

4 Comments

Please add an explanation of your code. In what way does it solve OP's problem?
I am getting error while running in pycharm and python 3.6 TypeError: Object type <class 'str'> cannot be passed to C code
@dinu0101 did you find any solution for this?
@sumanth Shetty, I found another solution which worked for me https://yococoxc.github.io/15493867450071.html.. It may help you, also

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.