Skip to content

Instantly share code, notes, and snippets.

@PM2Ring
Last active January 16, 2018 13:51
Show Gist options
  • Save PM2Ring/ac1d31d7391c94410c142d5a059b4cda to your computer and use it in GitHub Desktop.
Save PM2Ring/ac1d31d7391c94410c142d5a059b4cda to your computer and use it in GitHub Desktop.
Call the AES_set_encrypt_key, AES_set_decrypt_key, and AES_ecb_encrypt functions from the OpenSSL library using ctypes
#!/usr/bin/env python3
''' AES ECB
Call the AES_set_encrypt_key, AES_set_decrypt_key, and
AES_ecb_encrypt functions from the OpenSSL library
Uses info from /usr/include/openssl/aes.h
Also see https://boringssl.googlesource.com/boringssl/+/2490/include/openssl/aes.h
Written by PM 2Ring 2017.10.07
'''
import os
from ctypes import *
AES_MAXNR = 14
AES_BLOCK_SIZE = 16
DECODE = 0
ENCODE = 1
class AES_KEY(Structure):
_fields_ = [
("rd_key", c_long * 4 *(AES_MAXNR + 1)),
("rounds", c_int),
]
crypto = cdll.LoadLibrary("libeay32.dll" if os.name == "nt" else "libssl.so")
#crypto = cdll.LoadLibrary("libeay32.dll" if os.name == "nt" else "libcrypto.so")
# Function prototypes
AES_set_encrypt_key = crypto.AES_set_encrypt_key
AES_set_encrypt_key.restype = c_int
# userKey, bits, key
AES_set_encrypt_key.argtypes = [c_char_p, c_int, POINTER(AES_KEY)]
AES_set_decrypt_key = crypto.AES_set_decrypt_key
AES_set_decrypt_key.restype = c_int
# userKey, bits, key
AES_set_decrypt_key.argtypes = [c_char_p, c_int, POINTER(AES_KEY)]
AES_ecb_encrypt = crypto.AES_ecb_encrypt
AES_ecb_encrypt.restype = None
#in, out, key, enc(1=encode, 0=decode)
AES_ecb_encrypt.argtypes = [c_char_p, c_char_p, POINTER(AES_KEY), c_int]
set_key = (AES_set_decrypt_key, AES_set_encrypt_key)
def set_aes_key(key, encode):
''' Create an AES encoding or decoding key '''
keylen = len(key)
valid = {16, 24, 32}
if keylen not in valid:
msg = f'Key length must be one of {valid}, not {keylen}'
raise ValueError(msg)
aes_key = AES_KEY()
rc = set_key[encode](c_char_p(key), keylen * 8, byref(aes_key))
if rc != 0:
# I don't think we can get here...
raise ValueError('Error generating AES key', rc)
return aes_key
def aes_ecb(block, aes_key, encode):
''' Encrypt or decrypt a single block '''
outbuff = create_string_buffer(AES_BLOCK_SIZE)
AES_ecb_encrypt(c_char_p(block), outbuff, byref(aes_key), encode)
return outbuff.raw
def show_key(key):
print('rounds', key.rounds)
print('rd_key')
mask = 0xffffffff
for u in key.rd_key:
print([f'{v & mask:08x}' for v in u])
if __name__ == "__main__":
# Test
data = b'This is a test23'
key = b'YELLOW SUBMARINE'
print('Data: ', data)
print('Key: ', key, len(key), key.hex())
ekey = set_aes_key(key, ENCODE)
dkey = set_aes_key(key, DECODE)
#show_key(ekey)
#show_key(dkey)
coded = aes_ecb(data, ekey, ENCODE)
print('Coded:', coded.hex(), coded)
plain = aes_ecb(coded, dkey, DECODE)
print('Plain:', plain)
''' Encode / decode multiple blocks using aes_ecb.py '''
from aes_ecb import (
AES_BLOCK_SIZE, ENCODE, DECODE,
set_aes_key, aes_ecb, PKCS7_pad, PKCS7_unpad
)
def PKCS7_pad(data):
padsize = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
return data + bytes([padsize]) * padsize
def PKCS7_unpad(data):
offset = data[-1]
return data[:-offset]
def aes_ecb_multi_encode(data, key):
# Pad
padsize = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE
data += bytes([padsize]) * padsize
ekey = set_aes_key(key, ENCODE)
cipher = []
for block in zip(*[iter(data)] * AES_BLOCK_SIZE):
cipher.append(aes_ecb(bytes(block), ekey, ENCODE))
return b''.join(cipher)
def aes_ecb_multi_decode(data, key):
dkey = set_aes_key(key, DECODE)
plain = []
for block in zip(*[iter(data)] * AES_BLOCK_SIZE):
plain.append(aes_ecb(bytes(block), dkey, DECODE))
# Unpad
last = plain[-1]
offset = last[-1]
plain[-1] = last[:-offset]
return b''.join(plain)
#!/usr/bin/env python3
''' Verify AES_ECB against the test values from
http://csrc.nist.gov/groups/STM/cavp/documents/aes/KAT_AES.zip
Written by PM 2Ring 2017.10.07
'''
from itertools import product
from aes_ecb import set_aes_key, aes_ecb, ENCODE, DECODE
def group_lines(fname):
''' yield lists of non-empty lines from file fname '''
with open(fname) as f:
buff = []
for line in f:
line = line.rstrip()
if line:
buff.append(line)
else:
yield buff
buff = []
if buff:
yield buff
def report(mode, count, expected, actual):
print(f'Error in {mode} test {count}\n'
f'Expected: {expected.hex()}\n'
f'Got: {actual.hex()}\n')
def test(fname):
print(fname)
reader = group_lines(fname)
headers = next(reader)
for line in headers[2:5]:
print(line)
print()
for group in reader:
if len(group) == 1:
mode = group[0][1:-1].title()
print(mode)
else:
for line in group:
name, _, val = line.partition(' = ')
if name == 'COUNT':
count = val
d = {}
else:
d[name[0]] = bytes.fromhex(val)
key = d['K']
if mode == 'Encrypt':
source, expected, encode = d['P'], d['C'], ENCODE
else:
source, expected, encode = d['C'], d['P'], DECODE
aes_key = set_aes_key(key, encode)
coded = aes_ecb(source, aes_key, encode)
if coded != expected:
report(mode, count, expected, coded)
print()
basedir = 'KAT_AES'
names = ('ECBGFSbox', 'ECBKeySbox', 'ECBVarKey', 'ECBVarTxt')
for name, bits in product(names, (128, 192, 256)):
fname = f'{basedir}/{name}{bits}.rsp'
test(fname)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment