[Checkins] SVN: keas.kmi/trunk/ - Added a cache for unencrypted DEKs in the key management facility, like it

Stephan Richter srichter at gmail.com
Thu Oct 7 16:11:35 EDT 2010


Log message for revision 117381:
  - Added a cache for unencrypted DEKs in the key management facility, like it
    was already done for the local key management facility. This increases
    encryption and decryption performance by an order of magnitude from 
    roughly 2ms to 0.2ms.
  
  - Get ready for release.
  

Changed:
  U   keas.kmi/trunk/CHANGES.txt
  U   keas.kmi/trunk/setup.py
  U   keas.kmi/trunk/src/keas/kmi/README.txt
  U   keas.kmi/trunk/src/keas/kmi/facility.py

-=-
Modified: keas.kmi/trunk/CHANGES.txt
===================================================================
--- keas.kmi/trunk/CHANGES.txt	2010-10-07 19:26:24 UTC (rev 117380)
+++ keas.kmi/trunk/CHANGES.txt	2010-10-07 20:11:34 UTC (rev 117381)
@@ -2,10 +2,13 @@
 CHANGES
 =======
 
-2.0.1 (unreleased)
+2.1.0 (2010-10-07)
 ------------------
 
-- No changes yet.
+- Added a cache for unencrypted DEKs in the key management facility, like it
+  was already done for the local key management facility. This increases
+  encryption and decryption performance by an order of magnitude from roughly
+  2ms to 0.2ms.
 
 2.0.0 (2010-09-29)
 ------------------

Modified: keas.kmi/trunk/setup.py
===================================================================
--- keas.kmi/trunk/setup.py	2010-10-07 19:26:24 UTC (rev 117380)
+++ keas.kmi/trunk/setup.py	2010-10-07 20:11:34 UTC (rev 117381)
@@ -22,7 +22,7 @@
 
 setup (
     name='keas.kmi',
-    version = '2.0.1dev',
+    version = '2.1.0',
     author = "Stephan Richter and the Zope Community",
     author_email = "zope-dev at zope.org",
     description = "A Key Management Infrastructure",

Modified: keas.kmi/trunk/src/keas/kmi/README.txt
===================================================================
--- keas.kmi/trunk/src/keas/kmi/README.txt	2010-10-07 19:26:24 UTC (rev 117380)
+++ keas.kmi/trunk/src/keas/kmi/README.txt	2010-10-07 20:11:34 UTC (rev 117381)
@@ -69,9 +69,9 @@
   ...    from hashlib import md5
   ... except ImportError:
   ...    from md5 import md5
-  >>> hash = md5(key)
+  >>> hash_key = md5(key).hexdigest()
 
-  >>> len(keys.get(hash.hexdigest()))
+  >>> len(keys.get(hash_key))
   64
 
 Our key management facility also supports the encryption service, which allows
@@ -94,7 +94,35 @@
 And that's pretty much all there is to it. Most of the complicated
 crypto-related work happens under the hood, transparent to the user.
 
+One final note. Once the data encrypting key is looked up and decrypted, it is
+cached, since constantly decrypting the the DEK is expensive.
 
+  >>> hash_key in keys._KeyManagementFacility__dek_cache
+  True
+
+A timeout (in seconds) controls when a key must be looked up:
+
+  >>> keys.timeout
+  3600
+
+Let's now force a reload by setting the timeout to zero:
+
+  >>> keys.timeout = 0
+
+The cache is a dictionary of key encrypting key to a 2-tuple that contains the
+date/time the key has been fetched and the unencrypted DEK.
+
+  >>> firstTime = keys._KeyManagementFacility__dek_cache[hash_key][0]
+
+  >>> keys.decrypt(key, encrypted)
+  'Stephan Richter'
+
+  >>> secondTime = keys._KeyManagementFacility__dek_cache[hash_key][0]
+
+  >>> firstTime < secondTime
+  True
+
+
 The Local Key Management Facility
 ---------------------------------
 
@@ -155,7 +183,7 @@
 
 In this implementation, we do cache the keys in a private attribute:
 
-  >>> key in localKeys._cache
+  >>> key in localKeys._LocalKeyManagementFacility__cache
   True
 
 A timeout (in seconds) controls when a key must be refetched:
@@ -171,12 +199,12 @@
 date/time the key has been fetched, the encryption (public) key, and the
 decryption (private) key.
 
-  >>> firstTime = localKeys._cache[key][0]
+  >>> firstTime = localKeys._LocalKeyManagementFacility__cache[key][0]
 
   >>> localKeys.decrypt(key, encrypted)
   'Stephan Richter'
 
-  >>> secondTime = localKeys._cache[key][0]
+  >>> secondTime = localKeys._LocalKeyManagementFacility__cache[key][0]
 
   >>> firstTime < secondTime
   True

Modified: keas.kmi/trunk/src/keas/kmi/facility.py
===================================================================
--- keas.kmi/trunk/src/keas/kmi/facility.py	2010-10-07 19:26:24 UTC (rev 117380)
+++ keas.kmi/trunk/src/keas/kmi/facility.py	2010-10-07 20:11:34 UTC (rev 117381)
@@ -73,6 +73,8 @@
 class KeyManagementFacility(EncryptionService):
     zope.interface.implements(interfaces.IExtendedKeyManagementFacility)
 
+    timeout = 3600
+
     rsaKeyLength = 512 # The length of the key encrypting key
     rsaKeyExponent = 161 # Should be sufficiently high and non-symmetric
     rsaPassphrase = 'key management facility'
@@ -82,7 +84,8 @@
 
     def __init__(self, storage_dir):
         self.storage_dir = storage_dir
-        self.__cache = {}
+        self.__data_cache = {}
+        self.__dek_cache = {}
 
     def keys(self):
         return [filename[:-4] for filename in os.listdir(self.storage_dir)
@@ -92,14 +95,14 @@
         return iter(self.keys())
 
     def __getitem__(self, name):
-        if name in self.__cache:
-            return self.__cache[name]
+        if name in self.__data_cache:
+            return self.__data_cache[name]
         if name+'.dek' not in os.listdir(self.storage_dir):
             raise KeyError(name)
         fn = os.path.join(self.storage_dir, name+'.dek')
         with open(fn, 'rb') as file:
             data = file.read()
-            self.__cache[name] = data
+            self.__data_cache[name] = data
             return data
 
     def get(self, name, default=None):
@@ -129,8 +132,8 @@
         logger.info('New key added (hash): %s', name)
 
     def __delitem__(self, name):
-        if name in self.__cache:
-            del self.__cache[name]
+        if name in self.__data_cache:
+            del self.__data_cache[name]
         fn = os.path.join(self.storage_dir, name+'.dek')
         os.remove(fn)
         logger.info('Key removed (hash): %s', name)
@@ -161,13 +164,21 @@
         # 1. Create the lookup key in the container
         hash = md5()
         hash.update(key)
-        # 2. Extract the encrypted encryption key
-        encryptedKey = self[hash.hexdigest()]
-        # 3. Decrypt the key.
+        hash_key = hash.hexdigest()
+        # 2. Try to look up the key in the cache first.
+        if (hash_key in self.__dek_cache and
+            self.__dek_cache[hash_key][0] + self.timeout > time.time()):
+            return self.__dek_cache[hash_key][1]
+        # 3. Extract the encrypted encryption key
+        encryptedKey = self[hash_key]
+        # 4. Decrypt the key.
         rsa = M2Crypto.RSA.load_key_string(key)
         decryptedKey = rsa.private_decrypt(encryptedKey, self.rsaPadding)
-        # 4. Return decrypted encryption key
-        logger.info('Encryption key requested: %s', hash.hexdigest())
+        # 5. Return decrypted encryption key
+        logger.info('Encryption key requested: %s', hash_key)
+        # 6. Add the key to the cache
+        self.__dek_cache[hash_key] = (time.time(), decryptedKey)
+        # 7. Return the key
         return decryptedKey
 
     def __repr__(self):
@@ -183,7 +194,7 @@
 
     def __init__(self, url):
         self.url = url
-        self._cache = {}
+        self.__cache = {}
 
     def generate(self):
         """See interfaces.IKeyGenerationService"""
@@ -197,16 +208,16 @@
 
     def getEncryptionKey(self, key):
         """Given the key encrypting key, get the encryption key."""
-        if (key in self._cache and
-            self._cache[key][0] + self.timeout > time.time()):
-            return self._cache[key][1]
+        if (key in self.__cache and
+            self.__cache[key][0] + self.timeout > time.time()):
+            return self.__cache[key][1]
         pieces = urlparse.urlparse(self.url)
         conn = self.httpConnFactory(pieces.netloc)
         conn.request('POST', '/key', key, {'content-type': 'text/plain'})
         response = conn.getresponse()
         encryptionKey = response.read()
         response.close()
-        self._cache[key] = (time.time(), encryptionKey)
+        self.__cache[key] = (time.time(), encryptionKey)
         return encryptionKey
 
     def __repr__(self):



More information about the checkins mailing list