2
0

demo_dynamic.py3 11 KB

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