Last active
January 16, 2018 13:51
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
''' 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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