implement null-kdf to speed up testing
authorPhilipp Gesang <philipp.gesang@intra2net.com>
Mon, 20 Mar 2017 16:35:45 +0000 (17:35 +0100)
committerPhilipp Gesang <philipp.gesang@intra2net.com>
Mon, 20 Mar 2017 16:35:53 +0000 (17:35 +0100)
With “parameter version” zero, the KDF consists only of a trivial
string derived from the password so as to reduce runtime. (SCRYPT
takes about 48 seconds here with our parameters.)

deltatar/crypto.py

index 17466ad..1dab1be 100755 (executable)
@@ -32,7 +32,7 @@ passed in advance: https://github.com/pyca/cryptography/pull/3421
 import binascii
 import ctypes
 import io
-from functools import reduce
+from functools import reduce, partial
 import os
 import struct
 import sys
@@ -61,7 +61,9 @@ __all__ = [ "hdr_make", "hdr_read", "hdr_fmt", "hdr_fmt_pretty"
 ###############################################################################
 
 ENCRYPTION_PARAMETERS = \
-    { 1: \
+    { 0: \
+        { "kdf": ("dummy", 16) }
+    , 1: \
         { "kdf": ("scrypt",
             { "dkLen"    : 16
             , "N"        : 1 << 15
@@ -113,13 +115,6 @@ AES_GCM_IV_LEN   = 12
 AES_GCM_MAX_SIZE = (1 << 36) - (1 << 5) # 2^39 - 2^8 b ≅ 64 GB
 AES_GCM_FMT_TAG  = "<16s"
 
-# scrypt
-SCRYPT_dkLen     = 16
-SCRYPT_N         = 1 << 15
-SCRYPT_r         = 8
-SCRYPT_p         = 1
-SCRYPT_NaCl_LEN  = 16
-
 
 ###############################################################################
 ## header, trailer
@@ -262,7 +257,32 @@ def tag_read_stream (source):
 ## convenience wrapper
 ###############################################################################
 
-KEY_MEMO = { } # static because needed for both the info file and the archive
+
+def kdf_dummy (klen, password, _nacl):
+    q, r = divmod (klen, len (password))
+    if isinstance (password, bytes) is False:
+        password = password.encode ()
+    return password * q + password [:r], b""
+
+
+SCRYPT_KEY_MEMO = { } # static because needed for both the info file and the archive
+
+
+def kdf_scrypt (params, password, nacl):
+    N = params["N"]
+    r = params["r"]
+    p = params["p"]
+    dkLen = params["dkLen"]
+
+    if nacl is None:
+        nacl = os.urandom (params["NaCl_LEN"])
+
+    key_parms = (password, nacl, N, r, p, dkLen)
+    global SCRYPT_KEY_MEMO
+    if key_parms not in SCRYPT_KEY_MEMO:
+        SCRYPT_KEY_MEMO [key_parms] = \
+            pylibscrypt.scrypt (password, nacl, N, r, p, dkLen)
+    return SCRYPT_KEY_MEMO [key_parms], nacl
 
 
 def kdf_by_version (paramversion):
@@ -271,9 +291,12 @@ def kdf_by_version (paramversion):
         raise ValueError ("no encryption parameters for version %r"
                 % paramversion)
     (kdf, params) = defs["kdf"]
-    if kdf != "scrypt":
+    fn = None
+    if kdf == "scrypt" : fn = kdf_scrypt
+    if kdf == "dummy"  : fn = kdf_dummy
+    if fn is None:
         raise ValueError ("key derivation method %r unknown" % kdf)
-    return kdf, params
+    return partial (fn, params)
 
 
 
@@ -293,23 +316,14 @@ class Crypto (object):
         self.set_parameters (*al, **akv)
 
 
-    def set_parameters (self, password, paramversion, nacl, pfx=None):
+    def set_parameters (self, password, paramversion, nacl=None, pfx=None):
         if isinstance (password, bytes) is False: password = str.encode (password)
         self.password     = password
         self.nacl         = nacl
         self.paramversion = paramversion
-        (kdf, params)     = kdf_by_version (paramversion)
-
-        N = params["N"]
-        r = params["r"]
-        p = params["p"]
-        dkLen = params["dkLen"]
-
-        key_parms = (password, nacl, N, r, p, dkLen)
-        global KEY_MEMO
-        if key_parms not in KEY_MEMO:
-            KEY_MEMO [key_parms] = pylibscrypt.scrypt (password, nacl, N, r, p, dkLen)
-        self.key = KEY_MEMO [key_parms]
+        kdf               = kdf_by_version (paramversion)
+        if kdf is not None:
+            self.key, self.nacl = kdf (password, nacl)
 
         if pfx is not None:
             self.pfx = pfx
@@ -331,9 +345,6 @@ class Encrypt (Crypto):
     ctsize = -1     # per object from .next() → .done()
 
     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)
 
 
@@ -343,7 +354,7 @@ class Encrypt (Crypto):
 
     def next (self, filename, version, paramversion, nacl):
         self.iv = self.iv_make()
-        self.curobj = (filename, version, paramversion, nacl)
+        self.curobj = (filename, version, paramversion, nacl or self.nacl)
         self.cnt += 1
         self.ctsize = 0
         self.aes = Cipher \