pass salt between index and archive
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 16 Mar 2017 15:40:31 +0000 (16:40 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Thu, 16 Mar 2017 15:41:07 +0000 (16:41 +0100)
Memoization of the scrypt params requires static storage because
both the index file and the archive each have an encryption
context.

deltatar/crypto.py
deltatar/deltatar.py

index 1810eff..2ac588b 100755 (executable)
@@ -51,8 +51,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
 from cryptography.hazmat.backends import default_backend
 
 
-__all__ = [ "ENCRYPT", "DECRYPT"
-          , "hdr_make", "hdr_read", "hdr_fmt", "hdr_fmt_pretty"
+__all__ = [ "hdr_make", "hdr_read", "hdr_fmt", "hdr_fmt_pretty"
           , "I2N_HDR_SIZE", "I2N_TLR_SIZE_TAG" ]
 
 
@@ -245,11 +244,24 @@ def tag_read_stream (source):
     return tag_read (data)
 
 
-
 ###############################################################################
 ## convenience wrapper
 ###############################################################################
 
+KEY_MEMO = { } # static because needed for both the info file and the archive
+
+
+def kdf_by_version (paramversion):
+    defs = ENCRYPTION_PARAMETERS.get(paramversion)
+    if defs is None:
+        raise ValueError ("no encryption parameters for version %r"
+                % paramversion)
+    (kdf, params) = defs["kdf"]
+    if kdf != "scrypt":
+        raise ValueError ("key derivation method %r unknown" % kdf)
+    return kdf, params
+
+
 
 class Crypto (object):
     """
@@ -261,37 +273,31 @@ class Crypto (object):
     pfx  = None # 64 bit fixed parts of IV
     cnt  = None
 
-    key_memo = { }
-
-    def __init__ (self):
+    def __init__ (self, *al, **akv):
         self.cnt  = 1
+        self.set_parameters (*al, **akv)
 
 
-    def set_parameters (self, pw, nacl, paramversion, pfx=None):
-        self.pw           = pw
+    def set_parameters (self, password, paramversion, nacl, pfx=None):
+        if isinstance (password, bytes) is False: password = str.encode (password)
+        self.password     = password
         self.nacl         = nacl
         self.paramversion = paramversion
-
-        defs = ENCRYPTION_PARAMETERS.get(paramversion)
-        if defs is None:
-            raise ValueError ("no encryption parameters for version %r"
-                              % paramversion)
-        (kdf, params) = defs["hash"]
-        if kdf != "scrypt":
-            raise ValueError ("key derivation method %r unknown" % kdf)
-
-        if nacl is None:
-            nacl = os.urandom (params["NaCl_LEN"])
+        (kdf, params) = kdf_by_version (paramversion)
 
         N = params["N"]
         r = params["r"]
         p = params["p"]
         dkLen = params["dkLen"]
 
-        key_parms = (pw, nacl, N, r, p, dkLen)
-        if key_parms not in key_memo:
-            key_memo [key_parms] = pylibscrypt.scrypt (pw, nacl, N, r, p, dkLen)
-        self.key = key_memo [key_parms]
+        key_parms = (password, nacl, N, r, p, dkLen)
+        global KEY_MEMO
+        if key_parms not in KEY_MEMO:
+            print(">> new key, memoize parms %s" % repr (key_parms))
+            KEY_MEMO [key_parms] = pylibscrypt.scrypt (password, nacl, N, r, p, dkLen)
+        else:
+            print(">> use memoized key for parms %s" % repr (key_parms))
+        self.key = KEY_MEMO [key_parms]
 
         if pfx is not None:
             self.pfx = pfx
@@ -300,30 +306,34 @@ class Crypto (object):
 
 
     def set_parameters_from_header (self, hdr):
-        self.pw           = pw
+        self.password     = password
         self.nacl         = nacl
         self.paramversion = paramversion
         self.pfx          = pfx
 
 
     def next (self, aad=None, iv=None):
-        ctx.authenticate_additional_data (aad)
+        self.aes.authenticate_additional_data (aad)
+
 
     def process (self, buf):
-        self.ctx.update (buf)
+        self.aes.update (buf)
 
 
 class Encrypt (Crypto):
 
-    def __init__ (self, pw, nacl, paramversion):
-        super().__init__ (ENCRYPT, pw, nacl, paramversion)
+    def __init__ (self, password, paramversion, nacl=None):
+        if nacl is None:
+            _, params = kdf_by_version (paramversion)
+            nacl = os.urandom (params["NaCl_LEN"])
+        super().__init__ (password, paramversion, nacl)
 
 
     def iv_make (self):
         return struct.pack("<8sL", self.pfx, self.cnt % 0xffFFffFF)
 
 
-    def next (filename, ctsize):
+    def next (self, filename, ctsize):
         self.cnt += 1
         aad = "%.20x|%s" % (ctsize, filename)
         iv = iv_make()
@@ -337,19 +347,18 @@ class Encrypt (Crypto):
 
 
     def done (self):
-        return self.ctx.finalize ()
+        return self.aes.finalize ()
 
 
 class Decrypt (Crypto):
 
     pfx = None
 
-    def __init__ (self, pw, nacl, paramversion, pfx=None):
-        super().__init__ (DECRYPT, pw, nacl, paramversion)
-        self.pfx = pfx # XXX not needed, right?
+    def __init__ (self, password, paramversion, nacl):
+        super().__init__ (password, paramversion, nacl)
 
 
-    def next (filename, hdr):
+    def next (self, filename, hdr):
         self.cnt += 1
         aad = "%0.20x|%s" % (hdr["ctsize"], filename)
         print("I2N: got header ā€œ%sā€" % crypto.hdr_fmt (hdr))
@@ -361,7 +370,7 @@ class Decrypt (Crypto):
         return super().next(aad)
 
 
-    def next_in_source (tarinfo, source):
+    def next_in_source (self, tarinfo, source):
         ok, hdr = hdr_read_stream (source)
         if ok is False:
             raise DecryptionError("Irrecoverable error reading header from "
@@ -370,7 +379,7 @@ class Decrypt (Crypto):
 
 
     def done (self, tag):
-        return self.ctx.finalize_with_tag (tag)
+        return self.aes.finalize_with_tag (tag)
 
 
 ###############################################################################
index 27033a8..dd6f584 100644 (file)
@@ -72,6 +72,11 @@ class DeltaTar(object):
     # used together with aes modes to encrypt and decrypt backups.
     password = None
 
+    # When encrypting, the salt is created by the first crypto operation, i. e.
+    # opening the index for writing. The subsequently opened archive uses the
+    # same salt.
+    nacl = None
+
     # python logger object.
     logger = None
 
@@ -565,7 +570,9 @@ class DeltaTar(object):
         # init index
         index_name = self.index_name_func(True)
         index_path = os.path.join(backup_path, index_name)
-        index_fd = self.open_index(index_path, 'w')
+        index_fd = self.open_index(index_path, 'w') # **NOT** an fd
+        if index_fd.encryption is not None:
+            self.nacl = index_fd.encryption.nacl
 
         cwd = os.getcwd()
 
@@ -604,12 +611,11 @@ class DeltaTar(object):
                               format=tarfile.GNU_FORMAT,
                               concat_compression='#gz' in self.mode,
                               password=self.password,
+                              nacl=self.nacl,
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler,
                               save_to_members=False,
                               dereference=True)
-
-
         os.chdir(source_path)
 
         # for each file to be in the backup, do:
@@ -725,7 +731,9 @@ class DeltaTar(object):
         # init index
         index_name = self.index_name_func(is_full=False)
         index_path = os.path.join(backup_path, index_name)
-        index_fd = self.open_index(index_path, 'w')
+        index_fd = self.open_index(index_path, 'w') # **NOT** an fd
+        if index_fd.encryption is not None:
+            self.nacl = index_fd.encryption.nacl
 
         cwd = os.getcwd()
 
@@ -761,6 +769,7 @@ class DeltaTar(object):
                               format=tarfile.GNU_FORMAT,
                               concat_compression='#gz' in self.mode,
                               password=self.password,
+                              nacl=self.nacl,
                               max_volume_size=max_volume_size,
                               new_volume_handler=new_volume_handler,
                               save_to_members=False,
@@ -973,6 +982,7 @@ class DeltaTar(object):
                         format=tarfile.GNU_FORMAT,
                         concat_compression='#gz' in self.delta_tar.mode,
                         password=self.delta_tar.password,
+                        nacl=self.delta_tar.nacl,
                         new_volume_handler=self.new_volume_handler,
                         save_to_members=False,
                         dereference=True)
@@ -1148,6 +1158,7 @@ class DeltaTar(object):
                             format=tarfile.GNU_FORMAT,
                             concat_compression='#gz' in self.mode,
                             password=self.password,
+                            nacl=self.nacl,
                             new_volume_handler=new_volume_handler,
                             save_to_members=False,
                             dereference=True)
@@ -1645,6 +1656,7 @@ class RestoreHelper(object):
                     format=tarfile.GNU_FORMAT,
                     concat_compression='#gz' in self._deltatar.mode,
                     password=self._deltatar.password,
+                    nacl=self._deltatar.nacl,
                     new_volume_handler=index_data['new_volume_handler'],
                     save_to_members=False)