aboutsummaryrefslogtreecommitdiffstats
path: root/src/python/botan.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/python/botan.py')
-rwxr-xr-xsrc/python/botan.py517
1 files changed, 517 insertions, 0 deletions
diff --git a/src/python/botan.py b/src/python/botan.py
new file mode 100755
index 000000000..8414308ec
--- /dev/null
+++ b/src/python/botan.py
@@ -0,0 +1,517 @@
+#!/usr/bin/python
+
+"""
+Python wrapper of the botan crypto library
+http://botan.randombit.net
+
+(C) 2015 Jack Lloyd
+
+Botan is released under the Simplified BSD License (see license.txt)
+"""
+
+import sys
+from ctypes import *
+
+"""
+Module initialization
+"""
+botan = CDLL('libbotan-1.11.so')
+
+expected_api_rev = 20150210
+botan_api_rev = botan.botan_ffi_api_version()
+
+if botan_api_rev != expected_api_rev:
+ raise Exception("Bad botan API rev got %d expected %d" % (botan_api_rev, expected_api_rev))
+
+"""
+Versions
+"""
+def version_major():
+ return botan.botan_version_major()
+
+def version_minor():
+ return botan.botan_version_minor()
+
+def version_patch():
+ return botan.botan_version_patch()
+
+def version_string():
+ botan.botan_version_string.restype = c_char_p
+ return botan.botan_version_string()
+
+"""
+RNG
+"""
+class rng(object):
+ # Can also use type "system"
+ def __init__(self, rng_type = 'user'):
+ botan.botan_rng_init.argtypes = [c_void_p, c_char_p]
+ self.rng = c_void_p(0)
+ rc = botan.botan_rng_init(byref(self.rng), rng_type)
+ if rc != 0 or self.rng is None:
+ raise Exception("No rng " + algo + " for you!")
+
+ def __del__(self):
+ botan.botan_rng_destroy.argtypes = [c_void_p]
+ botan.botan_rng_destroy(self.rng)
+
+ def reseed(self, bits = 256):
+ botan.botan_rng_reseed.argtypes = [c_void_p, c_size_t]
+ botan.botan_rng_reseed(self.rng, bits)
+
+ def get(self, length):
+ botan.botan_rng_get.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ out = create_string_buffer(length)
+ l = c_size_t(length)
+ rc = botan.botan_rng_get(self.rng, out, l)
+ return str(out.raw)
+
+"""
+Hash function
+"""
+class hash_function(object):
+ def __init__(self, algo):
+ botan.botan_hash_init.argtypes = [c_void_p, c_char_p, c_uint32]
+ flags = 0 # always zero in this API version
+ self.hash = c_void_p(0)
+ rc = botan.botan_hash_init(byref(self.hash), algo, flags)
+ if rc != 0 or self.hash is None:
+ raise Exception("No hash " + algo + " for you!")
+
+ def __del__(self):
+ botan.botan_hash_destroy.argtypes = [c_void_p]
+ botan.botan_hash_destroy(self.hash)
+
+ def clear(self):
+ botan.botan_hash_clear.argtypes = [c_void_p]
+ return botan.botan_hash_clear(self.hash)
+
+ def output_length(self):
+ botan.botan_hash_output_length.argtypes = [c_void_p,POINTER(c_size_t)]
+ l = c_size_t(0)
+ rc = botan.botan_hash_output_length(self.hash, byref(l))
+ return l.value
+
+ def update(self, x):
+ botan.botan_hash_update.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_hash_update(self.hash, x, len(x))
+
+ def final(self):
+ botan.botan_hash_final.argtypes = [c_void_p, POINTER(c_char)]
+ out = create_string_buffer(self.output_length())
+ botan.botan_hash_final(self.hash, out)
+ return str(out.raw)
+
+"""
+Message authentication codes
+"""
+class message_authentication_code(object):
+ def __init__(self, algo):
+ botan.botan_mac_init.argtypes = [c_void_p, c_char_p, c_uint32]
+ flags = 0 # always zero in this API version
+ self.mac = c_void_p(0)
+ rc = botan.botan_mac_init(byref(self.mac), algo, flags)
+ if rc != 0 or self.hash is None:
+ raise Exception("No mac " + algo + " for you!")
+
+ def __del__(self):
+ botan.botan_mac_destroy.argtypes = [c_void_p]
+ botan.botan_mac_destroy(self.mac)
+
+ def clear(self):
+ botan.botan_mac_clear.argtypes = [c_void_p]
+ return botan.botan_mac_clear(self.mac)
+
+ def output_length(self):
+ botan.botan_mac_output_length.argtypes = [c_void_p, POINTER(c_size_t)]
+ l = c_size_t(0)
+ rc = botan.botan_mac_output_length(self.mac, byref(l))
+ return l.value
+
+ def set_key(self, key):
+ botan.botan_mac_set_key.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ return botan.botan_mac_set_key(self.mac, k, len(k))
+
+ def update(self, x):
+ botan.botan_mac_update.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_mac_update(self.mac, x, len(x))
+
+ def final(self):
+ botan.botan_mac_final.argtypes = [c_void_p, POINTER(c_char)]
+ out = create_string_buffer(self.output_length())
+ botan.botan_mac_final(self.mac, out)
+ return str(out.raw)
+
+class cipher(object):
+ def __init__(self, algo, encrypt = True):
+ botan.botan_cipher_init.argtypes = [c_void_p,c_char_p, c_uint32]
+ flags = 0 if encrypt else 1
+ self.cipher = c_void_p(0)
+ rc = botan.botan_cipher_init(byref(self.cipher), algo, flags)
+ if rc != 0 or self.cipher is None:
+ raise Exception("No cipher " + algo + " for you!")
+
+ def __del__(self):
+ botan.botan_cipher_destroy.argtypes = [c_void_p]
+ botan.botan_cipher_destroy(self.cipher)
+
+ def tag_length(self):
+ botan.botan_cipher_tag_length.argtypes = [c_void_p,POINTER(c_size_t)]
+ l = c_size_t(0)
+ botan.botan_cipher_tag_size(self.cipher, byref(l))
+ return l.value
+
+ def default_nonce_length(self):
+ botan.botan_cipher_default_nonce_length.argtypes = [c_void_p, POINTER(c_size_t)]
+ l = c_size_t(0)
+ botan.botan_cipher_default_nonce_length(self.cipher, byref(l))
+ return l.value
+
+ def update_granularity(self):
+ botan.botan_cipher_update_granularity.argtypes = [c_void_p, POINTER(c_size_t)]
+ l = c_size_t(0)
+ botan.botan_cipher_update_granularity(self.cipher, byref(l))
+ return l.value
+
+ def tag_length(self):
+ botan.botan_cipher_get_tag_length.argtypes = [c_void_p, POINTER(c_size_t)]
+ l = c_size_t(0)
+ botan.botan_cipher_get_tag_length(self.cipher, byref(l))
+ return l.value
+
+ def is_authenticated(self):
+ return self.tag_length() > 0
+
+ def valid_nonce_length(self, nonce_len):
+ botan.botan_cipher_valid_nonce_length.argtypes = [c_void_p, c_size_t]
+ rc = botan.botan_cipher_valid_nonce_length(self.cipher, nonce_len)
+ if rc < 0:
+ raise Exception('Error calling valid_nonce_length')
+ return True if rc == 1 else False
+
+ def clear(self):
+ botan.botan_cipher_clear.argtypes = [c_void_p]
+ botan.botan_cipher_clear(self.cipher)
+
+ def set_key(self, key):
+ botan.botan_cipher_set_key.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_cipher_set_key(self.cipher, key, len(key))
+
+
+ def start(self, nonce):
+ botan.botan_cipher_start.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_cipher_start(self.cipher, nonce, len(nonce))
+
+ def _update(self, txt, final):
+ botan.botan_cipher_update.argtypes = [c_void_p, c_uint32,
+ POINTER(c_char), c_size_t, POINTER(c_size_t),
+ POINTER(c_char), c_size_t, POINTER(c_size_t)]
+
+ inp = txt if txt else ''
+ inp_sz = c_size_t(len(inp))
+ inp_consumed = c_size_t(0)
+ out = create_string_buffer(inp_sz.value + (self.tag_length() if final else 0))
+ out_sz = c_size_t(len(out))
+ out_written = c_size_t(0)
+ flags = c_uint32(1 if final else 0)
+
+ botan.botan_cipher_update(self.cipher, flags,
+ out, out_sz, byref(out_written),
+ inp, inp_sz, byref(inp_consumed))
+
+ # buffering not supported yet
+ assert inp_consumed.value == inp_sz.value
+ return out.raw[0:out_written.value]
+
+ def update(self, txt):
+ return self._update(txt, False)
+
+ def finish(self, txt = None):
+ return self._update(txt, True)
+
+
+"""
+Bcrypt
+TODO: might not be enabled - handle that gracefully!
+"""
+def generate_bcrypt(passwd, rng, work_factor = 10):
+ botan.botan_bcrypt_generate.argtypes = [POINTER(c_char), c_size_t, c_char_p, c_void_p, c_size_t]
+ out = create_string_buffer(61)
+ rc = botan.botan_bcrypt_generate(out, sizeof(out), passwd, rng.rng, c_size_t(work_factor))
+
+ if rc != 0:
+ raise Exception('botan bcrypt failed, error %s' % (rc))
+ return str(out.raw)
+
+def check_bcrypt(passwd, bcrypt):
+ rc = botan.botan_bcrypt_is_valid(passwd, bcrypt)
+ return (rc == 0)
+
+"""
+PBKDF
+"""
+def pbkdf(algo, password, out_len, iterations, salt):
+ botan.botan_pbkdf.argtypes = [c_char_p, POINTER(c_char), c_size_t, c_char_p, c_void_p, c_size_t, c_size_t]
+ out_buf = create_string_buffer(out_len)
+ botan.botan_pbkdf(algo, out_buf, out_len, password, salt, len(salt), iterations)
+ return out_buf.raw
+
+def pbkdf_timed(algo, password, out_len, rng, ms_to_run, salt_len = 12):
+ botan.botan_pbkdf_timed.argtypes = [c_char_p, POINTER(c_char), c_size_t, c_char_p,
+ c_void_p, c_size_t, c_size_t, POINTER(c_size_t)]
+ out_buf = create_string_buffer(out_len)
+ salt = rng.get(salt_len)
+ iterations = c_size_t(0)
+ botan.botan_pbkdf_timed(algo, out_buf, out_len, password, salt, len(salt), ms_to_run, byref(iterations))
+ return (salt,iterations.value,out_buf.raw)
+
+"""
+KDF
+"""
+def kdf(algo, secret, out_len, salt):
+ botan.botan_kdf.argtypes = [c_char_p, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t, POINTER(c_char), c_size_t]
+ out_buf = create_string_buffer(out_len)
+ out_sz = c_size_t(out_len)
+ botan.botan_kdf(algo, out_buf, out_sz, secret, len(secret), salt, len(salt))
+ return out_buf.raw[0:out_sz.value]
+
+"""
+Public and private keys
+"""
+class public_key(object):
+ def __init__(self, obj = c_void_p(0)):
+ self.pubkey = obj
+
+ def __del__(self):
+ botan.botan_pubkey_destroy.argtypes = [c_void_p]
+ botan.botan_pubkey_destroy(self.pubkey)
+
+ def fingerprint(self, hash = 'SHA-256'):
+ botan.botan_pubkey_fingerprint.argtypes = [c_void_p, c_char_p,
+ POINTER(c_char), POINTER(c_size_t)]
+
+ n = hash_function(hash).output_length()
+ buf = create_string_buffer(n)
+ buf_len = c_size_t(n)
+ botan.botan_pubkey_fingerprint(self.pubkey, hash, buf, byref(buf_len))
+ return buf[0:buf_len.value].encode('hex')
+
+class private_key(object):
+ def __init__(self, alg, param, rng):
+ botan.botan_privkey_create_rsa.argtypes = [c_void_p, c_void_p, c_size_t]
+ botan.botan_privkey_create_ecdsa.argtypes = [c_void_p, c_void_p, c_char_p]
+ botan.botan_privkey_create_ecdh.argtypes = [c_void_p, c_void_p, c_char_p]
+
+ self.privkey = c_void_p(0)
+ if alg == 'rsa':
+ botan.botan_privkey_create_rsa(byref(self.privkey), rng.rng, param)
+ elif alg == 'ecdsa':
+ botan.botan_privkey_create_ecdsa(byref(self.privkey), rng.rng, param)
+ elif alg == 'ecdh':
+ botan.botan_privkey_create_ecdh(byref(self.privkey), rng.rng, param)
+ else:
+ raise Exception('Unknown public key algo ' + alg)
+
+ if self.privkey is None:
+ raise Exception('Error creating ' + alg + ' key')
+
+ def __del__(self):
+ botan.botan_privkey_destroy.argtypes = [c_void_p]
+ botan.botan_privkey_destroy(self.privkey)
+
+ def get_public_key(self):
+ botan.botan_privkey_export_pubkey.argtypes = [c_void_p, c_void_p]
+
+ pub = c_void_p(0)
+ botan.botan_privkey_export_pubkey(byref(pub), self.privkey)
+ return public_key(pub)
+
+ def export(self):
+ botan.botan_privkey_export.argtypes = [c_void_p,POINTER(c_char),c_void_p]
+
+ n = 4096
+ buf = create_string_buffer(n)
+ buf_len = c_size_t(n)
+
+ rc = botan.botan_privkey_export(self.privkey, buf, byref(buf_len))
+ if rc != 0:
+ buf = create_string_buffer(buf_len.value)
+ botan.botan_privkey_export(self.privkey, buf, byref(buf_len))
+ return buf[0:buf_len.value]
+
+
+class pk_op_encrypt(object):
+ def __init__(self, key, padding, rng):
+ botan.botan_pk_op_encrypt_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32]
+ self.op = c_void_p(0)
+ flags = 0 # always zero in this ABI
+ botan.botan_pk_op_encrypt_create(byref(self.op), key.pubkey, padding, flags)
+ if not self.op:
+ raise Exception("No pk op for you")
+
+ def __del__(self):
+ botan.botan_pk_op_encrypt_destroy.argtypes = [c_void_p]
+ botan.botan_pk_op_encrypt_destroy(self.op)
+
+ def encrypt(self, msg, rng):
+ botan.botan_pk_op_encrypt.argtypes = [c_void_p, c_void_p,
+ POINTER(c_char), POINTER(c_size_t),
+ POINTER(c_char), c_size_t]
+
+ outbuf_sz = c_size_t(4096) #?!?!
+ outbuf = create_string_buffer(outbuf_sz.value)
+ botan.botan_pk_op_encrypt(self.op, rng.rng, outbuf, byref(outbuf_sz), msg, len(msg))
+ return outbuf.raw[0:outbuf_sz.value]
+
+class pk_op_decrypt(object):
+ def __init__(self, key, padding):
+ botan.botan_pk_op_decrypt_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32]
+ self.op = c_void_p(0)
+ flags = 0 # always zero in this ABI
+ botan.botan_pk_op_decrypt_create(byref(self.op), key.privkey, padding, flags)
+ if not self.op:
+ raise Exception("No pk op for you")
+
+ def __del__(self):
+ botan.botan_pk_op_decrypt_destroy.argtypes = [c_void_p]
+ botan.botan_pk_op_decrypt_destroy(self.op)
+
+ def decrypt(self, msg):
+ botan.botan_pk_op_decrypt.argtypes = [c_void_p,
+ POINTER(c_char), POINTER(c_size_t),
+ POINTER(c_char), c_size_t]
+
+ outbuf_sz = c_size_t(4096) #?!?!
+ outbuf = create_string_buffer(outbuf_sz.value)
+ botan.botan_pk_op_decrypt(self.op, outbuf, byref(outbuf_sz), msg, len(msg))
+ return outbuf.raw[0:outbuf_sz.value]
+
+class pk_op_sign(object):
+ def __init__(self, key, padding):
+ botan.botan_pk_op_sign_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32]
+ self.op = c_void_p(0)
+ flags = 0 # always zero in this ABI
+ botan.botan_pk_op_sign_create(byref(self.op), key.privkey, padding, flags)
+ if not self.op:
+ raise Exception("No pk op for you")
+
+ def __del__(self):
+ botan.botan_pk_op_sign_destroy.argtypes = [c_void_p]
+ botan.botan_pk_op_sign_destroy(self.op)
+
+ def update(self, msg):
+ botan.botan_pk_op_sign_update.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_pk_op_sign_update(self.op, msg, len(msg))
+
+ def finish(self, rng):
+ botan.botan_pk_op_sign_finish.argtypes = [c_void_p, c_void_p, POINTER(c_char), POINTER(c_size_t)]
+ outbuf_sz = c_size_t(4096) #?!?!
+ outbuf = create_string_buffer(outbuf_sz.value)
+ botan.botan_pk_op_sign_finish(self.op, rng.rng, outbuf, byref(outbuf_sz))
+ return outbuf.raw[0:outbuf_sz.value]
+
+class pk_op_verify(object):
+ def __init__(self, key, padding):
+ botan.botan_pk_op_verify_create.argtypes = [c_void_p, c_void_p, c_char_p, c_uint32]
+ self.op = c_void_p(0)
+ flags = 0 # always zero in this ABI
+ botan.botan_pk_op_verify_create(byref(self.op), key.pubkey, padding, flags)
+ if not self.op:
+ raise Exception("No pk op for you")
+
+ def __del__(self):
+ botan.botan_pk_op_verify_destroy.argtypes = [c_void_p]
+ botan.botan_pk_op_verify_destroy(self.op)
+
+ def update(self, msg):
+ botan.botan_pk_op_verify_update.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ botan.botan_pk_op_verify_update(self.op, msg, len(msg))
+
+ def check_signature(self, signature):
+ botan.botan_pk_op_verify_finish.argtypes = [c_void_p, POINTER(c_char), c_size_t]
+ rc = botan.botan_pk_op_verify_finish(self.op, signature, len(signature))
+ if rc == 0:
+ return True
+ return False
+
+"""
+Tests and examples
+"""
+def test():
+ print version_string()
+ print version_major(), version_minor(), version_patch()
+
+
+ print kdf('KDF2(SHA-1)', '701F3480DFE95F57941F804B1B2413EF'.decode('hex'), 7, '55A4E9DD5F4CA2EF82'.decode('hex')).encode('hex')
+
+ print pbkdf('PBKDF2(SHA-1)', '', 32, 10000, '0001020304050607'.decode('hex')).encode('hex').upper()
+ print '59B2B1143B4CB1059EC58D9722FB1C72471E0D85C6F7543BA5228526375B0127'
+
+ r = rng("user")
+ (salt,iterations,psk) = pbkdf_timed('PBKDF2(SHA-256)', 'xyz', 32, r, 200, 12)
+ print salt.encode('hex'), iterations
+ print 'x', psk.encode('hex')
+ print 'y', pbkdf('PBKDF2(SHA-256)', 'xyz', 32, iterations, salt).encode('hex')
+
+ print r.get(42).encode('hex'), r.get(13).encode('hex'), r.get(9).encode('hex')
+
+ h = hash_function('MD5')
+ assert h.output_length() == 16
+ h.update('h')
+ h.update('i')
+ print "md5", h.final().encode('hex')
+
+ gcm = cipher('AES-128/GCM')
+ gcm.set_key('00000000000000000000000000000000'.decode('hex'))
+ gcm.start('000000000000000000000000'.decode('hex'))
+ gcm.update('')
+ gcm.update('')
+ print 'gcm', gcm.finish('00000000000000000000000000000000'.decode('hex')).encode('hex')
+
+ rsapriv = private_key('rsa', 1536, r)
+
+ dec = pk_op_decrypt(rsapriv, "EME1(SHA-256)")
+
+ rsapub = rsapriv.get_public_key()
+ print rsapub.fingerprint("SHA-1")
+
+ enc = pk_op_encrypt(rsapub, "EME1(SHA-256)", r)
+
+ ctext = enc.encrypt('foof', r)
+ print ctext.encode('hex')
+ print dec.decrypt(ctext)
+
+ signer = pk_op_sign(rsapriv, 'EMSA4(SHA-384)')
+
+ signer.update('mess')
+ signer.update('age')
+ sig = signer.finish(r)
+
+ r.reseed(200)
+ print sig.encode('hex')
+
+ verify = pk_op_verify(rsapub, 'EMSA4(SHA-384)')
+
+ verify.update('mess')
+ verify.update('age')
+ print "correct sig accepted?", verify.check_signature(sig)
+
+ verify.update('mess of things')
+ verify.update('age')
+ print "bad sig accepted?", verify.check_signature(sig)
+
+ key = private_key('ecdsa', 'secp256r1', r)
+ blob = key.export()
+
+ #f = open('key.ber','wb')
+ #f.write(blob)
+ #f.close()
+
+
+def main(args = None):
+ if args is None:
+ args = sys.argv
+ test()
+
+if __name__ == '__main__':
+ sys.exit(main())