Browse Source

128bit increment_iv implementation/pack/unpack

remove strange "lower 64bits of IV" stuff, 64bit pack/unpack.
while the 64bit counter 295EB "limit" was maybe high enough,
always dealing with dissecting and reassembling the IV was a pain.
Thomas Waldmann 10 years ago
parent
commit
5ae3fa2927
5 changed files with 54 additions and 30 deletions
  1. 29 3
      attic/crypto.pyx
  2. 1 20
      attic/key.py
  3. 2 2
      attic/testsuite/archiver.py
  4. 22 4
      attic/testsuite/crypto.py
  5. 0 1
      attic/testsuite/key.py

+ 29 - 3
attic/crypto.pyx

@@ -55,11 +55,21 @@ cdef extern from "openssl/evp.h":
 import struct
 
 _int = struct.Struct('>I')
-_long = struct.Struct('>Q')
+_2long = struct.Struct('>QQ')
 
 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 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 num_aes_blocks(length):
@@ -69,6 +79,22 @@ def num_aes_blocks(length):
     return (length + 15) // 16
 
 
+def increment_iv(iv, amount):
+    """
+    increment the given IV considering that <amount> bytes of data was
+    encrypted based on it. In CTR / GCM mode, the IV is just a counter and
+    must never repeat.
+
+    :param iv: current IV, 16 bytes (128 bit)
+    :param amount: amount of data (in bytes) that was encrypted
+    :return: new IV, 16 bytes (128 bit)
+    """
+    iv = bytes16_to_int(iv)
+    iv += num_aes_blocks(amount)
+    iv = int_to_bytes16(iv)
+    return iv
+
+
 def pbkdf2_sha256(password, salt, iterations, size):
     """Password based key derivation function 2 (RFC2898)
     """

+ 1 - 20
attic/key.py

@@ -17,7 +17,7 @@ except ImportError:
         lzma = None
 
 from attic.crypto import pbkdf2_sha256, get_random_bytes, AES, AES_CTR_MODE, AES_GCM_MODE, \
-    bytes_to_long, long_to_bytes, bytes_to_int, num_aes_blocks
+    bytes_to_int, increment_iv
 from attic.helpers import IntegrityError, get_keys_dir, Error
 
 # we do not store the full IV on disk, as the upper 8 bytes are expected to be
@@ -216,25 +216,6 @@ class PLAIN:
         return data
 
 
-def increment_iv(iv, amount):
-    """
-    increment the given IV considering that <amount> bytes of data was
-    encrypted based on it. In CTR / GCM mode, the IV is just a counter and
-    must never repeat.
-
-    :param iv: current IV, 16 bytes (128 bit)
-    :param amount: amount of data (in bytes) that was encrypted
-    :return: new IV, 16 bytes (128 bit)
-    """
-    # TODO: code assumes that the last 8 bytes are enough, the upper 8 always zero
-    iv_last8 = iv[8:]
-    current_iv = bytes_to_long(iv_last8)
-    new_iv = current_iv + num_aes_blocks(amount)
-    iv_last8 = long_to_bytes(new_iv)
-    iv = PREFIX + iv_last8
-    return iv
-
-
 def get_aad(meta):
     """get additional authenticated data for AEAD ciphers"""
     if meta.legacy:

+ 2 - 2
attic/testsuite/archiver.py

@@ -11,7 +11,7 @@ from hashlib import sha256
 from attic import xattr
 from attic.archive import Archive, ChunkBuffer
 from attic.archiver import Archiver
-from attic.crypto import bytes_to_long, num_aes_blocks
+from attic.crypto import bytes16_to_int, num_aes_blocks
 from attic.helpers import Manifest
 from attic.key import parser
 from attic.remote import RemoteRepository, PathNotAllowed
@@ -385,7 +385,7 @@ class ArchiverTestCase(ArchiverTestCaseBase):
                     seen.add(hash)
                     mac, meta, data = parser(data)
                     num_blocks = num_aes_blocks(len(data))
-                    nonce = bytes_to_long(meta.iv, 8)
+                    nonce = bytes16_to_int(meta.iv)
                     for counter in range(nonce, nonce + num_blocks):
                         self.assert_not_in(counter, used)
                         used.add(counter)

+ 22 - 4
attic/testsuite/crypto.py

@@ -1,7 +1,7 @@
 from binascii import hexlify
 from attic.testsuite import AtticTestCase
 from attic.crypto import pbkdf2_sha256, get_random_bytes, AES, AES_GCM_MODE, AES_CTR_MODE, \
-    bytes_to_long, bytes_to_int, long_to_bytes
+    bytes_to_int, bytes16_to_int, int_to_bytes16, increment_iv
 
 
 class CryptoTestCase(AtticTestCase):
@@ -9,9 +9,27 @@ class CryptoTestCase(AtticTestCase):
     def test_bytes_to_int(self):
         self.assert_equal(bytes_to_int(b'\0\0\0\1'), 1)
 
-    def test_bytes_to_long(self):
-        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')
+    def test_bytes16_to_int(self):
+        i, b = 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), i)
+        self.assert_equal(int_to_bytes16(i), b)
+        i, b = (1 << 64) + 2, b'\0\0\0\0\0\0\0\1\0\0\0\0\0\0\0\2'
+        self.assert_equal(bytes16_to_int(b), i)
+        self.assert_equal(int_to_bytes16(i), b)
+
+    def test_increment_iv(self):
+        tests = [
+            # iv, amount, iv_expected
+            (0, 0, 0),
+            (0, 15, 1),
+            (0, 16, 1),
+            (0, 17, 2),
+            (0xffffffffffffffff, 32, 0x10000000000000001),
+        ]
+        for iv, amount, iv_expected in tests:
+            iv = int_to_bytes16(iv)
+            iv_expected = int_to_bytes16(iv_expected)
+            self.assert_equal(increment_iv(iv, amount), iv_expected)
 
     def test_pbkdf2_sha256(self):
         self.assert_equal(hexlify(pbkdf2_sha256(b'password', b'salt', 1, 32)),

+ 0 - 1
attic/testsuite/key.py

@@ -3,7 +3,6 @@ import re
 import shutil
 import tempfile
 from binascii import hexlify
-from attic.crypto import bytes_to_long
 from attic.testsuite import AtticTestCase
 from attic.key import PlaintextKey, PassphraseKey, KeyfileKey, COMPR_DEFAULT, increment_iv
 from attic.helpers import Location, unhexlify