1
2
3"""
4    demo_dynamic.py                                    v2b
5
6    This program demonstrates Python's use of the dynamic
7    language support additions to LTC, namely access to LTC
8    constants, struct and union sizes, and the binding of a
9    math package to LTC.  Also provided are simple code
10    fragments to illustrate how one might write a Python
11    wrapper for LTC and how an app might call the wrapper.
12    This or a similar model should work for Ruby and other
13    dynamic languages.
14
15    This instance uses Python's ctypes and requires a single
16    .dylib linking together LTC and a math library.  Building
17    a single .dylib is needed because LTC wants a fairly tight
18    relationship between itself and the mathlib.  (ctypes can
19    load multiple .dylibs, but it does not support this level
20    of tight coupling between otherwise independent libraries.)
21
22    My .dylib was created on OSX/macOS with the following:
23        sudo make -j5 -f makefile.shared                        \
24            CFLAGS="-DUSE_TFM -DTFM_DESC -I/usr/local/include"  \
25            EXTRALIBS=/usr/local/lib/libtfm.a  install
26
27    For python 2.7.12 on Ubuntu Xenial the following worked for
28    me (without MPI support):
29        sudo make -f makefile.shared install PREFIX="/usr"
30
31    Reminder: you don't need to bind in a math library unless
32              you are going to use LTC functions that need a
33              mathlib.  For example, public key crypto requires
34              a mathlib; hashing and symmetric encryption do not.
35
36    ------
37
38    This code was originally written for Python 2.7 with the
39    ctypes standard library.  This version is modified to run
40    under both Python 2.7 and 3.6.
41
42    Arguably the biggest change for Python3 has to do with
43    strings.  Under Python2, native strings are ASCII bytes and
44    passing them to LTC is natural and requires no conversion.
45    Under Python3 all native strings are Unicode which requires
46    they be converted to bytes before use by LTC.
47
48    Note the following for Python3.
49        - ASCII keys, IVs and other string arguments must be
50          'bytes'.  Define them with a 'b' prefix or convert
51          via the 'bytes()' function.
52        - "strings" returned from LTC are bytes and conversion
53          to Unicode might be necessary for proper printing.
54          If so, use <string>.decode('utf-8').
55        - The Python2 'print' statement becomes a function in
56          Python3 which requires parenthesis, eg. 'print()'.
57
58    NB: Unicode is achieved under Python2 by either defining
59        a Unicode string with a 'u' prefix or passing ASCII
60        strings thru the 'unicode()' function.
61
62    Larry Bugbee
63    March 2014      v1
64    August 2017     v2b
65
66"""
67
68
69import sys
70from ctypes import *
71from ctypes.util import find_library
72
73# switches to enable/disable selected output
74SHOW_ALL_CONSTANTS      = True
75SHOW_ALL_SIZES          = True
76SHOW_SELECTED_CONSTANTS = True
77SHOW_SELECTED_SIZES     = True
78SHOW_BUILD_OPTIONS_ALGS = True
79SHOW_SHA256_EXAMPLE     = True
80SHOW_CHACHA_EXAMPLE     = True
81
82print(' ')
83print('  demo_dynamic.py')
84
85def inprint(s, indent=0):
86    "prints strings indented, including multline strings"
87    for line in s.split('\n'):
88        print(' '*indent + line)
89
90#-------------------------------------------------------------------------------
91# load the .dylib
92
93libname = 'tomcrypt'
94libpath = find_library(libname)
95print(' ')
96print('  path to library %s: %s' % (libname, libpath))
97
98LTC = cdll.LoadLibrary(libpath)
99print('  loaded: %s' % LTC)
100print(' ')
101
102
103#-------------------------------------------------------------------------------
104# get list of all supported constants followed by a list of all
105# supported sizes.  One alternative: these lists may be parsed
106# and used as needed.
107
108if SHOW_ALL_CONSTANTS:
109    print('-'*60)
110    print('  all supported constants and their values:')
111
112    # get size to allocate for constants output list
113    str_len = c_int(0)
114    ret = LTC.crypt_list_all_constants(None, byref(str_len))
115    print('    need to allocate %d bytes to build list \n' % str_len.value)
116
117    # allocate that size and get (name, size) pairs, each pair
118    # separated by a newline char.
119    names_sizes = c_buffer(str_len.value)
120    ret = LTC.crypt_list_all_constants(names_sizes, byref(str_len))
121    print(names_sizes.value.decode("utf-8"))
122    print(' ')
123
124
125if SHOW_ALL_SIZES:
126    print('-'*60)
127    print('  all supported sizes:')
128
129    # get size to allocate for sizes output list
130    str_len = c_int(0)
131    ret = LTC.crypt_list_all_sizes(None, byref(str_len))
132    print('    need to allocate %d bytes to build list \n' % str_len.value)
133
134    # allocate that size and get (name, size) pairs, each pair
135    # separated by a newline char.
136    names_sizes = c_buffer(str_len.value)
137    ret = LTC.crypt_list_all_sizes(names_sizes, byref(str_len))
138    print(names_sizes.value.decode("utf-8"))
139    print(' ')
140
141
142#-------------------------------------------------------------------------------
143# get individually named constants and sizes
144
145if SHOW_SELECTED_CONSTANTS:
146    print('-'*60)
147    print('\n  selected constants:')
148
149    names = [
150        b'ENDIAN_LITTLE',
151        b'ENDIAN_64BITWORD',
152        b'PK_PUBLIC',
153        b'LTC_MILLER_RABIN_REPS',
154        b'CTR_COUNTER_BIG_ENDIAN',
155    ]
156    for name in names:
157        const_value = c_int(0)
158        rc = LTC.crypt_get_constant(name, byref(const_value))
159        value = const_value.value
160        print('    %-25s  %d' % (name.decode("utf-8"), value))
161    print(' ')
162
163if SHOW_SELECTED_SIZES:
164    print('-'*60)
165    print('\n  selected sizes:')
166
167    names = [
168        b'rijndael_key',
169        b'rsa_key',
170        b'symmetric_CTR',
171        b'twofish_key',
172        b'ecc_point',
173        b'gcm_state',
174        b'sha512_state',
175    ]
176    for name in names:
177        size_value = c_int(0)
178        rc = LTC.crypt_get_size(name, byref(size_value))
179        value = size_value.value
180        print('    %-25s  %d' % (name.decode("utf-8"), value))
181    print(' ')
182
183
184#-------------------------------------------------------------------------------
185#-------------------------------------------------------------------------------
186# LibTomCrypt exposes one interesting string that can be accessed
187# via Python's ctypes module, "crypt_build_settings", which
188# provides a list of this build's compiler switches and supported
189# algorithms.  If someday LTC exposes other interesting strings,
190# they can be found with:
191#   nm /usr/local/lib/libtomcrypt.dylib | grep " D "
192
193def get_named_string(lib, name):
194    return c_char_p.in_dll(lib, name).value.decode("utf-8")
195
196if SHOW_BUILD_OPTIONS_ALGS:
197    print('-'*60)
198    print('This is a string compiled into LTC showing compile')
199    print('options and algorithms supported by this build \n')
200#    print(get_named_string(LTC, 'crypt_build_settings'))
201    inprint(get_named_string(LTC, 'crypt_build_settings'), 4)
202
203
204#-------------------------------------------------------------------------------
205#-------------------------------------------------------------------------------
206# here is an example of how Python code can be written to access
207# LTC's implementation of SHA256 and ChaCha,
208
209# - - - - - - - - - - - - -
210# definitions
211
212from binascii import hexlify, unhexlify
213
214def _err2str(err):
215    # define return type
216    errstr = LTC.error_to_string
217    errstr.restype = c_char_p
218    # get and return err string
219    return errstr(err)
220
221def _get_size(name):
222    size = c_int(0)
223    rc = LTC.crypt_get_size(bytes(name), byref(size))
224    if rc != 0:
225        raise Exception('LTC.crypt_get_size(%s) rc = %d' % (name, rc))
226    return size.value
227
228def _get_constant(name):
229    constant = c_int(0)
230    rc = LTC.crypt_get_constant(bytes(name), byref(constant))
231    if rc != 0:
232        raise Exception('LTC.crypt_get_constant(%s) rc = %d' % (name, rc))
233    return constant.value
234
235CRYPT_OK = _get_constant(b'CRYPT_OK')
236
237class SHA256(object):
238    def __init__(self):
239        self.state = c_buffer(_get_size(b'sha256_state'))
240        LTC.sha256_init(byref(self.state))
241    def update(self, data):
242        LTC.sha256_process(byref(self.state), data, len(data))
243    def digest(self):
244        md = c_buffer(32)
245        LTC.sha256_done(byref(self.state), byref(md))
246        return md.raw
247
248class ChaCha(object):
249    def __init__(self, key, rounds):
250        self.state   = c_buffer(_get_size(b'chacha_state'))
251        self.counter = c_int(1)
252        err = LTC.chacha_setup(byref(self.state), key, len(key), rounds)
253        if err != CRYPT_OK:
254            raise Exception('LTC.chacha_setup(), err = %d, "%s"' % (err, _err2str(err)))
255    def set_iv32(self, iv):
256        err = LTC.chacha_ivctr32(byref(self.state), iv, len(iv), byref(self.counter))
257        if err != CRYPT_OK:
258            raise Exception('LTC.chacha_ivctr32(), err = %d, "%s"' % (err, _err2str(err)))
259    def crypt(self, datain):
260        dataout = c_buffer(len(datain))
261        err = LTC.chacha_crypt(byref(self.state), datain, len(datain), byref(dataout))
262        if err != CRYPT_OK:
263            raise Exception('LTC.chacha_crypt(), err = %d, "%s"' % (err, _err2str(err)))
264        return dataout.raw
265
266# - - - - - - - - - - - - -
267# a SHA256 app fragment
268
269if SHOW_SHA256_EXAMPLE:
270    print('-'*60)
271    data = b'hello world'               # we want bytes, not Unicode
272
273    sha256 = SHA256()
274    sha256.update(data)
275    md = sha256.digest()
276
277    template = '\n  the SHA256 digest for "%s" is %s \n'
278    print(template % (data, hexlify(md)))
279
280# - - - - - - - - - - - - -
281# a ChaCha app fragment
282
283if SHOW_CHACHA_EXAMPLE:
284    print('-'*60)
285    key     = b'hownowbrowncow\x00\x00' # exactly 16 or 32 bytes
286    rounds  = 12                        # common values: 8, 12, 20
287    iv      = b'123456789012'           # exactly 12 bytes
288    plain   = b'Kilroy was here, there, and everywhere!'
289
290    cha = ChaCha(key, rounds)
291    cha.set_iv32(iv)
292    cipher = cha.crypt(plain)
293
294    template = '\n  ChaCha%d ciphertext   for "%s" is "%s"'
295    print(template % (rounds, plain, hexlify(cipher)))
296
297    cha.set_iv32(iv)                    # reset to decrypt
298    decrypted = cha.crypt(cipher)
299
300    template = '  ChaCha%d decoded text for "%s" is "%s" \n'
301    print(template % (rounds, plain, decrypted.decode("utf-8")))
302
303# Footnote: Keys should be erased fm memory as soon as possible after use,
304# and that includes Python.  For a tip on how to do that in Python, see
305# http://buggywhip.blogspot.com/2010/12/erase-keys-and-credit-card-numbers-in.html
306
307#-------------------------------------------------------------------------------
308#-------------------------------------------------------------------------------
309#-------------------------------------------------------------------------------
310