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:
parent
92080f9572
commit
15490d520d
4 changed files with 157 additions and 13 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue