Skip to content

Commit bab115c

Browse files
authored
converted demo_dynamic.py to Python3
1 parent ee55c4e commit bab115c

1 file changed

Lines changed: 314 additions & 0 deletions

File tree

demos/demo_dynamic.py3

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

0 commit comments

Comments
 (0)