lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 0.4-9-g713d106

[Date Prev][Date Next] [Thread Prev][Thread Next]

python-pskc branch master updated. 0.4-9-g713d106



This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "python-pskc".

The branch, master has been updated
       via  713d10620107a0d38a90b8110a31a856fca36a85 (commit)
       via  ff811c9041312c2ae5eaa3bb47b96e3ea5f6f9db (commit)
       via  fa07aa588d8c0b5932bab2b056e07ed9c11dd7eb (commit)
       via  a444f78959cc8f160f9a1b0c1c630e42c42ae407 (commit)
       via  9b76135997517c325d9847ea28b176a12d2cb5ad (commit)
       via  d53f05b1a8be02d62b29a3890e3af92f11eaf463 (commit)
      from  5dbfefdd05133448c91f1ef014f71f3dc001dcd7 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
http://arthurdejong.org/git/python-pskc/commit/?id=713d10620107a0d38a90b8110a31a856fca36a85

commit 713d10620107a0d38a90b8110a31a856fca36a85
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Sep 11 22:15:55 2016 +0200

    Support specifying PRF in setup_pbkdf2()
    
    This also ensures that the PRF URL is normalised.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index fd8dd49..4ce3f24 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -160,7 +160,8 @@ class KeyDerivation(object):
             # pseudorandom function used
             prf = find(pbkdf2, 'PRF')
             if prf is not None:
-                self.pbkdf2_prf = prf.get('Algorithm')
+                from pskc.algorithms import normalise_algorithm
+                self.pbkdf2_prf = normalise_algorithm(prf.get('Algorithm'))
 
     def make_xml(self, encryption_key, key_names):
         from pskc.xml import mk_elem
@@ -220,6 +221,8 @@ class KeyDerivation(object):
             self.pbkdf2_iterations = 12 * 1000
         if key_length:
             self.pbkdf2_key_length = key_length
+        if prf:
+            self.pbkdf2_prf = normalise_algorithm(prf)
         return self.derive_pbkdf2(password)
 
 
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index eed76a1..22bb118 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -210,10 +210,12 @@ reasonable defaults.
 
 All properties can also be manually specified.
 
+>>> pskc = PSKC()
 >>> pskc.encryption.setup_pbkdf2(
 ...    'qwerty', iterations=1000, algorithm='aes256-cbc', key_length=24,
 ...    salt=base64.b64decode('Ej7/PEpyEpw='),
-...    key_name='PBKDF2 passphrase')
+...    key_name='PBKDF2 passphrase',
+...    prf='hmac-md5')
 >>> pskc.encryption.derivation.algorithm
 'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2'
 >>> pskc.encryption.derivation.pbkdf2_iterations
@@ -222,9 +224,11 @@ All properties can also be manually specified.
 '123eff3c4a72129c'
 >>> pskc.encryption.derivation.pbkdf2_key_length
 24
+>>> pskc.encryption.derivation.pbkdf2_prf
+'http://www.w3.org/2001/04/xmldsig-more#hmac-md5'
 >>> pskc.encryption.algorithm
 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'
 >>> pskc.encryption.key_name
 'PBKDF2 passphrase'
 >>> b2a_hex(pskc.encryption.key)
-'651e63cd57008476af1ff6422cd02e41a13be8f92db69ec9'
+'e8c5fecfb2a5cbb80ff791782ff5e125cc375bb6ba113071'

http://arthurdejong.org/git/python-pskc/commit/?id=ff811c9041312c2ae5eaa3bb47b96e3ea5f6f9db

commit ff811c9041312c2ae5eaa3bb47b96e3ea5f6f9db
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Sep 11 21:41:37 2016 +0200

    Fix bug in passing explicit key to setup_preshared_key()

diff --git a/pskc/encryption.py b/pskc/encryption.py
index 16cc565..fd8dd49 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -335,8 +335,8 @@ class Encryption(object):
         chosen for missing arguments.
         """
         self._setup_encryption(kwargs)
-        key = kwargs.pop('key', self.key)
-        if not key:
+        self.key = kwargs.pop('key', self.key)
+        if not self.key:
             from Crypto import Random
             self.key = Random.get_random_bytes(kwargs.pop(
                 'key_length', self.algorithm_key_lengths[-1]))

http://arthurdejong.org/git/python-pskc/commit/?id=fa07aa588d8c0b5932bab2b056e07ed9c11dd7eb

commit fa07aa588d8c0b5932bab2b056e07ed9c11dd7eb
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sun Sep 11 16:28:55 2016 +0200

    Clarify encryption.setup_*() documentation
    
    This tries to make it clearer that the setup_preshared_key() and
    setup_pbkdf2() functions are meant to be used when writing out PSKC
    files.

diff --git a/docs/encryption.rst b/docs/encryption.rst
index cafdb74..59242dd 100644
--- a/docs/encryption.rst
+++ b/docs/encryption.rst
@@ -93,7 +93,7 @@ The Encryption class
 
    .. function:: setup_preshared_key(...)
 
-      Configure pre-shared key encryption.
+      Configure pre-shared key encryption when writing the file.
 
       :param binary key: the encryption key to use
       :param str id: encryption key identifier
@@ -119,7 +119,7 @@ The Encryption class
 
    .. function:: setup_pbkdf2(...)
 
-      Configure password-based PSKC encryption.
+      Configure password-based PSKC encryption when writing the file.
 
       :param str password: the password to use (required)
       :param str id: encryption key identifier
diff --git a/pskc/encryption.py b/pskc/encryption.py
index 4ca2042..16cc565 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -320,7 +320,7 @@ class Encryption(object):
             self.pskc.mac.setup()
 
     def setup_preshared_key(self, **kwargs):
-        """Configure pre-shared key encryption.
+        """Configure pre-shared key encryption when writing the file.
 
         The following arguments may be supplied:
           key: the encryption key to use
@@ -342,7 +342,7 @@ class Encryption(object):
                 'key_length', self.algorithm_key_lengths[-1]))
 
     def setup_pbkdf2(self, password, **kwargs):
-        """Configure password-based PSKC encryption.
+        """Configure password-based PSKC encryption when writing the file.
 
         The following arguments may be supplied:
           password: the password to use (required)

http://arthurdejong.org/git/python-pskc/commit/?id=a444f78959cc8f160f9a1b0c1c630e42c42ae407

commit a444f78959cc8f160f9a1b0c1c630e42c42ae407
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 23 19:06:20 2016 +0200

    Fall back to encryption key for MAC
    
    This uses the encryption key also as MAC key if no MAC key has been
    specified in the PSKC file. Earlier versions of the PSKC draft specified
    this behaviour.

diff --git a/pskc/key.py b/pskc/key.py
index 56120ea..8ae0fda 100644
--- a/pskc/key.py
+++ b/pskc/key.py
@@ -122,7 +122,7 @@ class DataType(object):
             if self.value_mac:
                 mk_elem(element, 'pskc:ValueMAC', base64.b64encode(
                     self.value_mac).decode())
-            elif self.pskc.mac.key:
+            elif self.pskc.mac.algorithm:
                 mk_elem(element, 'pskc:ValueMAC', base64.b64encode(
                     self.pskc.mac.generate_mac(self.cipher_value)
                 ).decode())
diff --git a/pskc/mac.py b/pskc/mac.py
index b9dc94b..4517d43 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -58,6 +58,8 @@ def get_mac(algorithm, key, value):
     from pskc.exceptions import DecryptionError
     if key is None:
         raise DecryptionError('No MAC key available')
+    if algorithm is None:
+        raise DecryptionError('No MAC algorithm set')
     hmacfn = get_hmac(algorithm)
     if hmacfn is None:
         raise DecryptionError(
@@ -124,6 +126,8 @@ class MAC(object):
         elif self.key_cipher_value:
             return self.pskc.encryption.decrypt_value(
                 self.key_cipher_value, self.key_algorithm)
+        # fall back to encryption key
+        return self.pskc.encryption.key
 
     @key.setter
     def key(self, value):
diff --git a/tests/encryption/no-mac-key.pskcxml 
b/tests/encryption/no-mac-key.pskcxml
new file mode 100644
index 0000000..d880238
--- /dev/null
+++ b/tests/encryption/no-mac-key.pskcxml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Simple test that uses encryption and a MAC but the MAC key is absent. Older
+  versions of the format allowed a fallback to using the encryption key for
+  the HMAC function.
+-->
+
+<KeyContainer Version="1.0"
+  xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+  xmlns:ds="http://www.w3.org/2000/09/xmldsig#";
+  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#";>
+ <EncryptionKey>
+  <ds:KeyName>Pre-shared-key</ds:KeyName>
+ </EncryptionKey>
+ <MACMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha1"/>
+ <KeyPackage>
+  <Key>
+   <Data>
+    <Secret>
+     <EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       
<xenc:CipherValue>AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGv</xenc:CipherValue>
+      </xenc:CipherData>
+     </EncryptedValue>
+     <ValueMAC>N8Agsfe5A7WQFswmdvL/MHdd3y0=</ValueMAC>
+    </Secret>
+   </Data>
+  </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/tests/invalid/mac-missing.pskcxml 
b/tests/invalid/mac-missing.pskcxml
new file mode 100644
index 0000000..04b8ba7
--- /dev/null
+++ b/tests/invalid/mac-missing.pskcxml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Based on figure 6 from RFC6030 but with a missing encryption element
+  but MAC present.
+-->
+
+<KeyContainer Version="1.0"
+  xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+  xmlns:ds="http://www.w3.org/2000/09/xmldsig#";
+  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#";>
+  <MACMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
+  <KeyPackage>
+    <DeviceInfo>
+      <Manufacturer>Manufacturer</Manufacturer>
+      <SerialNo>987654321</SerialNo>
+    </DeviceInfo>
+    <CryptoModuleInfo>
+      <Id>CM_ID_001</Id>
+    </CryptoModuleInfo>
+    <Key Id="12345678" Algorithm="urn:ietf:params:xml:ns:keyprov:pskc:hotp">
+      <Issuer>Issuer</Issuer>
+      <AlgorithmParameters>
+        <ResponseFormat Length="8" Encoding="DECIMAL"/>
+      </AlgorithmParameters>
+      <Data>
+        <Secret>
+          <EncryptedValue>
+            <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+            <xenc:CipherData>
+              
<xenc:CipherValue>AAECAwQFBgcICQoLDA0OD+cIHItlB3Wra1DUpxVvOx2lef1VmNPCMl8jwZqIUqGz</xenc:CipherValue>
+            </xenc:CipherData>
+          </EncryptedValue>
+          <ValueMAC>Su+NvtQfmvfJzF6bmQiJqoLRExc=</ValueMAC>
+        </Secret>
+        <Counter>
+          <PlainValue>0</PlainValue>
+        </Counter>
+      </Data>
+    </Key>
+  </KeyPackage>
+</KeyContainer>
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index 92ce728..eed76a1 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -135,6 +135,17 @@ The IV can also be specified globally.
 'MacMacMacMacMacMacMa'
 
 
+If the PSKC file does not have a MAC key configured, older versions of the
+PSKC format allowed using the encryption key for the HMAC function.
+
+>>> pskc = PSKC('tests/encryption/no-mac-key.pskcxml')
+>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
+>>> b2a_hex(pskc.mac.key)
+'12345678901234567890123456789012'
+>>> tostr(pskc.keys[0].secret)
+'12345678901234567890'
+
+
 MAC key and algorithm will use useful defaults but can also be manually
 specified.
 
diff --git a/tests/test_invalid.doctest b/tests/test_invalid.doctest
index d8f697d..66fa241 100644
--- a/tests/test_invalid.doctest
+++ b/tests/test_invalid.doctest
@@ -158,6 +158,18 @@ Traceback (most recent call last):
 DecryptionError: MAC value does not match
 
 
+A MAC is specified but keys are missing.
+
+>>> pskc = PSKC('tests/invalid/mac-missing.pskcxml')
+>>> key = pskc.keys[0]
+>>> key.id
+'12345678'
+>>> key.secret  # doctest: +IGNORE_EXCEPTION_DETAIL
+Traceback (most recent call last):
+    ...
+DecryptionError: No MAC key available
+
+
 Checks to see that invalid values are detected.
 
 >>> pskc = PSKC('tests/invalid/not-integer.pskcxml')  # doctest: 
 >>> +IGNORE_EXCEPTION_DETAIL

http://arthurdejong.org/git/python-pskc/commit/?id=9b76135997517c325d9847ea28b176a12d2cb5ad

commit 9b76135997517c325d9847ea28b176a12d2cb5ad
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 23 17:49:53 2016 +0200

    Allow global specification of IV
    
    In older versions of the PSKC standard it was allowed to have a global
    initialization vector for CBC based encryption algorithms. It is
    probably not a good idea to re-use an IV in general.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index cf29506..4ca2042 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -64,15 +64,17 @@ def decrypt(algorithm, key, ciphertext, iv=None):
             algorithm.endswith('#aes256-cbc'):
         from Crypto.Cipher import AES
         from pskc.crypto import unpad
-        iv = ciphertext[:AES.block_size]
-        ciphertext = ciphertext[AES.block_size:]
+        if not iv:
+            iv = ciphertext[:AES.block_size]
+            ciphertext = ciphertext[AES.block_size:]
         cipher = AES.new(key, AES.MODE_CBC, iv)
         return unpad(cipher.decrypt(ciphertext))
     elif algorithm.endswith('#tripledes-cbc'):
         from Crypto.Cipher import DES3
         from pskc.crypto import unpad
-        iv = ciphertext[:DES3.block_size]
-        ciphertext = ciphertext[DES3.block_size:]
+        if not iv:
+            iv = ciphertext[:DES3.block_size]
+            ciphertext = ciphertext[DES3.block_size:]
         cipher = DES3.new(key, DES3.MODE_CBC, iv)
         return unpad(cipher.decrypt(ciphertext))
     elif algorithm.endswith('#kw-aes128') or \
@@ -100,14 +102,14 @@ def encrypt(algorithm, key, plaintext, iv=None):
         from Crypto import Random
         from Crypto.Cipher import AES
         from pskc.crypto import pad
-        iv = Random.get_random_bytes(AES.block_size)
+        iv = iv or Random.get_random_bytes(AES.block_size)
         cipher = AES.new(key, AES.MODE_CBC, iv)
         return iv + cipher.encrypt(pad(plaintext, AES.block_size))
     elif algorithm.endswith('#tripledes-cbc'):
         from Crypto import Random
         from Crypto.Cipher import DES3
         from pskc.crypto import pad
-        iv = Random.get_random_bytes(DES3.block_size)
+        iv = iv or Random.get_random_bytes(DES3.block_size)
         cipher = DES3.new(key, DES3.MODE_CBC, iv)
         return iv + cipher.encrypt(pad(plaintext, DES3.block_size))
     elif algorithm.endswith('#kw-aes128') or \
@@ -233,6 +235,7 @@ class Encryption(object):
       key_names: list of names for the key
       key_name: (first) name of the key (usually there is only one)
       key: the key value itself (binary form)
+      iv: optional initialization vector for CBC based encryption
       fields: a list of Key fields that will be encrypted on writing
 
     The key can either be assigned to the key property or derived using the
@@ -242,9 +245,10 @@ class Encryption(object):
     def __init__(self, pskc):
         self.pskc = pskc
         self.id = None
+        self._algorithm = None
         self.key_names = []
         self.key = None
-        self._algorithm = None
+        self.iv = None
         self.derivation = KeyDerivation()
         self.fields = []
 
@@ -369,8 +373,8 @@ class Encryption(object):
     def decrypt_value(self, cipher_value, algorithm=None):
         """Decrypt the cipher_value and return the plaintext value."""
         return decrypt(
-            algorithm or self.algorithm, self.key, cipher_value)
+            algorithm or self.algorithm, self.key, cipher_value, self.iv)
 
     def encrypt_value(self, plaintext):
         """Encrypt the provided value and return the cipher_value."""
-        return encrypt(self.algorithm, self.key, plaintext)
+        return encrypt(self.algorithm, self.key, plaintext, self.iv)
diff --git a/tests/encryption/aes128-cbc-noiv.pskcxml 
b/tests/encryption/aes128-cbc-noiv.pskcxml
new file mode 100644
index 0000000..1fba51b
--- /dev/null
+++ b/tests/encryption/aes128-cbc-noiv.pskcxml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Test that holds an aes128-cbc encrypted value. Key is
+  12345678901234567890123456789012. The IV is not part of the CipherValue.
+-->
+
+<KeyContainer Version="1.0"
+  xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+  xmlns:ds="http://www.w3.org/2000/09/xmldsig#";
+  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#";>
+ <EncryptionKey>
+  <ds:KeyName>Pre-shared-key</ds:KeyName>
+ </EncryptionKey>
+ <MACMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#hmac-sha224";>
+  <MACKey>
+   <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+   <xenc:CipherData>
+    
<xenc:CipherValue>Diahu/VzjP5IbRYxRgNYT+YQcIa03s5FLMnHjTM0rSQ=</xenc:CipherValue>
+   </xenc:CipherData>
+  </MACKey>
+ </MACMethod>
+ <KeyPackage>
+  <Key>
+   <Data>
+    <Secret>
+     <EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
+      <xenc:CipherData>
+       
<xenc:CipherValue>5wgci2UHdatrUNSnFW87HaV5/VWY08IyXyPBmohSoa8=</xenc:CipherValue>
+      </xenc:CipherData>
+     </EncryptedValue>
+     <ValueMAC>mNUFNm7a8VqhdmoYDX95B/V7HY36hHOKr6F9jQ==</ValueMAC>
+    </Secret>
+   </Data>
+  </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index aba2014..92ce728 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -124,6 +124,17 @@ DecryptionError: Invalid key length
 '2923bf85e06dd6ae529149f1f1bae9eab3a7da3d860d3e98'
 
 
+The IV can also be specified globally.
+
+>>> pskc = PSKC('tests/encryption/aes128-cbc-noiv.pskcxml')
+>>> pskc.encryption.key = a2b_hex('12345678901234567890123456789012')
+>>> pskc.encryption.iv = a2b_hex('000102030405060708090a0b0c0d0e0f')
+>>> tostr(pskc.keys[0].secret)
+'12345678901234567890'
+>>> tostr(pskc.mac.key)
+'MacMacMacMacMacMacMa'
+
+
 MAC key and algorithm will use useful defaults but can also be manually
 specified.
 

http://arthurdejong.org/git/python-pskc/commit/?id=d53f05b1a8be02d62b29a3890e3af92f11eaf463

commit d53f05b1a8be02d62b29a3890e3af92f11eaf463
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Sat Apr 23 17:29:39 2016 +0200

    Move crypto to functions
    
    This makes it much easier to test the encryption, decryption and HMAC
    processing separate from the PSKC parsing.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index f63bf7b..cf29506 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -29,6 +29,97 @@ The encryption key can be derived using the KeyDerivation 
class.
 import base64
 
 
+def algorithm_key_lengths(algorithm):
+    """Return the possible key lengths for the configured algorithm."""
+    from pskc.exceptions import DecryptionError
+    if algorithm is None:
+        raise DecryptionError('No algorithm specified')
+    elif algorithm.endswith('#aes128-cbc') or \
+            algorithm.endswith('#aes192-cbc') or \
+            algorithm.endswith('#aes256-cbc'):
+        return [int(algorithm[-7:-4]) // 8]
+    elif algorithm.endswith('#tripledes-cbc') or \
+            algorithm.endswith('#kw-tripledes'):
+        from Crypto.Cipher import DES3
+        return list(DES3.key_size)
+    elif algorithm.endswith('#kw-aes128') or \
+            algorithm.endswith('#kw-aes192') or \
+            algorithm.endswith('#kw-aes256'):
+        return [int(algorithm[-3:]) // 8]
+    else:
+        raise DecryptionError('Unsupported algorithm: %r' % algorithm)
+
+
+def decrypt(algorithm, key, ciphertext, iv=None):
+    """Decrypt the ciphertext and return the plaintext value."""
+    from pskc.exceptions import DecryptionError
+    if key is None:
+        raise DecryptionError('No key available')
+    if algorithm is None:
+        raise DecryptionError('No algorithm specified')
+    if len(key) not in algorithm_key_lengths(algorithm):
+        raise DecryptionError('Invalid key length')
+    if algorithm.endswith('#aes128-cbc') or \
+            algorithm.endswith('#aes192-cbc') or \
+            algorithm.endswith('#aes256-cbc'):
+        from Crypto.Cipher import AES
+        from pskc.crypto import unpad
+        iv = ciphertext[:AES.block_size]
+        ciphertext = ciphertext[AES.block_size:]
+        cipher = AES.new(key, AES.MODE_CBC, iv)
+        return unpad(cipher.decrypt(ciphertext))
+    elif algorithm.endswith('#tripledes-cbc'):
+        from Crypto.Cipher import DES3
+        from pskc.crypto import unpad
+        iv = ciphertext[:DES3.block_size]
+        ciphertext = ciphertext[DES3.block_size:]
+        cipher = DES3.new(key, DES3.MODE_CBC, iv)
+        return unpad(cipher.decrypt(ciphertext))
+    elif algorithm.endswith('#kw-aes128') or \
+            algorithm.endswith('#kw-aes192') or \
+            algorithm.endswith('#kw-aes256'):
+        from pskc.crypto.aeskw import unwrap
+        return unwrap(ciphertext, key)
+    elif algorithm.endswith('#kw-tripledes'):
+        from pskc.crypto.tripledeskw import unwrap
+        return unwrap(ciphertext, key)
+
+
+def encrypt(algorithm, key, plaintext, iv=None):
+    """Encrypt the provided value with the key using the algorithm."""
+    from pskc.exceptions import EncryptionError
+    if key is None:
+        raise EncryptionError('No key available')
+    if algorithm is None:
+        raise EncryptionError('No algorithm specified')
+    if len(key) not in algorithm_key_lengths(algorithm):
+        raise EncryptionError('Invalid key length')
+    if algorithm.endswith('#aes128-cbc') or \
+            algorithm.endswith('#aes192-cbc') or \
+            algorithm.endswith('#aes256-cbc'):
+        from Crypto import Random
+        from Crypto.Cipher import AES
+        from pskc.crypto import pad
+        iv = Random.get_random_bytes(AES.block_size)
+        cipher = AES.new(key, AES.MODE_CBC, iv)
+        return iv + cipher.encrypt(pad(plaintext, AES.block_size))
+    elif algorithm.endswith('#tripledes-cbc'):
+        from Crypto import Random
+        from Crypto.Cipher import DES3
+        from pskc.crypto import pad
+        iv = Random.get_random_bytes(DES3.block_size)
+        cipher = DES3.new(key, DES3.MODE_CBC, iv)
+        return iv + cipher.encrypt(pad(plaintext, DES3.block_size))
+    elif algorithm.endswith('#kw-aes128') or \
+            algorithm.endswith('#kw-aes192') or \
+            algorithm.endswith('#kw-aes256'):
+        from pskc.crypto.aeskw import wrap
+        return wrap(plaintext, key)
+    elif algorithm.endswith('#kw-tripledes'):
+        from pskc.crypto.tripledeskw import wrap
+        return wrap(plaintext, key)
+
+
 class KeyDerivation(object):
     """Handle key derivation.
 
@@ -273,93 +364,13 @@ class Encryption(object):
     @property
     def algorithm_key_lengths(self):
         """Provide the possible key lengths for the configured algorithm."""
-        from pskc.exceptions import DecryptionError
-        algorithm = self.algorithm
-        if algorithm is None:
-            raise DecryptionError('No algorithm specified')
-        elif algorithm.endswith('#aes128-cbc') or \
-                algorithm.endswith('#aes192-cbc') or \
-                algorithm.endswith('#aes256-cbc'):
-            return [int(algorithm[-7:-4]) // 8]
-        elif algorithm.endswith('#tripledes-cbc') or \
-                algorithm.endswith('#kw-tripledes'):
-            from Crypto.Cipher import DES3
-            return list(DES3.key_size)
-        elif algorithm.endswith('#kw-aes128') or \
-                algorithm.endswith('#kw-aes192') or \
-                algorithm.endswith('#kw-aes256'):
-            return [int(algorithm[-3:]) // 8]
-        else:
-            raise DecryptionError('Unsupported algorithm: %r' % algorithm)
+        return algorithm_key_lengths(self.algorithm)
 
     def decrypt_value(self, cipher_value, algorithm=None):
         """Decrypt the cipher_value and return the plaintext value."""
-        from pskc.exceptions import DecryptionError
-        key = self.key
-        if key is None:
-            raise DecryptionError('No key available')
-        algorithm = algorithm or self.algorithm
-        if algorithm is None:
-            raise DecryptionError('No algorithm specified')
-        if len(key) not in self.algorithm_key_lengths:
-            raise DecryptionError('Invalid key length')
-        if algorithm.endswith('#aes128-cbc') or \
-                algorithm.endswith('#aes192-cbc') or \
-                algorithm.endswith('#aes256-cbc'):
-            from Crypto.Cipher import AES
-            from pskc.crypto import unpad
-            iv = cipher_value[:AES.block_size]
-            ciphertext = cipher_value[AES.block_size:]
-            cipher = AES.new(key, AES.MODE_CBC, iv)
-            return unpad(cipher.decrypt(ciphertext))
-        elif algorithm.endswith('#tripledes-cbc'):
-            from Crypto.Cipher import DES3
-            from pskc.crypto import unpad
-            iv = cipher_value[:DES3.block_size]
-            ciphertext = cipher_value[DES3.block_size:]
-            cipher = DES3.new(key, DES3.MODE_CBC, iv)
-            return unpad(cipher.decrypt(ciphertext))
-        elif algorithm.endswith('#kw-aes128') or \
-                algorithm.endswith('#kw-aes192') or \
-                algorithm.endswith('#kw-aes256'):
-            from pskc.crypto.aeskw import unwrap
-            return unwrap(cipher_value, key)
-        elif algorithm.endswith('#kw-tripledes'):
-            from pskc.crypto.tripledeskw import unwrap
-            return unwrap(cipher_value, key)
+        return decrypt(
+            algorithm or self.algorithm, self.key, cipher_value)
 
     def encrypt_value(self, plaintext):
         """Encrypt the provided value and return the cipher_value."""
-        from pskc.exceptions import EncryptionError
-        key = self.key
-        if key is None:
-            raise EncryptionError('No key available')
-        algorithm = self.algorithm
-        if algorithm is None:
-            raise EncryptionError('No algorithm specified')
-        if len(key) not in self.algorithm_key_lengths:
-            raise EncryptionError('Invalid key length')
-        if algorithm.endswith('#aes128-cbc') or \
-                algorithm.endswith('#aes192-cbc') or \
-                algorithm.endswith('#aes256-cbc'):
-            from Crypto import Random
-            from Crypto.Cipher import AES
-            from pskc.crypto import pad
-            iv = Random.get_random_bytes(AES.block_size)
-            cipher = AES.new(key, AES.MODE_CBC, iv)
-            return iv + cipher.encrypt(pad(plaintext, AES.block_size))
-        elif algorithm.endswith('#tripledes-cbc'):
-            from Crypto import Random
-            from Crypto.Cipher import DES3
-            from pskc.crypto import pad
-            iv = Random.get_random_bytes(DES3.block_size)
-            cipher = DES3.new(key, DES3.MODE_CBC, iv)
-            return iv + cipher.encrypt(pad(plaintext, DES3.block_size))
-        elif algorithm.endswith('#kw-aes128') or \
-                algorithm.endswith('#kw-aes192') or \
-                algorithm.endswith('#kw-aes256'):
-            from pskc.crypto.aeskw import wrap
-            return wrap(plaintext, key)
-        elif algorithm.endswith('#kw-tripledes'):
-            from pskc.crypto.tripledeskw import wrap
-            return wrap(plaintext, key)
+        return encrypt(self.algorithm, self.key, plaintext)
diff --git a/pskc/mac.py b/pskc/mac.py
index 56e8cfa..b9dc94b 100644
--- a/pskc/mac.py
+++ b/pskc/mac.py
@@ -53,6 +53,18 @@ def get_hmac(algorithm):
         return lambda key, value: hmac.new(key, value, digestmod).digest()
 
 
+def get_mac(algorithm, key, value):
+    """Generate the MAC value over the specified value."""
+    from pskc.exceptions import DecryptionError
+    if key is None:
+        raise DecryptionError('No MAC key available')
+    hmacfn = get_hmac(algorithm)
+    if hmacfn is None:
+        raise DecryptionError(
+            'Unsupported MAC algorithm: %r' % algorithm)
+    return hmacfn(key, value)
+
+
 class MAC(object):
     """Class describing the MAC algorithm to use and how to get the key.
 
@@ -142,15 +154,7 @@ class MAC(object):
 
     def generate_mac(self, value):
         """Generate the MAC over the specified value."""
-        from pskc.exceptions import DecryptionError
-        key = self.key
-        if key is None:
-            raise DecryptionError('No MAC key available')
-        hmacfn = get_hmac(self.algorithm)
-        if hmacfn is None:
-            raise DecryptionError(
-                'Unsupported MAC algorithm: %r' % self.algorithm)
-        return hmacfn(key, value)
+        return get_mac(self.algorithm, self.key, value)
 
     def check_value(self, value, value_mac):
         """Check if the provided value matches the MAC.

-----------------------------------------------------------------------

Summary of changes:
 docs/encryption.rst                                |   4 +-
 pskc/encryption.py                                 | 198 +++++++++++----------
 pskc/key.py                                        |   2 +-
 pskc/mac.py                                        |  26 ++-
 ...{aes128-cbc.pskcxml => aes128-cbc-noiv.pskcxml} |   8 +-
 .../{aes128-cbc.pskcxml => no-mac-key.pskcxml}     |  16 +-
 .../{mac-value.pskcxml => mac-missing.pskcxml}     |  15 +-
 tests/test_encryption.doctest                      |  30 +++-
 tests/test_invalid.doctest                         |  12 ++
 9 files changed, 180 insertions(+), 131 deletions(-)
 copy tests/encryption/{aes128-cbc.pskcxml => aes128-cbc-noiv.pskcxml} (73%)
 copy tests/encryption/{aes128-cbc.pskcxml => no-mac-key.pskcxml} (63%)
 copy tests/invalid/{mac-value.pskcxml => mac-missing.pskcxml} (74%)


hooks/post-receive
-- 
python-pskc
-- 
To unsubscribe send an email to
python-pskc-commits-unsubscribe@lists.arthurdejong.org or see
https://lists.arthurdejong.org/python-pskc-commits/