#!/usr/bin/env python # coding: utf-8 """ pyEstream: a Python wrapper for the eSTREAM Streaming Ciphers -------------------------------- pyEstream is a Python demonstration program that uses ctypes to access a number of the new stream ciphers submitted to the EU eSTREAM competition. Now in Phase 3, the winners are expected to be announced in May 2008. This program demonstrates a way to access the eSTREAM Phase 3 algorithms CryptMT, Dragon, HC-256, LEX, NLS, and Rabbit. Both Salsa20 and Sosemanuk were implemented earlier and may be found at: http://www.seanet.com/~bugbee/crypto/salsa20 http://www.seanet.com/~bugbee/crypto/sosemanuk Generally speaking, the eSTREAM "design-to" goals are design strength 128 bits key length 128 or 256 bits, more permitted IV, aka nonce need to specify chunk size need to specify Compile the version of your choice as a shared library (not as a Python extension), name and install it as libXXXXX.so where XXXXX is one of 'salsa20', 'dragon', 'rabbit, etc. Be sure to change the '_defaultlibname' parameter at the top of this library (see below), or provide the name of the algorithm (all lower case) as an arguement on the commandline. For example: python pyEstream.py nls Sample usage: from pyEstream import eSTREAM es = eSTREAM(algname, key, IV) dataout = es.encryptBytes(datain) # same for decrypt Prerequisites: - Python 2.5 (or 2.4 with ctypes added) - gcc 4.x (gcc 3 should work) This is EXPERIMENTAL software and intended for educational purposes only. To make experimentation less cumbersome, pySalsa20 is also free for any use. THIS PROGRAM IS PROVIDED WITHOUT WARRANTY OR GUARANTEE OF ANY KIND. USE AT YOUR OWN RISK. Enjoy, Larry Bugbee bugbee@seanet.com April 2007 """ from ctypes import * import sys _version = '1' _defaultlibname = 'rabbit' # dragon rabbit lex nls cryptmt3 hc128 hc256 #-------------------------------------------------------------------------- class eSTREAM(object): def __init__(self, libname=_defaultlibname, key=None, IV=None): self._lib = loadLib(libname) self._ctxSize = self._lib.getCtxSize() self._ctx = c_buffer(self._ctxSize) self._chunkSize = self._lib.getChunkSize() self._prevBlkOK = True self._keySizes = self._getSizes(self._lib.isValidKeySize) self._IVSizes = self._getSizes(self._lib.isValidIVSize) self.IV = None if key: self.setKey(key) if IV: self.setIV(IV) # not all implementations compile the same functions so # let's see which they did compile... try: self.encrypt_func = self._lib.ECRYPT_encrypt_bytes self.decrypt_func = self._lib.ECRYPT_decrypt_bytes self.process = 0 # print 'found ECRYPT_encrypt_bytes' except: try: self.encrypt_func = self._lib.ECRYPT_process_bytes self.decrypt_func = self.encrypt_func self.process = 1 # print 'found ECRYPT_process_bytes' except: assert False, ' *** did not find a suitable encryption function ***' def setKey(self, key): numbits = len(key)*8 assert numbits in self._keySizes, '%d-bit key not %s bits' % (numbits, str(self._keySizes)) self._lib.ECRYPT_keysetup(byref(self._ctx), key, numbits, self._IVSizes[-1]) def setIV(self, IV): numbits = len(IV)*8 assert numbits in self._IVSizes, '%d-bit IV not %s bits' % (numbits, str(self._IVSizes)) self.IV = IV self._prevBlkOK = True self._lib.ECRYPT_ivsetup(byref(self._ctx), IV) setNonce = setIV # support an alternate name def reset(self): assert self.IV, 'IV not set' self._lib.ECRYPT_ivsetup(self._ctx, self.IV) self._prevBlkOK = True def encryptBytes(self, data): assert type(data) == type(''), 'data must be byte string' assert self._prevBlkOK, 'previous chunk not multiple of %d bytes' % self._chunkSize lendata = len(data) munged = c_buffer(lendata) if not self.process: # print 'using ECRYPT_encrypt_bytes', self.encrypt_func self.encrypt_func(byref(self._ctx), c_buffer(data), byref(munged), lendata) else: # print 'using ECRYPT_process_bytes', self.encrypt_func # the 0 or 1 in next line is not used, but must be there self.encrypt_func(0, byref(self._ctx), c_buffer(data), byref(munged), lendata) # some ciphers require multiple chunks of data be a # multiple of some block size if self._chunkSize == 0: self._prevBlkOK = True else: self._prevBlkOK = not lendata % self._chunkSize return munged.raw[:lendata] def decryptBytes(self, data): assert type(data) == type(''), 'data must be byte string' assert self._prevBlkOK, 'previous chunk not multiple of %d bytes' % self._chunkSize lendata = len(data) munged = c_buffer(lendata) if not self.process: # print 'using ECRYPT_decrypt_bytes', self.encrypt_func self.decrypt_func(byref(self._ctx), c_buffer(data), byref(munged), lendata) else: # print 'using ECRYPT_process_bytes', self.encrypt_func # while 0 or 1 in next line is not used, it must be there self.decrypt_func(1, byref(self._ctx), c_buffer(data), byref(munged), lendata) # some ciphers require multiple chunks of data be fed in a # multiple of some block size if self._chunkSize == 0: self._prevBlkOK = True else: self._prevBlkOK = not lendata % self._chunkSize return munged.raw[:lendata] # decryptBytes = encryptBytes # sometimes encrypt and decrypt # can use the same function def _getSizes(self, func): sizes = [] for i in range(257): if func(i): sizes.append(i) return sizes # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # load the shared library into ctypes def loadLib(name): # prefix might need to change for some platforms ??? prefix = '' # get the correct library suffix libsuffixes = {'darwin': '.so', # .dylib ??? 'linux': '.so', 'linux2': '.so', 'win32': '.dll'} # .lib ??? try: libsuffix = libsuffixes[sys.platform] except: raise Exception('library suffix for "%s" is what?' % sys.platform) libname = prefix+'lib'+name+libsuffix return CDLL(libname) # load and return the library #-------------------------------------------------------------------------- # utilities def savetofile(filename, content): "write content to a file [as binary]" f = open(filename, 'wb') f.write(content) f.close() def loadfmfile(filename): "get [binary] content from a file" f = open(filename, 'rb') content = f.read() f.close() return content def bytestring(hex): "remove whitespace and convert hex string to a byte string" return hex.replace(' ', '').replace('\n', '').decode('hex') #-------------------------------------------------------------------------- # a quick self-test def test(algname): print ' pyEstream version %s' % _version print ' loading: %s' % algname es = eSTREAM(algname) keySizes = es._keySizes IVSizes = es._IVSizes print ' key sizes: %s' % str(keySizes) print ' IV sizes: %s' % str(IVSizes) print ' chunk size:', es._chunkSize print ' ctx size:', es._ctxSize # choose some test data if 0: message = loadfmfile('testdata.txt') else: message = 'Kilroy was here! ...there, and everywhere.' nomsize = 128 # make a key key = 'myKey' # funky but it works if nomsize in keySizes: # if nomsize is supported, use it keysize = nomsize / 8 else: # else use the largest supported keysize = keySizes[-1] / 8 key = (key+'*'*keysize)[:keysize] # force to exactly keysize bits # make an IV nonce = 'aNonce' # do better in real life if nomsize in IVSizes: # if nomsize is supported, use it ivsize = nomsize / 8 else: # else use the largest supported ivsize = IVSizes[-1] / 8 IV = (nonce+'*'*ivsize)[:ivsize] # force to exactly ivsize bits # for reasons not yet determined..... # the reference implementations of the various eSTREAM algorithms # respond differently when reinstantiated within the same program. # For example, rabbit and dragon create fresh new contexts without # any apparant problems. lex and nls, on the other hand, want to # be loaded with key and IV, or reset with the IV, as per the # following code. (If some of these algorithms ever become # candidates for production use, their initialization routines # need to be rethought. if algname in ['rabbit', 'dragon', 'hc128', 'hc256', 'cryptmt3']: # hc256 will not work if given a 128 key (needs to be # padded up to 256) if algname == 'hc256' and keysize == 16: key += chr(0)*16 if algname == 'hc256' and ivsize == 16: IV += chr(0)*16 # encrypt es = eSTREAM(algname, key, IV) # create new instance ciphertxt = es.encryptBytes(message) # decrypt es = eSTREAM(algname, key, IV) # create new instance plaintxt = es.decryptBytes(ciphertxt) elif algname in ['lex', 'nls']: # encrypt es.setKey(key) # must use existing instance es.setIV(IV) ciphertxt = es.encryptBytes(message) # decrypt es.reset() # must reset existing instance plaintxt = es.decryptBytes(ciphertxt) else: template = 'algorithm %s not recognized in this version' raise Exception(template % algname) # show results print ' %s' % message print ' %s' % plaintxt if message == plaintxt: print ' *** good ***' else: print ' *** bad ***' # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': import sys if len(sys.argv) == 2: test(sys.argv[1].lower()) else: test(_defaultlibname) else: lib = loadLib(_defaultlibname) #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- #--------------------------------------------------------------------------