File: demo_dynamic.py

package info (click to toggle)
libtomcrypt 1.18.2-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 9,472 kB
  • sloc: ansic: 62,544; python: 370; perl: 330; sh: 260; java: 177; makefile: 155
file content (309 lines) | stat: -rw-r--r-- 10,688 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309


"""
    demo_dynamic.py                                    v2b

    This program demonstrates Python's use of the dynamic
    language support additions to LTC, namely access to LTC
    constants, struct and union sizes, and the binding of a
    math package to LTC.  Also provided are simple code
    fragments to illustrate how one might write a Python
    wrapper for LTC and how an app might call the wrapper.
    This or a similar model should work for Ruby and other
    dynamic languages.

    This instance uses Python's ctypes and requires a single
    .dylib linking together LTC and a math library.  Building
    a single .dylib is needed because LTC wants a fairly tight
    relationship between itself and the mathlib.  (ctypes can
    load multiple .dylibs, but it does not support this level
    of tight coupling between otherwise independent libraries.)

    My .dylib was created on OSX/macOS with the following:
        sudo make -j5 -f makefile.shared                        \
            CFLAGS="-DUSE_TFM -DTFM_DESC -I/usr/local/include"  \
            EXTRALIBS=/usr/local/lib/libtfm.a  install

    For python 2.7.12 on Ubuntu Xenial the following worked for
    me (without MPI support):
        sudo make -f makefile.shared install PREFIX="/usr"

    Reminder: you don't need to bind in a math library unless
              you are going to use LTC functions that need a
              mathlib.  For example, public key crypto requires
              a mathlib; hashing and symmetric encryption do not.

    ------

    This code was originally written for Python 2.7 with the
    ctypes standard library.  This version is modified to run
    under both Python 2.7 and 3.6.

    Arguably the biggest change for Python3 has to do with
    strings.  Under Python2, native strings are ASCII bytes and
    passing them to LTC is natural and requires no conversion.
    Under Python3 all native strings are Unicode which requires
    they be converted to bytes before use by LTC.

    Note the following for Python3.
        - ASCII keys, IVs and other string arguments must be
          'bytes'.  Define them with a 'b' prefix or convert
          via the 'bytes()' function.
        - "strings" returned from LTC are bytes and conversion
          to Unicode might be necessary for proper printing.
          If so, use <string>.decode('utf-8').
        - The Python2 'print' statement becomes a function in
          Python3 which requires parenthesis, eg. 'print()'.

    NB: Unicode is achieved under Python2 by either defining
        a Unicode string with a 'u' prefix or passing ASCII
        strings thru the 'unicode()' function.

    Larry Bugbee
    March 2014      v1
    August 2017     v2b

"""


import sys
from ctypes import *
from ctypes.util import find_library

# switches to enable/disable selected output
SHOW_ALL_CONSTANTS      = True
SHOW_ALL_SIZES          = True
SHOW_SELECTED_CONSTANTS = True
SHOW_SELECTED_SIZES     = True
SHOW_BUILD_OPTIONS_ALGS = True
SHOW_SHA256_EXAMPLE     = True
SHOW_CHACHA_EXAMPLE     = True

print(' ')
print('  demo_dynamic.py')

def inprint(s, indent=0):
    "prints strings indented, including multline strings"
    for line in s.split('\n'):
        print(' '*indent + line)

#-------------------------------------------------------------------------------
# load the .dylib

libname = 'tomcrypt'
libpath = find_library(libname)
print(' ')
print('  path to library %s: %s' % (libname, libpath))

LTC = cdll.LoadLibrary(libpath)
print('  loaded: %s' % LTC)
print(' ')


#-------------------------------------------------------------------------------
# get list of all supported constants followed by a list of all
# supported sizes.  One alternative: these lists may be parsed
# and used as needed.

if SHOW_ALL_CONSTANTS:
    print('-'*60)
    print('  all supported constants and their values:')

    # get size to allocate for constants output list
    str_len = c_int(0)
    ret = LTC.crypt_list_all_constants(None, byref(str_len))
    print('    need to allocate %d bytes to build list \n' % str_len.value)

    # allocate that size and get (name, size) pairs, each pair
    # separated by a newline char.
    names_sizes = c_buffer(str_len.value)
    ret = LTC.crypt_list_all_constants(names_sizes, byref(str_len))
    print(names_sizes.value.decode("utf-8"))
    print(' ')


if SHOW_ALL_SIZES:
    print('-'*60)
    print('  all supported sizes:')

    # get size to allocate for sizes output list
    str_len = c_int(0)
    ret = LTC.crypt_list_all_sizes(None, byref(str_len))
    print('    need to allocate %d bytes to build list \n' % str_len.value)

    # allocate that size and get (name, size) pairs, each pair
    # separated by a newline char.
    names_sizes = c_buffer(str_len.value)
    ret = LTC.crypt_list_all_sizes(names_sizes, byref(str_len))
    print(names_sizes.value.decode("utf-8"))
    print(' ')


#-------------------------------------------------------------------------------
# get individually named constants and sizes

if SHOW_SELECTED_CONSTANTS:
    print('-'*60)
    print('\n  selected constants:')

    names = [
        b'ENDIAN_LITTLE',
        b'ENDIAN_64BITWORD',
        b'PK_PUBLIC',
        b'LTC_MILLER_RABIN_REPS',
        b'CTR_COUNTER_BIG_ENDIAN',
    ]
    for name in names:
        const_value = c_int(0)
        rc = LTC.crypt_get_constant(name, byref(const_value))
        value = const_value.value
        print('    %-25s  %d' % (name.decode("utf-8"), value))
    print(' ')

if SHOW_SELECTED_SIZES:
    print('-'*60)
    print('\n  selected sizes:')

    names = [
        b'rijndael_key',
        b'rsa_key',
        b'symmetric_CTR',
        b'twofish_key',
        b'ecc_point',
        b'gcm_state',
        b'sha512_state',
    ]
    for name in names:
        size_value = c_int(0)
        rc = LTC.crypt_get_size(name, byref(size_value))
        value = size_value.value
        print('    %-25s  %d' % (name.decode("utf-8"), value))
    print(' ')


#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# LibTomCrypt exposes one interesting string that can be accessed
# via Python's ctypes module, "crypt_build_settings", which
# provides a list of this build's compiler switches and supported
# algorithms.  If someday LTC exposes other interesting strings,
# they can be found with:
#   nm /usr/local/lib/libtomcrypt.dylib | grep " D "

def get_named_string(lib, name):
    return c_char_p.in_dll(lib, name).value.decode("utf-8")

if SHOW_BUILD_OPTIONS_ALGS:
    print('-'*60)
    print('This is a string compiled into LTC showing compile')
    print('options and algorithms supported by this build \n')
#    print(get_named_string(LTC, 'crypt_build_settings'))
    inprint(get_named_string(LTC, 'crypt_build_settings'), 4)


#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
# here is an example of how Python code can be written to access
# LTC's implementation of SHA256 and ChaCha,

# - - - - - - - - - - - - -
# definitions

from binascii import hexlify, unhexlify

def _err2str(err):
    # define return type
    errstr = LTC.error_to_string
    errstr.restype = c_char_p
    # get and return err string
    return errstr(err)

def _get_size(name):
    size = c_int(0)
    rc = LTC.crypt_get_size(bytes(name), byref(size))
    if rc != 0:
        raise Exception('LTC.crypt_get_size(%s) rc = %d' % (name, rc))
    return size.value

def _get_constant(name):
    constant = c_int(0)
    rc = LTC.crypt_get_constant(bytes(name), byref(constant))
    if rc != 0:
        raise Exception('LTC.crypt_get_constant(%s) rc = %d' % (name, rc))
    return constant.value

CRYPT_OK = _get_constant(b'CRYPT_OK')

class SHA256(object):
    def __init__(self):
        self.state = c_buffer(_get_size(b'sha256_state'))
        LTC.sha256_init(byref(self.state))
    def update(self, data):
        LTC.sha256_process(byref(self.state), data, len(data))
    def digest(self):
        md = c_buffer(32)
        LTC.sha256_done(byref(self.state), byref(md))
        return md.raw

class ChaCha(object):
    def __init__(self, key, rounds):
        self.state   = c_buffer(_get_size(b'chacha_state'))
        self.counter = c_int(1)
        err = LTC.chacha_setup(byref(self.state), key, len(key), rounds)
        if err != CRYPT_OK:
            raise Exception('LTC.chacha_setup(), err = %d, "%s"' % (err, _err2str(err)))
    def set_iv32(self, iv):
        err = LTC.chacha_ivctr32(byref(self.state), iv, len(iv), byref(self.counter))
        if err != CRYPT_OK:
            raise Exception('LTC.chacha_ivctr32(), err = %d, "%s"' % (err, _err2str(err)))
    def crypt(self, datain):
        dataout = c_buffer(len(datain))
        err = LTC.chacha_crypt(byref(self.state), datain, len(datain), byref(dataout))
        if err != CRYPT_OK:
            raise Exception('LTC.chacha_crypt(), err = %d, "%s"' % (err, _err2str(err)))
        return dataout.raw

# - - - - - - - - - - - - -
# a SHA256 app fragment

if SHOW_SHA256_EXAMPLE:
    print('-'*60)
    data = b'hello world'               # we want bytes, not Unicode

    sha256 = SHA256()
    sha256.update(data)
    md = sha256.digest()

    template = '\n  the SHA256 digest for "%s" is %s \n'
    print(template % (data, hexlify(md)))

# - - - - - - - - - - - - -
# a ChaCha app fragment

if SHOW_CHACHA_EXAMPLE:
    print('-'*60)
    key     = b'hownowbrowncow\x00\x00' # exactly 16 or 32 bytes
    rounds  = 12                        # common values: 8, 12, 20
    iv      = b'123456789012'           # exactly 12 bytes
    plain   = b'Kilroy was here, there, and everywhere!'

    cha = ChaCha(key, rounds)
    cha.set_iv32(iv)
    cipher = cha.crypt(plain)

    template = '\n  ChaCha%d ciphertext   for "%s" is "%s"'
    print(template % (rounds, plain, hexlify(cipher)))

    cha.set_iv32(iv)                    # reset to decrypt
    decrypted = cha.crypt(cipher)

    template = '  ChaCha%d decoded text for "%s" is "%s" \n'
    print(template % (rounds, plain, decrypted.decode("utf-8")))

# Footnote: Keys should be erased fm memory as soon as possible after use,
# and that includes Python.  For a tip on how to do that in Python, see
# http://buggywhip.blogspot.com/2010/12/erase-keys-and-credit-card-numbers-in.html

#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------
#-------------------------------------------------------------------------------