소스 검색

make borg build/work on OpenSSL 1.0 and 1.1, fixes #1187

in openssl 1.1, the cipher context is opaque, members can not
be accessed directly. we only used this for ctx.iv to determine
the current IV (counter value).

now, we just remember the original IV, count the AES blocks we
process and then compute iv = iv_orig + blocks.

that way, it works on OpenSSL 1.0.x and >= 1.1 in the same way.
Thomas Waldmann 9 년 전
부모
커밋
b5362fa5c8
2개의 변경된 파일59개의 추가작업 그리고 2개의 파일을 삭제
  1. 37 2
      borg/crypto.pyx
  2. 22 0
      borg/testsuite/crypto.py

+ 37 - 2
borg/crypto.pyx

@@ -16,7 +16,6 @@ cdef extern from "openssl/evp.h":
     ctypedef struct EVP_CIPHER:
     ctypedef struct EVP_CIPHER:
         pass
         pass
     ctypedef struct EVP_CIPHER_CTX:
     ctypedef struct EVP_CIPHER_CTX:
-        unsigned char *iv
         pass
         pass
     ctypedef struct ENGINE:
     ctypedef struct ENGINE:
         pass
         pass
@@ -40,12 +39,40 @@ import struct
 
 
 _int = struct.Struct('>I')
 _int = struct.Struct('>I')
 _long = struct.Struct('>Q')
 _long = struct.Struct('>Q')
+_2long = struct.Struct('>QQ')
 
 
 bytes_to_int = lambda x, offset=0: _int.unpack_from(x, offset)[0]
 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]
 bytes_to_long = lambda x, offset=0: _long.unpack_from(x, offset)[0]
 long_to_bytes = lambda x: _long.pack(x)
 long_to_bytes = lambda x: _long.pack(x)
 
 
 
 
+def bytes16_to_int(b, offset=0):
+    h, l = _2long.unpack_from(b, offset)
+    return (h << 64) + l
+
+
+def int_to_bytes16(i):
+    max_uint64 = 0xffffffffffffffff
+    l = i & max_uint64
+    h = (i >> 64) & max_uint64
+    return _2long.pack(h, l)
+
+
+def increment_iv(iv, amount=1):
+    """
+    Increment the IV by the given amount (default 1).
+
+    :param iv: input IV, 16 bytes (128 bit)
+    :param amount: increment value
+    :return: input_IV + amount, 16 bytes (128 bit)
+    """
+    assert len(iv) == 16
+    iv = bytes16_to_int(iv)
+    iv += amount
+    iv = int_to_bytes16(iv)
+    return iv
+
+
 def num_aes_blocks(int length):
 def num_aes_blocks(int length):
     """Return the number of AES blocks required to encrypt/decrypt *length* bytes of data.
     """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.
        Note: this is only correct for modes without padding, like AES-CTR.
@@ -58,6 +85,8 @@ cdef class AES:
     """
     """
     cdef EVP_CIPHER_CTX *ctx
     cdef EVP_CIPHER_CTX *ctx
     cdef int is_encrypt
     cdef int is_encrypt
+    cdef unsigned char iv_orig[16]
+    cdef int blocks
 
 
     def __cinit__(self, is_encrypt, key, iv=None):
     def __cinit__(self, is_encrypt, key, iv=None):
         self.ctx = EVP_CIPHER_CTX_new()
         self.ctx = EVP_CIPHER_CTX_new()
@@ -82,6 +111,10 @@ cdef class AES:
             key2 = key
             key2 = key
         if iv:
         if iv:
             iv2 = iv
             iv2 = iv
+            assert isinstance(iv, bytes) and len(iv) == 16
+            for i in range(16):
+                self.iv_orig[i] = iv[i]
+            self.blocks = 0  # number of AES blocks encrypted starting with iv_orig
         # Initialise key and IV
         # Initialise key and IV
         if self.is_encrypt:
         if self.is_encrypt:
             if not EVP_EncryptInit_ex(self.ctx, NULL, NULL, key2, iv2):
             if not EVP_EncryptInit_ex(self.ctx, NULL, NULL, key2, iv2):
@@ -92,7 +125,7 @@ cdef class AES:
 
 
     @property
     @property
     def iv(self):
     def iv(self):
-        return self.ctx.iv[:16]
+        return increment_iv(self.iv_orig[:16], self.blocks)
 
 
     def encrypt(self, data):
     def encrypt(self, data):
         cdef int inl = len(data)
         cdef int inl = len(data)
@@ -109,6 +142,7 @@ cdef class AES:
             if not EVP_EncryptFinal_ex(self.ctx, out+ctl, &outl):
             if not EVP_EncryptFinal_ex(self.ctx, out+ctl, &outl):
                 raise Exception('EVP_EncryptFinal failed')
                 raise Exception('EVP_EncryptFinal failed')
             ctl += outl
             ctl += outl
+            self.blocks += num_aes_blocks(ctl)
             return out[:ctl]
             return out[:ctl]
         finally:
         finally:
             free(out)
             free(out)
@@ -133,6 +167,7 @@ cdef class AES:
                 # CTR mode does not use padding nor authentication.
                 # CTR mode does not use padding nor authentication.
                 raise Exception('EVP_DecryptFinal failed')
                 raise Exception('EVP_DecryptFinal failed')
             ptl += outl
             ptl += outl
+            self.blocks += num_aes_blocks(inl)
             return out[:ptl]
             return out[:ptl]
         finally:
         finally:
             free(out)
             free(out)

+ 22 - 0
borg/testsuite/crypto.py

@@ -1,6 +1,7 @@
 from binascii import hexlify
 from binascii import hexlify
 
 
 from ..crypto import AES, bytes_to_long, bytes_to_int, long_to_bytes
 from ..crypto import AES, bytes_to_long, bytes_to_int, long_to_bytes
+from ..crypto import increment_iv, bytes16_to_int, int_to_bytes16
 from . import BaseTestCase
 from . import BaseTestCase
 
 
 
 
@@ -13,6 +14,27 @@ class CryptoTestCase(BaseTestCase):
         self.assert_equal(bytes_to_long(b'\0\0\0\0\0\0\0\1'), 1)
         self.assert_equal(bytes_to_long(b'\0\0\0\0\0\0\0\1'), 1)
         self.assert_equal(long_to_bytes(1), b'\0\0\0\0\0\0\0\1')
         self.assert_equal(long_to_bytes(1), b'\0\0\0\0\0\0\0\1')
 
 
+    def test_bytes16_to_int(self):
+        self.assert_equal(bytes16_to_int(b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1'), 1)
+        self.assert_equal(int_to_bytes16(1), b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
+        self.assert_equal(bytes16_to_int(b'\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0'), 2 ** 64)
+        self.assert_equal(int_to_bytes16(2 ** 64), b'\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\0')
+
+    def test_increment_iv(self):
+        iv0 = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
+        iv1 = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1'
+        iv2 = b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\2'
+        self.assert_equal(increment_iv(iv0, 0), iv0)
+        self.assert_equal(increment_iv(iv0, 1), iv1)
+        self.assert_equal(increment_iv(iv0, 2), iv2)
+        iva = b'\0\0\0\0\0\0\0\0\xff\xff\xff\xff\xff\xff\xff\xff'
+        ivb = b'\0\0\0\0\0\0\0\1\x00\x00\x00\x00\x00\x00\x00\x00'
+        ivc = b'\0\0\0\0\0\0\0\1\x00\x00\x00\x00\x00\x00\x00\x01'
+        self.assert_equal(increment_iv(iva, 0), iva)
+        self.assert_equal(increment_iv(iva, 1), ivb)
+        self.assert_equal(increment_iv(iva, 2), ivc)
+        self.assert_equal(increment_iv(iv0, 2**64), ivb)
+
     def test_aes(self):
     def test_aes(self):
         key = b'X' * 32
         key = b'X' * 32
         data = b'foo' * 10
         data = b'foo' * 10