2014-03-10 21:50:38 +00:00
|
|
|
"""A thin OpenSSL wrapper
|
|
|
|
|
|
|
|
This could be replaced by PyCrypto or something similar when the performance
|
|
|
|
of their PBKDF2 implementation is comparable to the OpenSSL version.
|
|
|
|
"""
|
|
|
|
from libc.stdlib cimport malloc, free
|
|
|
|
|
2014-05-13 20:27:57 +00:00
|
|
|
API_VERSION = 2
|
2014-03-18 21:04:08 +00:00
|
|
|
|
2014-03-10 21:50:38 +00:00
|
|
|
cdef extern from "openssl/rand.h":
|
2015-03-03 19:11:28 +00:00
|
|
|
int RAND_bytes(unsigned char *buf, int num)
|
2014-03-10 21:50:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
cdef extern from "openssl/evp.h":
|
|
|
|
ctypedef struct EVP_MD:
|
|
|
|
pass
|
2014-05-13 20:27:57 +00:00
|
|
|
ctypedef struct EVP_CIPHER:
|
|
|
|
pass
|
|
|
|
ctypedef struct EVP_CIPHER_CTX:
|
|
|
|
unsigned char *iv
|
|
|
|
pass
|
|
|
|
ctypedef struct ENGINE:
|
|
|
|
pass
|
2014-03-10 21:50:38 +00:00
|
|
|
const EVP_MD *EVP_sha256()
|
2014-05-13 20:27:57 +00:00
|
|
|
const EVP_CIPHER *EVP_aes_256_ctr()
|
2014-05-13 21:18:32 +00:00
|
|
|
void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a)
|
|
|
|
void EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a)
|
2014-05-13 20:27:57 +00:00
|
|
|
|
2015-03-03 19:11:28 +00:00
|
|
|
int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
|
2014-05-13 20:27:57 +00:00
|
|
|
const unsigned char *key, const unsigned char *iv)
|
2015-03-03 19:11:28 +00:00
|
|
|
int EVP_DecryptInit_ex(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher, ENGINE *impl,
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
const unsigned char *key, const unsigned char *iv)
|
2015-03-03 19:11:28 +00:00
|
|
|
int EVP_EncryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
|
|
|
|
const unsigned char *in_, int inl)
|
|
|
|
int EVP_DecryptUpdate(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl,
|
|
|
|
const unsigned char *in_, int inl)
|
|
|
|
int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
|
|
|
|
int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *ctx, unsigned char *out, int *outl)
|
2014-05-13 20:27:57 +00:00
|
|
|
|
2014-03-10 21:50:38 +00:00
|
|
|
int PKCS5_PBKDF2_HMAC(const char *password, int passwordlen,
|
|
|
|
const unsigned char *salt, int saltlen, int iter,
|
|
|
|
const EVP_MD *digest,
|
|
|
|
int keylen, unsigned char *out)
|
|
|
|
|
|
|
|
import struct
|
|
|
|
|
|
|
|
_int = struct.Struct('>I')
|
|
|
|
_long = struct.Struct('>Q')
|
|
|
|
|
|
|
|
bytes_to_int = lambda x, offset=0: _int.unpack_from(x, offset)[0]
|
|
|
|
bytes_to_long = lambda x, offset=0: _long.unpack_from(x, offset)[0]
|
|
|
|
long_to_bytes = lambda x: _long.pack(x)
|
|
|
|
|
|
|
|
|
|
|
|
def num_aes_blocks(length):
|
2015-03-03 19:11:28 +00:00
|
|
|
"""Return the number of AES blocks required to encrypt/decrypt *length* bytes of data.
|
|
|
|
Note: this is only correct for modes without padding, like AES-CTR.
|
2014-03-10 21:50:38 +00:00
|
|
|
"""
|
|
|
|
return (length + 15) // 16
|
|
|
|
|
|
|
|
|
|
|
|
def pbkdf2_sha256(password, salt, iterations, size):
|
|
|
|
"""Password based key derivation function 2 (RFC2898)
|
|
|
|
"""
|
|
|
|
cdef unsigned char *key = <unsigned char *>malloc(size)
|
|
|
|
if not key:
|
|
|
|
raise MemoryError
|
|
|
|
try:
|
|
|
|
rv = PKCS5_PBKDF2_HMAC(password, len(password), salt, len(salt), iterations, EVP_sha256(), size, key)
|
|
|
|
if not rv:
|
|
|
|
raise Exception('PKCS5_PBKDF2_HMAC failed')
|
|
|
|
return key[:size]
|
|
|
|
finally:
|
|
|
|
free(key)
|
|
|
|
|
|
|
|
|
|
|
|
def get_random_bytes(n):
|
|
|
|
"""Return n cryptographically strong pseudo-random bytes
|
|
|
|
"""
|
|
|
|
cdef unsigned char *buf = <unsigned char *>malloc(n)
|
|
|
|
if not buf:
|
|
|
|
raise MemoryError
|
|
|
|
try:
|
|
|
|
if RAND_bytes(buf, n) < 1:
|
|
|
|
raise Exception('RAND_bytes failed')
|
|
|
|
return buf[:n]
|
|
|
|
finally:
|
|
|
|
free(buf)
|
|
|
|
|
|
|
|
|
|
|
|
cdef class AES:
|
2014-05-13 20:27:57 +00:00
|
|
|
"""A thin wrapper around the OpenSSL EVP cipher API
|
2014-03-10 21:50:38 +00:00
|
|
|
"""
|
2014-05-13 21:18:32 +00:00
|
|
|
cdef EVP_CIPHER_CTX ctx
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
cdef int is_encrypt
|
2014-03-10 21:50:38 +00:00
|
|
|
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
def __cinit__(self, is_encrypt, key, iv=None):
|
2014-05-13 21:18:32 +00:00
|
|
|
EVP_CIPHER_CTX_init(&self.ctx)
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
self.is_encrypt = is_encrypt
|
|
|
|
# Set cipher type and mode
|
|
|
|
cipher_mode = EVP_aes_256_ctr()
|
|
|
|
if self.is_encrypt:
|
|
|
|
if not EVP_EncryptInit_ex(&self.ctx, cipher_mode, NULL, NULL, NULL):
|
|
|
|
raise Exception('EVP_EncryptInit_ex failed')
|
|
|
|
else: # decrypt
|
|
|
|
if not EVP_DecryptInit_ex(&self.ctx, cipher_mode, NULL, NULL, NULL):
|
|
|
|
raise Exception('EVP_DecryptInit_ex failed')
|
2014-03-10 21:50:38 +00:00
|
|
|
self.reset(key, iv)
|
|
|
|
|
2014-05-13 20:27:57 +00:00
|
|
|
def __dealloc__(self):
|
2014-05-13 21:18:32 +00:00
|
|
|
EVP_CIPHER_CTX_cleanup(&self.ctx)
|
2014-05-13 20:27:57 +00:00
|
|
|
|
2014-03-10 21:50:38 +00:00
|
|
|
def reset(self, key=None, iv=None):
|
2014-05-13 20:27:57 +00:00
|
|
|
cdef const unsigned char *key2 = NULL
|
|
|
|
cdef const unsigned char *iv2 = NULL
|
2014-03-10 21:50:38 +00:00
|
|
|
if key:
|
2014-05-13 20:27:57 +00:00
|
|
|
key2 = key
|
2014-03-10 21:50:38 +00:00
|
|
|
if iv:
|
2014-05-13 20:27:57 +00:00
|
|
|
iv2 = iv
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
# Initialise key and IV
|
|
|
|
if self.is_encrypt:
|
|
|
|
if not EVP_EncryptInit_ex(&self.ctx, NULL, NULL, key2, iv2):
|
|
|
|
raise Exception('EVP_EncryptInit_ex failed')
|
|
|
|
else: # decrypt
|
|
|
|
if not EVP_DecryptInit_ex(&self.ctx, NULL, NULL, key2, iv2):
|
|
|
|
raise Exception('EVP_DecryptInit_ex failed')
|
2014-03-10 21:50:38 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def iv(self):
|
2014-05-13 20:27:57 +00:00
|
|
|
return self.ctx.iv[:16]
|
2014-03-10 21:50:38 +00:00
|
|
|
|
|
|
|
def encrypt(self, data):
|
2014-05-13 20:27:57 +00:00
|
|
|
cdef int inl = len(data)
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
cdef int ctl = 0
|
|
|
|
cdef int outl = 0
|
|
|
|
# note: modes that use padding, need up to one extra AES block (16b)
|
|
|
|
cdef unsigned char *out = <unsigned char *>malloc(inl+16)
|
2014-03-10 21:50:38 +00:00
|
|
|
if not out:
|
|
|
|
raise MemoryError
|
|
|
|
try:
|
2014-05-13 21:18:32 +00:00
|
|
|
if not EVP_EncryptUpdate(&self.ctx, out, &outl, data, inl):
|
2014-05-13 20:27:57 +00:00
|
|
|
raise Exception('EVP_EncryptUpdate failed')
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
ctl = outl
|
|
|
|
if not EVP_EncryptFinal_ex(&self.ctx, out+ctl, &outl):
|
|
|
|
raise Exception('EVP_EncryptFinal failed')
|
|
|
|
ctl += outl
|
|
|
|
return out[:ctl]
|
2014-03-10 21:50:38 +00:00
|
|
|
finally:
|
|
|
|
free(out)
|
|
|
|
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
def decrypt(self, data):
|
|
|
|
cdef int inl = len(data)
|
|
|
|
cdef int ptl = 0
|
|
|
|
cdef int outl = 0
|
2015-03-03 19:18:28 +00:00
|
|
|
# note: modes that use padding, need up to one extra AES block (16b).
|
|
|
|
# This is what the openssl docs say. I am not sure this is correct,
|
|
|
|
# but OTOH it will not cause any harm if our buffer is a little bigger.
|
|
|
|
cdef unsigned char *out = <unsigned char *>malloc(inl+16)
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
if not out:
|
|
|
|
raise MemoryError
|
|
|
|
try:
|
|
|
|
if not EVP_DecryptUpdate(&self.ctx, out, &outl, data, inl):
|
|
|
|
raise Exception('EVP_DecryptUpdate failed')
|
|
|
|
ptl = outl
|
2015-03-03 19:11:28 +00:00
|
|
|
if EVP_DecryptFinal_ex(&self.ctx, out+ptl, &outl) <= 0:
|
|
|
|
# this error check is very important for modes with padding or
|
|
|
|
# authentication. for them, a failure here means corrupted data.
|
|
|
|
# CTR mode does not use padding nor authentication.
|
cleanup crypto.pyx, make it easier to adapt to other modes
There were some small issues:
a) it never called EVP_EncryptFinal_ex.
For CTR mode, this had no visible consequences as EVP_EncryptUpdate already yielded all ciphertext.
For cleanliness and to have correctness even in other modes, the missing call was added.
b) decrypt = encrypt hack
This is a nice hack to abbreviate, but it only works for modes without padding and without authentication.
For cleanliness and to have correctness even in other modes, the missing usage of the decrypt api was added.
c) outl == inl assumption
Again, True for CTR mode, but not for padding or authenticating modes.
Fixed so it computes the ciphertext / plaintext length based on api return values.
Other changes:
As encrypt and decrypt API calls are different even for initialization/reset, added a is_encrypt flag.
Defensive output buffer allocation. Added the length of one extra AES block (16bytes) so it would
work even with padding modes. 16bytes are needed because a full block of padding might get
added when the plaintext was a multiple of aes block size.
These changes are based on some experimental code I did for aes-cbc and aes-gcm.
While we likely won't ever want aes-cbc in attic (maybe gcm though?), I think it is cleaner
to not make too many mode specific assumptions and hacks, but just use the API as it
was meant to be used.
2015-03-03 18:19:28 +00:00
|
|
|
raise Exception('EVP_DecryptFinal failed')
|
|
|
|
ptl += outl
|
|
|
|
return out[:ptl]
|
|
|
|
finally:
|
|
|
|
free(out)
|