crypto.py 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. """A thin ctypes based wrapper for OpenSSL 1.0
  2. """
  3. import os
  4. from ctypes import cdll, c_char_p, c_int, c_uint, c_void_p, POINTER, create_string_buffer
  5. from ctypes.util import find_library
  6. import struct
  7. def _find_libcrypto():
  8. _possible_paths = [
  9. find_library('crypto'),
  10. os.environ.get('ATTIC_LIBCRYPTO_PATH'),
  11. '/usr/local/opt/openssl/lib/libcrypto.dylib', # OS X Brew
  12. '/usr/local/lib/libcrypto.so', # FreeBSD Ports
  13. '/usr/local/ssl/lib/libcrypto.so'
  14. ]
  15. for path in _possible_paths:
  16. try:
  17. lib = cdll.LoadLibrary(path)
  18. if hasattr(lib, 'PKCS5_PBKDF2_HMAC'):
  19. return lib
  20. except OSError:
  21. pass
  22. raise Exception('Failed to find libcrypto version >= 1.0')
  23. libcrypto = _find_libcrypto()
  24. libcrypto.PKCS5_PBKDF2_HMAC.argtypes = (c_char_p, c_int, c_char_p, c_int, c_int, c_void_p, c_int, c_char_p)
  25. libcrypto.EVP_sha256.restype = c_void_p
  26. libcrypto.AES_set_encrypt_key.argtypes = (c_char_p, c_int, c_char_p)
  27. libcrypto.AES_ctr128_encrypt.argtypes = (c_char_p, c_char_p, c_int, c_char_p, c_char_p, c_char_p, POINTER(c_uint))
  28. libcrypto.RAND_bytes.argtypes = (c_char_p, c_int)
  29. libcrypto.RAND_bytes.restype = c_int
  30. _int = struct.Struct('>I')
  31. _long = struct.Struct('>Q')
  32. bytes_to_int = lambda x, offset=0: _int.unpack_from(x, offset)[0]
  33. bytes_to_long = lambda x, offset=0: _long.unpack_from(x, offset)[0]
  34. long_to_bytes = lambda x: _long.pack(x)
  35. def num_aes_blocks(length):
  36. """Return the number of AES blocks required to encrypt/decrypt *length* bytes of data
  37. """
  38. return (length + 15) // 16
  39. def pbkdf2_sha256(password, salt, iterations, size):
  40. """Password based key derivation function 2 (RFC2898)
  41. """
  42. key = create_string_buffer(size)
  43. rv = libcrypto.PKCS5_PBKDF2_HMAC(password, len(password), salt, len(salt), iterations, libcrypto.EVP_sha256(), size, key)
  44. if not rv:
  45. raise Exception('PKCS5_PBKDF2_HMAC failed')
  46. return key.raw
  47. def get_random_bytes(n):
  48. """Return n cryptographically strong pseudo-random bytes
  49. """
  50. buf = create_string_buffer(n)
  51. if libcrypto.RAND_bytes(buf, n) < 1:
  52. raise Exception('RAND_bytes failed')
  53. return buf.raw
  54. class AES:
  55. """A thin wrapper around the OpenSSL AES CTR_MODE cipher
  56. """
  57. def __init__(self, key, iv=None):
  58. self.key = create_string_buffer(2000)
  59. self.iv = create_string_buffer(16)
  60. self.buf = create_string_buffer(16)
  61. self.num = c_uint()
  62. self.reset(key, iv)
  63. def reset(self, key=None, iv=None):
  64. if key:
  65. libcrypto.AES_set_encrypt_key(key, len(key) * 8, self.key)
  66. if iv:
  67. self.iv.raw = iv
  68. self.num.value = 0
  69. def encrypt(self, data):
  70. out = create_string_buffer(len(data))
  71. libcrypto.AES_ctr128_encrypt(data, out, len(data), self.key, self.iv, self.buf, self.num)
  72. return out.raw
  73. decrypt = encrypt