From ec9a7a3b318e8f72e9c6f0128b3cc074243d9fa1 Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Mon, 27 Mar 2017 17:43:27 +0200 Subject: [PATCH] automate iv fixed-part generation MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The crypto context keeps track of the used IV fixed parts so they can eventually be included in the info file. A new fixed part is created in the ctor, then for every time the counter wraps. “Wrapping” resets the counter to 2 since 1 is globally reserved for the info file. --- deltatar/crypto.py | 95 ++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 67 insertions(+), 28 deletions(-) diff --git a/deltatar/crypto.py b/deltatar/crypto.py index 7a5b3a7..8c826b3 100755 --- a/deltatar/crypto.py +++ b/deltatar/crypto.py @@ -30,6 +30,7 @@ passed in advance: https://github.com/pyca/cryptography/pull/3421 """ import binascii +import bisect import ctypes import io from functools import reduce, partial @@ -104,6 +105,7 @@ HDR_OFF_TAG = HDR_OFF_CTSIZE + I2N_HDR_SIZE_CTSIZE FMT_UINT16_LE = " AES_GCM_IV_CNT_MAX + 1: + raise Exception ("XXX invalid counter value %d requested" % cnt) + if cnt == AES_GCM_IV_CNT_INFOFILE: + if self.info_counter_used is True: + raise Exception ("XXX attempted to reuse info file counter") + self.info_counter_used = True + return + if cnt <= AES_GCM_IV_CNT_MAX: + self.cnt = cnt + return + # cnt == AES_GCM_IV_CNT_MAX + 1 → wrap + self.cnt = AES_GCM_IV_CNT_DATA + self.next_pfx () + + + def set_parameters (self, password, paramversion, nacl=None, counter=None, + nextpfx=None): + if nextpfx is not None: + self.next_pfx = nextpfx + self.next_pfx () + self.set_object_counter (counter) if isinstance (password, bytes) is False: password = str.encode (password) self.password = password if paramversion is None and nacl is None: @@ -361,11 +390,6 @@ class Crypto (object): if kdf is not None: self.key, self.nacl = kdf (password, nacl) - if pfx is not None and isinstance (pfx, bytes) is True: - self.pfx = pfx - if self.pfx is None: - self.pfx = os.urandom(8) - self.paramversion = paramversion @@ -382,7 +406,6 @@ class Crypto (object): self.ctsize = 0 self.ptsize = 0 self.stats ["obj"] += 1 - self.state = STATE_LIVE if ( self.paramversion != paramversion or self.password != password or self.nacl != nacl): @@ -393,27 +416,27 @@ class Crypto (object): return self.stats ["obj"], self.stats ["in"], self.stats ["out"] - def currentstate (self): return self.state - - class Encrypt (Crypto): curobj = None hdrdum = None + def __init__ (self, password, paramversion, nacl=None, counter=AES_GCM_IV_CNT_DATA): - super().__init__ (password, paramversion, nacl, counter=counter) + self.pfx = [ ] + super().__init__ (password, paramversion, nacl, counter=counter, + nextpfx=lambda: self.pfx.append (os.urandom(8))) def iv_make (self): - return struct.pack("<8sL", self.pfx, self.cnt % 0xffFFffFF) + return struct.pack(FMT_I2N_IV, self.pfx [-1], self.cnt) def next (self, filename, version, paramversion, nacl): - self.iv = self.iv_make() self.curobj = (filename, version, paramversion, nacl or self.nacl) - self.cnt += 1 + self.set_object_counter (self.cnt + 1) + self.iv = self.iv_make () defs = ENCRYPTION_PARAMETERS.get(paramversion) enc = defs ["enc"] if enc == "aes-gcm": @@ -443,7 +466,6 @@ class Encrypt (Crypto): self.ctsize, self.enc.tag) if ok is False: raise Exception ("XXX error constructing header: %r" % hdr) ## we need to converge on a sensible error handling strategy - self.state = STATE_DEAD return data, hdr @@ -456,19 +478,36 @@ class Encrypt (Crypto): class Decrypt (Crypto): - pfx = None - tag = None # GCM tag, part of header + tag = None # GCM tag, part of header - def __init__ (self, password, paramversion=None, nacl=None, counter=None): + def __init__ (self, password, paramversion=None, nacl=None, counter=None, + fixedparts=None): + if fixedparts is not None: + self.pfx = fixedparts + self.pfx.sort () + super().__init__ (password, paramversion, nacl, counter=counter, + nextpfx=lambda: self.pfx.pop()) super().__init__ (password, paramversion, nacl, counter=counter) + def valid_pfx (self, iv): + # check if fixed part is known + pfx, _cnt = struct.unpack (FMT_I2N_IV, iv) + i = bisect.bisect_left (self.pfx, pfx) + return i != len (self.pfx) and self.pfx [i] == pfx + + def next (self, hdr): + paramversion = hdr ["paramversion"] if self.key is None: - super().next (self.password, hdr ["paramversion"], hdr ["nacl"]) - self.cnt += 1 + super().next (self.password, paramversion, hdr ["nacl"]) + self.set_object_counter (self.cnt + 1) iv = hdr ["iv"] + if self.pfx is not None and self.valid_pfx (iv) is False: + raise Exception ("XXX iv %r has invalid fixed part" % iv) self.tag = hdr ["tag"] + defs = ENCRYPTION_PARAMETERS.get(paramversion) + enc = defs ["enc"] if enc == "aes-gcm": self.enc = Cipher \ ( algorithms.AES (self.key) @@ -491,8 +530,8 @@ class Decrypt (Crypto): ret, data = self.enc.finalize_with_tag (self.tag) except crypto.cryptography.exceptions.InvalidTag as exn: return False, repr (exn) + self.ctsize += len (data) self.stats ["out"] += len (data) - self.state = STATE_DEAD return ret, data -- 1.7.1