From c8b79bf686113c3418d8d65ff0a1920da2b409bc Mon Sep 17 00:00:00 2001 From: Micah Anderson Date: Thu, 14 Nov 2013 16:35:32 -0500 Subject: initial commit --- scrypt.py | 224 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 scrypt.py (limited to 'scrypt.py') diff --git a/scrypt.py b/scrypt.py new file mode 100644 index 0000000..d504400 --- /dev/null +++ b/scrypt.py @@ -0,0 +1,224 @@ +import imp +import os +import sys + +from ctypes import (cdll, + POINTER, pointer, + c_char_p, + c_size_t, c_double, c_int, c_uint64, c_uint32, + create_string_buffer) + +_scrypt = cdll.LoadLibrary(imp.find_module('_scrypt')[1]) + +_scryptenc_buf = _scrypt.exp_scryptenc_buf +_scryptenc_buf.argtypes = [c_char_p, # const uint_t *inbuf + c_size_t, # size_t inbuflen + c_char_p, # uint8_t *outbuf + c_char_p, # const uint8_t *passwd + c_size_t, # size_t passwdlen + c_size_t, # size_t maxmem + c_double, # double maxmemfrac + c_double, # double maxtime + ] +_scryptenc_buf.restype = c_int + +_scryptdec_buf = _scrypt.exp_scryptdec_buf +_scryptdec_buf.argtypes = [c_char_p, # const uint8_t *inbuf + c_size_t, # size_t inbuflen + c_char_p, # uint8_t *outbuf + POINTER(c_size_t), # size_t *outlen + c_char_p, # const uint8_t *passwd + c_size_t, # size_t passwdlen + c_size_t, # size_t maxmem + c_double, # double maxmemfrac + c_double, # double maxtime + ] +_scryptdec_buf.restype = c_int + +_crypto_scrypt = _scrypt.exp_crypto_scrypt +_crypto_scrypt.argtypes = [c_char_p, # const uint8_t *passwd + c_size_t, # size_t passwdlen + c_char_p, # const uint8_t *salt + c_size_t, # size_t saltlen + c_uint64, # uint64_t N + c_uint32, # uint32_t r + c_uint32, # uint32_t p + c_char_p, # uint8_t *buf + c_size_t, # size_t buflen + ] +_crypto_scrypt.restype = c_int + +ERROR_MESSAGES = ['success', + 'getrlimit or sysctl(hw.usermem) failed', + 'clock_getres or clock_gettime failed', + 'error computing derived key', + 'could not read salt from /dev/urandom', + 'error in OpenSSL', + 'malloc failed', + 'data is not a valid scrypt-encrypted block', + 'unrecognized scrypt format', + 'decrypting file would take too much memory', + 'decrypting file would take too long', + 'password is incorrect', + 'error writing output file', + 'error reading input file'] + +MAXMEM_DEFAULT = 0 +MAXMEMFRAC_DEFAULT = 0.5 +MAXTIME_DEFAULT = 300.0 +MAXTIME_DEFAULT_ENC = 5.0 + +IS_PY2 = sys.version_info < (3, 0, 0, 'final', 0) + + +class error(Exception): + def __init__(self, scrypt_code): + if isinstance(scrypt_code, int): + self._scrypt_code = scrypt_code + super(error, self).__init__(ERROR_MESSAGES[scrypt_code]) + else: + self._scrypt_code = -1 + super(error, self).__init__(scrypt_code) + + +def _ensure_bytes(data): + if IS_PY2 and isinstance(data, unicode): + raise TypeError('can not encrypt/decrypt unicode objects') + + if not IS_PY2 and isinstance(data, str): + return bytes(data, 'utf-8') + + return data + + +def encrypt(input, password, + maxtime=MAXTIME_DEFAULT_ENC, + maxmem=MAXMEM_DEFAULT, + maxmemfrac=MAXMEMFRAC_DEFAULT): + """ + Encrypt a string using a password. The resulting data will have len = + len(input) + 128. + + Notes for Python 2: + - `input` and `password` must be str instances + - The result will be a str instance + + Notes for Python 3: + - `input` and `password` can be both str and bytes. If they are str + instances, they will be encoded with utf-8 + - The result will be a bytes instance + + Exceptions raised: + - TypeError on invalid input + - scrypt.error if encryption failed + + For more information on the `maxtime`, `maxmem`, and `maxmemfrac` + parameters, see the scrypt documentation. + """ + + input = _ensure_bytes(input) + password = _ensure_bytes(password) + + outbuf = create_string_buffer(len(input) + 128) + result = _scryptenc_buf(input, len(input), + outbuf, + password, len(password), + maxmem, maxmemfrac, maxtime) + if result: + raise error(result) + + return outbuf.raw + + +def decrypt(input, password, + maxtime=MAXTIME_DEFAULT, + maxmem=MAXMEM_DEFAULT, + maxmemfrac=MAXMEMFRAC_DEFAULT, + encoding='utf-8'): + """ + Decrypt a string using a password. + + Notes for Python 2: + - `input` and `password` must be str instances + - The result will be a str instance + - The encoding parameter is ignored + + Notes for Python 3: + - `input` and `password` can be both str and bytes. If they are str + instances, they wil be encoded with utf-8. `input` *should* + really be a bytes instance, since that's what `encrypt` returns. + - The result will be a str instance encoded with `encoding`. + If encoding=None, the result will be a bytes instance. + + Exceptions raised: + - TypeError on invalid input + - scrypt.error if decryption failed + + For more information on the `maxtime`, `maxmem`, and `maxmemfrac` + parameters, see the scrypt documentation. + """ + + outbuf = create_string_buffer(len(input)) + outbuflen = pointer(c_size_t(0)) + + input = _ensure_bytes(input) + password = _ensure_bytes(password) + + result = _scryptdec_buf(input, len(input), + outbuf, outbuflen, + password, len(password), + maxmem, maxmemfrac, maxtime) + + if result: + raise error(result) + + out_bytes = outbuf.raw[:outbuflen.contents.value] + + if IS_PY2 or encoding is None: + return out_bytes + + return str(out_bytes, encoding) + + +def hash(password, salt, N=1 << 14, r=8, p=1, buflen=64): + """ + Compute scrypt(password, salt, N, r, p, buflen). + + The parameters r, p, and buflen must satisfy r * p < 2^30 and + buflen <= (2^32 - 1) * 32. The parameter N must be a power of 2 + greater than 1. N, r and p must all be positive. + + Notes for Python 2: + - `password` and `salt` must be str instances + - The result will be a str instance + + Notes for Python 3: + - `password` and `salt` can be both str and bytes. If they are str + instances, they wil be encoded with utf-8. + - The result will be a bytes instance + + Exceptions raised: + - TypeError on invalid input + - scrypt.error if scrypt failed + """ + + outbuf = create_string_buffer(buflen) + + password = _ensure_bytes(password) + salt = _ensure_bytes(salt) + + if r * p >= (1 << 30) or N <= 1 or (N & (N - 1)) != 0 or p < 1 or r < 1: + raise error('hash parameters are wrong (r*p should be < 2**30, and N should be a power of two > 1)') + + result = _crypto_scrypt(password, len(password), + salt, len(salt), + N, r, p, + outbuf, buflen) + + if result: + raise error('could not compute hash') + + return outbuf.raw + + +__all__ = ['error', 'encrypt', 'decrypt', 'hash'] -- cgit v1.2.3