1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2024-12-26 17:57:59 +00:00

add support for AES-OCB and chacha20-poly1305

also: use AEAD base class
This commit is contained in:
Thomas Waldmann 2016-08-16 06:34:52 +02:00
parent 92080f9572
commit 15490d520d
4 changed files with 157 additions and 13 deletions

View file

@ -1,4 +1,4 @@
/* add missing HMAC functions, so OpenSSL 1.0.x can be used like 1.1 */ /* some helpers, so our code also works with OpenSSL 1.0.x */
#include <string.h> #include <string.h>
#include <openssl/opensslv.h> #include <openssl/opensslv.h>
@ -24,4 +24,12 @@ void HMAC_CTX_free(HMAC_CTX *ctx)
} }
} }
const EVP_CIPHER *EVP_aes_256_ocb(void){ /* dummy, so that code compiles */
return NULL;
}
const EVP_CIPHER *EVP_chacha20_poly1305(void){ /* dummy, so that code compiles */
return NULL;
}
#endif #endif

View file

@ -1,11 +1,15 @@
/* add missing HMAC functions, so OpenSSL 1.0.x can be used like 1.1 */ /* some helpers, so our code also works with OpenSSL 1.0.x */
#include <openssl/opensslv.h> #include <openssl/opensslv.h>
#include <openssl/hmac.h> #include <openssl/hmac.h>
#include <openssl/evp.h>
#if OPENSSL_VERSION_NUMBER < 0x10100000L #if OPENSSL_VERSION_NUMBER < 0x10100000L
HMAC_CTX *HMAC_CTX_new(void); HMAC_CTX *HMAC_CTX_new(void);
void HMAC_CTX_free(HMAC_CTX *ctx); void HMAC_CTX_free(HMAC_CTX *ctx);
const EVP_CIPHER *EVP_aes_256_ocb(void); /* dummy, so that code compiles */
const EVP_CIPHER *EVP_chacha20_poly1305(void); /* dummy, so that code compiles */
#endif #endif

View file

@ -1,7 +1,5 @@
"""An AEAD style OpenSSL wrapper """An AEAD style OpenSSL wrapper
Note: AES-GCM mode needs OpenSSL >= 1.0.1d due to bug fixes in OpenSSL.
API: API:
encrypt(data, header=b'', aad_offset=0) -> envelope encrypt(data, header=b'', aad_offset=0) -> envelope
@ -79,6 +77,8 @@ cdef extern from "openssl/evp.h":
const EVP_CIPHER *EVP_aes_256_ctr() const EVP_CIPHER *EVP_aes_256_ctr()
const EVP_CIPHER *EVP_aes_256_gcm() const EVP_CIPHER *EVP_aes_256_gcm()
const EVP_CIPHER *EVP_aes_256_ocb()
const EVP_CIPHER *EVP_chacha20_poly1305()
void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a) void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *a)
void EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a) void EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *a)
@ -124,12 +124,20 @@ cdef extern from "openssl/hmac.h":
unsigned char *md, unsigned int *md_len) nogil unsigned char *md, unsigned int *md_len) nogil
cdef extern from "_crypto_helpers.h": cdef extern from "_crypto_helpers.h":
long OPENSSL_VERSION_NUMBER
ctypedef struct HMAC_CTX: ctypedef struct HMAC_CTX:
pass pass
HMAC_CTX *HMAC_CTX_new() HMAC_CTX *HMAC_CTX_new()
void HMAC_CTX_free(HMAC_CTX *a) void HMAC_CTX_free(HMAC_CTX *a)
const EVP_CIPHER *EVP_aes_256_ocb() # dummy
const EVP_CIPHER *EVP_chacha20_poly1305() # dummy
openssl10 = OPENSSL_VERSION_NUMBER < 0x10100000
import struct import struct
@ -331,9 +339,13 @@ cdef class AES256_CTR_HMAC_SHA256:
iv_out[i] = iv[8+i] iv_out[i] = iv[8+i]
cdef class AES256_GCM: ctypedef const EVP_CIPHER * (* CIPHER)()
# Layout: HEADER + GMAC 16 + IV 12 + CT
cdef class _AEAD_BASE:
# Layout: HEADER + MAC 16 + IV 12 + CT
cdef CIPHER cipher
cdef EVP_CIPHER_CTX *ctx cdef EVP_CIPHER_CTX *ctx
cdef unsigned char *enc_key cdef unsigned char *enc_key
cdef unsigned char iv[12] cdef unsigned char iv[12]
@ -376,7 +388,7 @@ cdef class AES256_GCM:
offset += hlen offset += hlen
offset += 16 offset += 16
self.store_iv(odata+offset, self.iv) self.store_iv(odata+offset, self.iv)
rc = EVP_EncryptInit_ex(self.ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) rc = EVP_EncryptInit_ex(self.ctx, self.cipher(), NULL, NULL, NULL)
if not rc: if not rc:
raise CryptoError('EVP_EncryptInit_ex failed') raise CryptoError('EVP_EncryptInit_ex failed')
if not EVP_CIPHER_CTX_ctrl(self.ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL): if not EVP_CIPHER_CTX_ctrl(self.ctx, EVP_CTRL_GCM_SET_IVLEN, 12, NULL):
@ -422,7 +434,7 @@ cdef class AES256_GCM:
cdef int offset cdef int offset
cdef Py_buffer idata = ro_buffer(envelope) cdef Py_buffer idata = ro_buffer(envelope)
try: try:
if not EVP_DecryptInit_ex(self.ctx, EVP_aes_256_gcm(), NULL, NULL, NULL): if not EVP_DecryptInit_ex(self.ctx, self.cipher(), NULL, NULL, NULL):
raise CryptoError('EVP_DecryptInit_ex failed') raise CryptoError('EVP_DecryptInit_ex failed')
iv = self.fetch_iv(<unsigned char *> idata.buf+hlen+16) iv = self.fetch_iv(<unsigned char *> idata.buf+hlen+16)
self.set_iv(iv) self.set_iv(iv)
@ -445,7 +457,7 @@ cdef class AES256_GCM:
rc = EVP_DecryptFinal_ex(self.ctx, odata+offset, &olen) rc = EVP_DecryptFinal_ex(self.ctx, odata+offset, &olen)
if rc <= 0: if rc <= 0:
# a failure here means corrupted or tampered tag (mac) or data. # a failure here means corrupted or tampered tag (mac) or data.
raise IntegrityError('GCM Authentication / EVP_DecryptFinal_ex failed') raise IntegrityError('Authentication / EVP_DecryptFinal_ex failed')
offset += olen offset += olen
self.blocks += num_aes_blocks(offset) self.blocks += num_aes_blocks(offset)
return odata[:offset] return odata[:offset]
@ -454,7 +466,7 @@ cdef class AES256_GCM:
PyBuffer_Release(&idata) PyBuffer_Release(&idata)
def set_iv(self, iv): def set_iv(self, iv):
self.blocks = 0 # number of AES blocks encrypted with this IV self.blocks = 0 # number of cipher blocks encrypted with this IV
for i in range(12): for i in range(12):
self.iv[i] = iv[i] self.iv[i] = iv[i]
@ -476,6 +488,30 @@ cdef class AES256_GCM:
iv_out[i] = iv[i] iv_out[i] = iv[i]
cdef class AES256_GCM(_AEAD_BASE):
def __init__(self, mac_key, enc_key, iv=None):
if OPENSSL_VERSION_NUMBER < 0x10001040:
raise ValueError('AES GCM requires OpenSSL >= 1.0.1d. Detected: OpenSSL %08x' % OPENSSL_VERSION_NUMBER)
self.cipher = EVP_aes_256_gcm
super().__init__(mac_key, enc_key, iv=iv)
cdef class AES256_OCB(_AEAD_BASE):
def __init__(self, mac_key, enc_key, iv=None):
if OPENSSL_VERSION_NUMBER < 0x10100000:
raise ValueError('AES OCB requires OpenSSL >= 1.1.0. Detected: OpenSSL %08x' % OPENSSL_VERSION_NUMBER)
self.cipher = EVP_aes_256_ocb
super().__init__(mac_key, enc_key, iv=iv)
cdef class CHACHA20_POLY1305(_AEAD_BASE):
def __init__(self, mac_key, enc_key, iv=None):
if OPENSSL_VERSION_NUMBER < 0x10100000:
raise ValueError('CHACHA20-POLY1305 requires OpenSSL >= 1.1.0. Detected: OpenSSL %08x' % OPENSSL_VERSION_NUMBER)
self.cipher = EVP_chacha20_poly1305
super().__init__(mac_key, enc_key, iv=iv)
def hmac_sha256(key, data): def hmac_sha256(key, data):
cdef Py_buffer data_buf = ro_buffer(data) cdef Py_buffer data_buf = ro_buffer(data)
cdef const unsigned char *key_ptr = key cdef const unsigned char *key_ptr = key

View file

@ -1,6 +1,7 @@
from binascii import hexlify, unhexlify from binascii import hexlify, unhexlify
from ..crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_GCM, IntegrityError, hmac_sha256, blake2b_256 from ..crypto.low_level import AES256_CTR_HMAC_SHA256, AES256_GCM, AES256_OCB, CHACHA20_POLY1305, \
IntegrityError, hmac_sha256, blake2b_256, openssl10
from ..crypto.low_level import bytes_to_long, bytes_to_int, long_to_bytes, bytes16_to_int, int_to_bytes16, increment_iv from ..crypto.low_level import bytes_to_long, bytes_to_int, long_to_bytes, bytes16_to_int, int_to_bytes16, increment_iv
from ..crypto.low_level import hkdf_hmac_sha512 from ..crypto.low_level import hkdf_hmac_sha512
@ -99,7 +100,7 @@ def test_AES256_CTR_HMAC_SHA256_aad(self):
self.assert_raises(IntegrityError, self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1)) lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_AES_GCM_256_GMAC(self): def test_AES_GCM_256(self):
# gcm used in legacy-like layout (1 type byte, no aad) # gcm used in legacy-like layout (1 type byte, no aad)
mac_key = None mac_key = None
enc_key = b'X' * 32 enc_key = b'X' * 32
@ -129,7 +130,7 @@ def test_AES_GCM_256_GMAC(self):
self.assert_raises(IntegrityError, self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1)) lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_AES_GCM_256_GMAC_aad(self): def test_AES_GCM_256_aad(self):
mac_key = None mac_key = None
enc_key = b'X' * 32 enc_key = b'X' * 32
iv = b'\0' * 12 iv = b'\0' * 12
@ -158,6 +159,101 @@ def test_AES_GCM_256_GMAC_aad(self):
self.assert_raises(IntegrityError, self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1)) lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_AES_OCB_256(self):
if openssl10: # no OCB
return
# ocb used in legacy-like layout (1 type byte, no aad)
mac_key = None
enc_key = b'X' * 32
iv = b'\0' * 12
data = b'foo' * 10
header = b'\x23'
# encrypt-then-mac
cs = AES256_OCB(mac_key, enc_key, iv)
hdr_mac_iv_cdata = cs.encrypt(data, header=header, aad_offset=1)
hdr = hdr_mac_iv_cdata[0:1]
mac = hdr_mac_iv_cdata[1:17]
iv = hdr_mac_iv_cdata[17:29]
cdata = hdr_mac_iv_cdata[29:]
self.assert_equal(hexlify(hdr), b'23')
self.assert_equal(hexlify(mac), b'b6909c23c9aaebd9abbe1ff42097652d')
self.assert_equal(hexlify(iv), b'000000000000000000000000')
self.assert_equal(hexlify(cdata), b'877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493')
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-then-decrypt
cs = AES256_OCB(mac_key, enc_key)
pdata = cs.decrypt(hdr_mac_iv_cdata, header_len=len(header), aad_offset=1)
self.assert_equal(data, pdata)
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-failure due to corruption (corrupted data)
cs = AES256_OCB(mac_key, enc_key)
hdr_mac_iv_cdata_corrupted = hdr_mac_iv_cdata[:29] + b'\0' + hdr_mac_iv_cdata[30:]
self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_AES_OCB_256_aad(self):
if openssl10: # no OCB
return
mac_key = None
enc_key = b'X' * 32
iv = b'\0' * 12
data = b'foo' * 10
header = b'\x12\x34\x56'
# encrypt-then-mac
cs = AES256_OCB(mac_key, enc_key, iv)
hdr_mac_iv_cdata = cs.encrypt(data, header=header, aad_offset=1)
hdr = hdr_mac_iv_cdata[0:3]
mac = hdr_mac_iv_cdata[3:19]
iv = hdr_mac_iv_cdata[19:31]
cdata = hdr_mac_iv_cdata[31:]
self.assert_equal(hexlify(hdr), b'123456')
self.assert_equal(hexlify(mac), b'f2748c412af1c7ead81863a18c2c1893')
self.assert_equal(hexlify(iv), b'000000000000000000000000')
self.assert_equal(hexlify(cdata), b'877ce46d2f62dee54699cebc3ba41d9ab613f7c486778c1b3636664b1493')
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-then-decrypt
cs = AES256_OCB(mac_key, enc_key)
pdata = cs.decrypt(hdr_mac_iv_cdata, header_len=len(header), aad_offset=1)
self.assert_equal(data, pdata)
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-failure due to corruption (corrupted aad)
cs = AES256_OCB(mac_key, enc_key)
hdr_mac_iv_cdata_corrupted = hdr_mac_iv_cdata[:1] + b'\0' + hdr_mac_iv_cdata[2:]
self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_CHACHA20_POLY1305(self):
if openssl10: # no CHACHA20, no POLY1305
return
# used in legacy-like layout (1 type byte, no aad)
mac_key = None
enc_key = b'X' * 32
iv = b'\0' * 12
data = b'foo' * 10
header = b'\x23'
# encrypt-then-mac
cs = CHACHA20_POLY1305(mac_key, enc_key, iv)
hdr_mac_iv_cdata = cs.encrypt(data, header=header, aad_offset=1)
hdr = hdr_mac_iv_cdata[0:1]
mac = hdr_mac_iv_cdata[1:17]
iv = hdr_mac_iv_cdata[17:29]
cdata = hdr_mac_iv_cdata[29:]
self.assert_equal(hexlify(hdr), b'23')
self.assert_equal(hexlify(mac), b'fd08594796e0706cde1e8b461e3e0555')
self.assert_equal(hexlify(iv), b'000000000000000000000000')
self.assert_equal(hexlify(cdata), b'a093e4b0387526f085d3c40cca84a35230a5c0dd766453b77ba38bcff775')
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-then-decrypt
cs = CHACHA20_POLY1305(mac_key, enc_key)
pdata = cs.decrypt(hdr_mac_iv_cdata, header_len=len(header), aad_offset=1)
self.assert_equal(data, pdata)
self.assert_equal(hexlify(cs.next_iv()), b'000000000000000000000001')
# auth-failure due to corruption (corrupted data)
cs = CHACHA20_POLY1305(mac_key, enc_key)
hdr_mac_iv_cdata_corrupted = hdr_mac_iv_cdata[:29] + b'\0' + hdr_mac_iv_cdata[30:]
self.assert_raises(IntegrityError,
lambda: cs.decrypt(hdr_mac_iv_cdata_corrupted, header_len=len(header), aad_offset=1))
def test_hmac_sha256(self): def test_hmac_sha256(self):
# RFC 4231 test vectors # RFC 4231 test vectors
key = b'\x0b' * 20 key = b'\x0b' * 20