From 5ae3fa29273ac5a996de997aa3e28b66eecd92c2 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 23 Mar 2015 14:01:47 +0100 Subject: [PATCH] 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. --- attic/crypto.pyx | 32 +++++++++++++++++++++++++++++--- attic/key.py | 21 +-------------------- attic/testsuite/archiver.py | 4 ++-- attic/testsuite/crypto.py | 26 ++++++++++++++++++++++---- attic/testsuite/key.py | 1 - 5 files changed, 54 insertions(+), 30 deletions(-) diff --git a/attic/crypto.pyx b/attic/crypto.pyx index 4e8e259a1..4a82d8c6c 100644 --- a/attic/crypto.pyx +++ b/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 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) """ diff --git a/attic/key.py b/attic/key.py index 5617c1059..007e2aa4f 100644 --- a/attic/key.py +++ b/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 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: diff --git a/attic/testsuite/archiver.py b/attic/testsuite/archiver.py index 96fa476ea..24fc92376 100644 --- a/attic/testsuite/archiver.py +++ b/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) diff --git a/attic/testsuite/crypto.py b/attic/testsuite/crypto.py index bf3fe912a..1c1795be6 100644 --- a/attic/testsuite/crypto.py +++ b/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)), diff --git a/attic/testsuite/key.py b/attic/testsuite/key.py index 371ba8d8a..8651a9785 100644 --- a/attic/testsuite/key.py +++ b/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