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.
This commit is contained in:
Thomas Waldmann 2015-03-23 14:01:47 +01:00
parent 0c183acb55
commit 5ae3fa2927
5 changed files with 54 additions and 30 deletions

View file

@ -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)
"""

View file

@ -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:

View file

@ -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)

View file

@ -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)),

View file

@ -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