lists.arthurdejong.org
RSS feed

python-pskc branch master updated. 0.1-22-g287afa7

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

python-pskc branch master updated. 0.1-22-g287afa7



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  287afa7b9d8d8d0a3a6fb6db67939ced0ab4caa0 (commit)
       via  99ba287f5aeb53ee252f9ad2e14086a439b187c1 (commit)
       via  ebf894573f716c63a99970cdc15824322a71ed1d (commit)
       via  5720fe55f7549feb6f0319b720cded777e8f6d9f (commit)
       via  7164d892734dd2cf7a4358ddb31f532e1f781721 (commit)
      from  ccebb694d460ec2292a51a3e8d9f81fe54969c3e (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=287afa7b9d8d8d0a3a6fb6db67939ced0ab4caa0

commit 287afa7b9d8d8d0a3a6fb6db67939ced0ab4caa0
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri May 30 13:26:42 2014 +0200

    Support kw-aes128, kw-aes192 and kw-aes256
    
    This adds support for key unwrapping using the RFC 3394 or RFC 5649
    algorithm if the PSKC file uses this.

diff --git a/pskc/encryption.py b/pskc/encryption.py
index 9b6f763..d1451df 100644
--- a/pskc/encryption.py
+++ b/pskc/encryption.py
@@ -93,6 +93,15 @@ class EncryptedValue(object):
             ciphertext = self.cipher_value[DES3.block_size:]
             cipher = DES3.new(key, DES3.MODE_CBC, iv)
             return unpad(cipher.decrypt(ciphertext))
+        elif self.algorithm.endswith('#kw-aes128') or \
+             self.algorithm.endswith('#kw-aes192') or \
+             self.algorithm.endswith('#kw-aes256'):
+            from pskc.aeskw import unwrap
+            from Crypto.Cipher import AES
+            if len(key) * 8 != int(self.algorithm[-3:]) or \
+               len(key) not in AES.key_size:
+                raise DecryptionError('Invalid key length')
+            return unwrap(self.cipher_value, key)
         else:
             raise DecryptionError('Unsupported algorithm: %r' % self.algorithm)
 
diff --git a/tests/kw-aes128.pskcxml b/tests/kw-aes128.pskcxml
new file mode 100644
index 0000000..29ba6de
--- /dev/null
+++ b/tests/kw-aes128.pskcxml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Test that holds an kw-aes128 encrypted value. Key is
+  000102030405060708090A0B0C0D0E0F and the resulting plaintext should be
+  00112233445566778899AABBCCDDEEFF.
+-->
+
+<KeyContainer Version="1.0"
+  xmlns="urn:ietf:params:xml:ns:keyprov:pskc"
+  xmlns:xenc="http://www.w3.org/2001/04/xmlenc#";>
+ <EncryptionKey>
+  <KeyName xmlns="http://www.w3.org/2000/09/xmldsig#";>Pre-shared-key</KeyName>
+ </EncryptionKey>
+ <KeyPackage>
+  <Key>
+   <Data>
+    <Secret>
+     <EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes128"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>H6aLCoEStEeu80vY+1p7gp0+hiNx0s/l</xenc:CipherValue>
+      </xenc:CipherData>
+     </EncryptedValue>
+    </Secret>
+   </Data>
+  </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/tests/kw-aes192.pskcxml b/tests/kw-aes192.pskcxml
new file mode 100644
index 0000000..20bb260
--- /dev/null
+++ b/tests/kw-aes192.pskcxml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Test that holds an kw-aes192 encrypted value. Key is
+  000102030405060708090A0B0C0D0E0F1011121314151617, plain value is
+  00112233445566778899AABBCCDDEEFF.
+-->
+
+<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>
+ <KeyPackage>
+  <Key>
+   <Data>
+    <Secret>
+     <EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes192"/>
+      <xenc:CipherData>
+       <xenc:CipherValue>lneLJa5spDX5K1uXwFCu0kaKuKF62E5d</xenc:CipherValue>
+      </xenc:CipherData>
+     </EncryptedValue>
+    </Secret>
+   </Data>
+  </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/tests/kw-aes256.pskcxml b/tests/kw-aes256.pskcxml
new file mode 100644
index 0000000..2bd6e56
--- /dev/null
+++ b/tests/kw-aes256.pskcxml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  Test that holds an kw-aes256 encrypted value. Key is
+  000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F,
+  plain value is 00112233445566778899AABBCCDDEEFF0001020304050607.
+-->
+
+<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>
+ <KeyPackage>
+  <Key>
+   <Data>
+    <Secret>
+     <EncryptedValue>
+      <xenc:EncryptionMethod 
Algorithm="http://www.w3.org/2001/04/xmlenc#kw-aes256"/>
+      <xenc:CipherData>
+       
<xenc:CipherValue>qPm8FhLGiz/25vT74w5x5Haci4CjLLiVjNXRfWslTaE=</xenc:CipherValue>
+      </xenc:CipherData>
+     </EncryptedValue>
+    </Secret>
+   </Data>
+  </Key>
+ </KeyPackage>
+</KeyContainer>
diff --git a/tests/test_encryption.doctest b/tests/test_encryption.doctest
index 3f54998..19ea062 100644
--- a/tests/test_encryption.doctest
+++ b/tests/test_encryption.doctest
@@ -53,3 +53,31 @@ DecryptionError: Invalid key length
 >>> pskc.encryption.key = '12345678901234567890123456789012'.decode('hex')
 >>> pskc.keys[0].secret
 '12345678901234567890'
+
+
+>>> pskc = PSKC('tests/kw-aes128.pskcxml')
+>>> pskc.encryption.key = '1234'.decode('hex')
+>>> pskc.keys[0].secret
+Traceback (most recent call last):
+    ...
+DecryptionError: Invalid key length
+>>> pskc.encryption.key = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+>>> pskc.keys[0].secret.encode('hex')
+'00112233445566778899aabbccddeeff'
+
+
+>>> pskc = PSKC('tests/kw-aes192.pskcxml')
+>>> pskc.encryption.key = '000102030405060708090a0b0c0d0e0f'.decode('hex')
+>>> pskc.keys[0].secret
+Traceback (most recent call last):
+    ...
+DecryptionError: Invalid key length
+>>> pskc.encryption.key = 
'000102030405060708090a0b0c0d0e0f1011121314151617'.decode('hex')
+>>> pskc.keys[0].secret.encode('hex')
+'00112233445566778899aabbccddeeff'
+
+
+>>> pskc = PSKC('tests/kw-aes256.pskcxml')
+>>> pskc.encryption.key = 
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'.decode('hex')
+>>> pskc.keys[0].secret.encode('hex')
+'00112233445566778899aabbccddeeff0001020304050607'

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

commit 99ba287f5aeb53ee252f9ad2e14086a439b187c1
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Fri May 30 13:09:19 2014 +0200

    Implement padding as specified in RFC 5649
    
    This adds a pad argument with which padding can be forced or disabled.

diff --git a/pskc/aeskw.py b/pskc/aeskw.py
index 31877dd..24e90b0 100644
--- a/pskc/aeskw.py
+++ b/pskc/aeskw.py
@@ -18,10 +18,10 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 # 02110-1301 USA
 
-"""Implement key wrapping as described in RFC 3394."""
+"""Implement key wrapping as described in RFC 3394 and RFC 5649."""
 
 from Crypto.Cipher import AES
-from Crypto.Util.number import long_to_bytes
+from Crypto.Util.number import bytes_to_long, long_to_bytes
 from Crypto.Util.strxor import strxor
 
 from pskc.exceptions import EncryptionError, DecryptionError
@@ -32,23 +32,43 @@ def _split(value):
 
 
 RFC3394_IV = 'a6a6a6a6a6a6a6a6'.decode('hex')
+RFC5649_IV = 'a65959a6'.decode('hex')
 
 
-def wrap(plaintext, key, iv=None):
+def wrap(plaintext, key, iv=None, pad=None):
     """Apply the AES key wrap algorithm to the plaintext.
 
-    The iv can specify an initial value, otherwise the value from RFC 3394
-    will be used.
-    """
+    The iv can specify an initial value, otherwise the value from RFC 3394 or
+    RFC 5649 will be used, depending on the plaintext length and the value of
+    pad.
 
-    if len(plaintext) % 8 != 0 or len(plaintext) < 16:
+    If pad is True, padding as described in RFC 5649 will always be used. If
+    pad is False, padding is disabled. Other values automatically enable RFC
+    5649 padding when needed."""
+
+    if iv is not None:
+        pad = False
+
+    mli = len(plaintext)
+    if pad is False and (mli % 8 != 0 or mli < 16):
         raise EncryptionError('Plaintext length wrong')
+    if mli % 8 != 0 and pad is not False:
+        r = (mli + 7) // 8
+        plaintext += ((r * 8) - mli) * '\0'
 
     if iv is None:
-        iv = RFC3394_IV
+        if len(plaintext) != mli or pad is True:
+            iv = RFC5649_IV + long_to_bytes(mli, 4)
+        else:
+            iv = RFC3394_IV
 
     encrypt = AES.new(key).encrypt
     n = len(plaintext) / 8
+
+    if n == 1:
+        # RFC 5649 shortcut
+        return encrypt(iv + plaintext)
+
     A = iv
     R = [plaintext[i * 8:i * 8 + 8]
          for i in range(n)]
@@ -59,29 +79,45 @@ def wrap(plaintext, key, iv=None):
     return A + ''.join(R)
 
 
-def unwrap(ciphertext, key, iv=None):
+def unwrap(ciphertext, key, iv=None, pad=None):
     """Apply the AES key unwrap algorithm to the ciphertext.
 
-    The iv can specify an initial value, otherwise the value from RFC 3394
-    will be used.
-    """
+    The iv can specify an initial value, otherwise the value from RFC 3394 or
+    RFC 5649 will be used, depending on the value of pad.
 
-    if len(ciphertext) % 8 != 0 or len(ciphertext) < 24:
-        raise DecryptionError('Ciphertext length wrong')
+    If pad is False, unpadding as described in RFC 5649 will be disabled,
+    otherwise checking and removing the padding is automatically done."""
 
-    if iv is None:
-        iv = RFC3394_IV
+    if iv is not None:
+        pad = False
+
+    if len(ciphertext) % 8 != 0 or (pad is False and len(ciphertext) < 24):
+        raise DecryptionError('Ciphertext length wrong')
 
     decrypt = AES.new(key).decrypt
     n = len(ciphertext) / 8 - 1
-    A = ciphertext[:8]
-    R = [ciphertext[(i + 1) * 8:(i + 2) * 8]
-         for i in range(n)]
-    for j in reversed(range(6)):
-        for i in reversed(range(n)):
-            A = strxor(A, long_to_bytes(n * j + i + 1, 8))
-            A, R[i] = _split(decrypt(A + R[i]))
 
-    if A == iv:
-        return ''.join(R)
+    if n == 1:
+        A, plaintext = _split(decrypt(ciphertext))
+    else:
+        A = ciphertext[:8]
+        R = [ciphertext[(i + 1) * 8:(i + 2) * 8]
+             for i in range(n)]
+        for j in reversed(range(6)):
+            for i in reversed(range(n)):
+                A = strxor(A, long_to_bytes(n * j + i + 1, 8))
+                A, R[i] = _split(decrypt(A + R[i]))
+        plaintext = ''.join(R)
+
+    if iv is None:
+        if A == RFC3394_IV and pad is not True:
+            return plaintext
+        elif A[:4] == RFC5649_IV and pad is not False:
+            mli = bytes_to_long(A[4:])
+            # check padding length is valid and only contains zeros
+            if 8 * (n - 1) < mli <= 8 * n and \
+               all(x == '\0' for x in plaintext[mli:]):
+                return plaintext[:mli]
+    elif A == iv:
+        return plaintext
     raise DecryptionError('IV does not match')
diff --git a/tests/test_aeskw.doctest b/tests/test_aeskw.doctest
index 5a764d9..ad3a0f9 100644
--- a/tests/test_aeskw.doctest
+++ b/tests/test_aeskw.doctest
@@ -101,7 +101,85 @@ Traceback (most recent call last):
 DecryptionError: Ciphertext length wrong
 
 
-Lastly, an explicit IV can be set.
+Wrap 20 octets with a 192-bit key (first example from section 6 of RFC 5649).
+
+>>> key = '5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8'.decode('hex')
+>>> plaintext = 'c37b7e6492584340bed12207808941155068f738'.decode('hex')
+>>> ciphertext = 
'138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+>>> wrap(plaintext, key, pad=False)  # disable padding
+Traceback (most recent call last):
+    ...
+EncryptionError: Plaintext length wrong
+>>> unwrap(ciphertext, key, pad=False)
+Traceback (most recent call last):
+    ...
+DecryptionError: IV does not match
+
+
+Wrap 7 octets with a 192-bit key (second example from section 6 of RFC 5649).
+
+>>> key = '5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8'.decode('hex')
+>>> plaintext = '466f7250617369'.decode('hex')
+>>> ciphertext = 'afbeb0f07dfbf5419200f2ccb50bb24f'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+>>> wrap(plaintext, key, pad=False)  # disable padding
+Traceback (most recent call last):
+    ...
+EncryptionError: Plaintext length wrong
+>>> unwrap(ciphertext, key, pad=False)
+Traceback (most recent call last):
+    ...
+DecryptionError: Ciphertext length wrong
+
+
+Normally padding is only done if needed but it can be forced.
+
+>>> key = '000102030405060708090A0B0C0D0E0F'.decode('hex')
+>>> plaintext = '00112233445566778899AABBCCDDEEFF'.decode('hex')
+>>> ciphertext = 
'1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+>>> ciphertext = 
'2cef0c9e30de26016c230cb78bc60d51b1fe083ba0c79cd5'.decode('hex')
+>>> wrap(plaintext, key, pad=True) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+>>> unwrap(ciphertext, key, pad=False)  # disabling padding fails IV check
+Traceback (most recent call last):
+    ...
+DecryptionError: IV does not match
+
+
+Padding can also be disabled. This also disables the shortcut for small
+plaintexts as described in RFC 5649.
+
+>>> key = '000102030405060708090A0B0C0D0E0F'.decode('hex')
+>>> plaintext = '0011223344556677'.decode('hex')
+>>> ciphertext = 'f4740052e82a225174ce86fbd7b805e7'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+>>> wrap(plaintext, key, pad=False)  # disable padding
+Traceback (most recent call last):
+    ...
+EncryptionError: Plaintext length wrong
+>>> unwrap(ciphertext, key, pad=False)
+Traceback (most recent call last):
+    ...
+DecryptionError: Ciphertext length wrong
+
+
+Lastly, an explicit IV can be set but this disables the padding functionality.
 
 >>> key = '000102030405060708090A0B0C0D0E0F'.decode('hex')
 >>> plaintext = '0011223344556677'.decode('hex')

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

commit ebf894573f716c63a99970cdc15824322a71ed1d
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu May 29 20:33:56 2014 +0200

    Allow speciying an initial value for key wrapping

diff --git a/pskc/aeskw.py b/pskc/aeskw.py
index da75a18..31877dd 100644
--- a/pskc/aeskw.py
+++ b/pskc/aeskw.py
@@ -34,15 +34,22 @@ def _split(value):
 RFC3394_IV = 'a6a6a6a6a6a6a6a6'.decode('hex')
 
 
-def wrap(plaintext, key):
-    """Apply the AES key wrap algorithm to the plaintext."""
+def wrap(plaintext, key, iv=None):
+    """Apply the AES key wrap algorithm to the plaintext.
+
+    The iv can specify an initial value, otherwise the value from RFC 3394
+    will be used.
+    """
 
     if len(plaintext) % 8 != 0 or len(plaintext) < 16:
         raise EncryptionError('Plaintext length wrong')
 
+    if iv is None:
+        iv = RFC3394_IV
+
     encrypt = AES.new(key).encrypt
     n = len(plaintext) / 8
-    A = RFC3394_IV
+    A = iv
     R = [plaintext[i * 8:i * 8 + 8]
          for i in range(n)]
     for j in range(6):
@@ -52,12 +59,19 @@ def wrap(plaintext, key):
     return A + ''.join(R)
 
 
-def unwrap(ciphertext, key):
-    """Apply the AES key unwrap algorithm to the ciphertext."""
+def unwrap(ciphertext, key, iv=None):
+    """Apply the AES key unwrap algorithm to the ciphertext.
+
+    The iv can specify an initial value, otherwise the value from RFC 3394
+    will be used.
+    """
 
     if len(ciphertext) % 8 != 0 or len(ciphertext) < 24:
         raise DecryptionError('Ciphertext length wrong')
 
+    if iv is None:
+        iv = RFC3394_IV
+
     decrypt = AES.new(key).decrypt
     n = len(ciphertext) / 8 - 1
     A = ciphertext[:8]
@@ -68,6 +82,6 @@ def unwrap(ciphertext, key):
             A = strxor(A, long_to_bytes(n * j + i + 1, 8))
             A, R[i] = _split(decrypt(A + R[i]))
 
-    if A == RFC3394_IV:
+    if A == iv:
         return ''.join(R)
     raise DecryptionError('IV does not match')
diff --git a/tests/test_aeskw.doctest b/tests/test_aeskw.doctest
index c0fc449..5a764d9 100644
--- a/tests/test_aeskw.doctest
+++ b/tests/test_aeskw.doctest
@@ -99,3 +99,20 @@ DecryptionError: IV does not match
 Traceback (most recent call last):
     ...
 DecryptionError: Ciphertext length wrong
+
+
+Lastly, an explicit IV can be set.
+
+>>> key = '000102030405060708090A0B0C0D0E0F'.decode('hex')
+>>> plaintext = '0011223344556677'.decode('hex')
+>>> iv = '1010101010101010'.decode('hex')
+>>> wrap(plaintext, key, iv)
+Traceback (most recent call last):
+    ...
+EncryptionError: Plaintext length wrong
+>>> plaintext = '00112233445566778899AABBCCDDEEFF'.decode('hex')
+>>> ciphertext = 
'4cd926c570e19c35ace71d59a1062dae850e6a709066e0bf'.decode('hex')
+>>> wrap(plaintext, key, iv) == ciphertext
+True
+>>> unwrap(ciphertext, key, iv) == plaintext
+True

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

commit 5720fe55f7549feb6f0319b720cded777e8f6d9f
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu May 29 18:39:35 2014 +0200

    Provide an RFC 3394 AES key wrapping algorithm
    
    This also introduces an EncryptionError exception.

diff --git a/pskc/aeskw.py b/pskc/aeskw.py
new file mode 100644
index 0000000..da75a18
--- /dev/null
+++ b/pskc/aeskw.py
@@ -0,0 +1,73 @@
+# aeskw.py - implementation of AES key wrapping
+# coding: utf-8
+#
+# Copyright (C) 2014 Arthur de Jong
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 USA
+
+"""Implement key wrapping as described in RFC 3394."""
+
+from Crypto.Cipher import AES
+from Crypto.Util.number import long_to_bytes
+from Crypto.Util.strxor import strxor
+
+from pskc.exceptions import EncryptionError, DecryptionError
+
+
+def _split(value):
+    return value[:8], value[8:]
+
+
+RFC3394_IV = 'a6a6a6a6a6a6a6a6'.decode('hex')
+
+
+def wrap(plaintext, key):
+    """Apply the AES key wrap algorithm to the plaintext."""
+
+    if len(plaintext) % 8 != 0 or len(plaintext) < 16:
+        raise EncryptionError('Plaintext length wrong')
+
+    encrypt = AES.new(key).encrypt
+    n = len(plaintext) / 8
+    A = RFC3394_IV
+    R = [plaintext[i * 8:i * 8 + 8]
+         for i in range(n)]
+    for j in range(6):
+        for i in range(n):
+            A, R[i] = _split(encrypt(A + R[i]))
+            A = strxor(A, long_to_bytes(n * j + i + 1, 8))
+    return A + ''.join(R)
+
+
+def unwrap(ciphertext, key):
+    """Apply the AES key unwrap algorithm to the ciphertext."""
+
+    if len(ciphertext) % 8 != 0 or len(ciphertext) < 24:
+        raise DecryptionError('Ciphertext length wrong')
+
+    decrypt = AES.new(key).decrypt
+    n = len(ciphertext) / 8 - 1
+    A = ciphertext[:8]
+    R = [ciphertext[(i + 1) * 8:(i + 2) * 8]
+         for i in range(n)]
+    for j in reversed(range(6)):
+        for i in reversed(range(n)):
+            A = strxor(A, long_to_bytes(n * j + i + 1, 8))
+            A, R[i] = _split(decrypt(A + R[i]))
+
+    if A == RFC3394_IV:
+        return ''.join(R)
+    raise DecryptionError('IV does not match')
diff --git a/pskc/exceptions.py b/pskc/exceptions.py
index 7fde416..19d801e 100644
--- a/pskc/exceptions.py
+++ b/pskc/exceptions.py
@@ -36,6 +36,11 @@ class ParseError(PSKCError):
     pass
 
 
+class EncryptionError(PSKCError):
+    """There was a problem encrypting the value."""
+    pass
+
+
 class DecryptionError(PSKCError):
     """There was a problem decrypting the value.
 
diff --git a/tests/test_aeskw.doctest b/tests/test_aeskw.doctest
new file mode 100644
index 0000000..c0fc449
--- /dev/null
+++ b/tests/test_aeskw.doctest
@@ -0,0 +1,101 @@
+test_keywrap.doctest - test keywrap functions
+
+Copyright (C) 2014 Arthur de Jong
+
+This library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Lesser General Public
+License as published by the Free Software Foundation; either
+version 2.1 of the License, or (at your option) any later version.
+
+This library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA
+
+
+>>> from pskc.aeskw import wrap, unwrap
+
+
+Wrap 128 bits of Key Data with a 128-bit KEK (test vector 4.1 from RFC 3394).
+
+>>> key = '000102030405060708090A0B0C0D0E0F'.decode('hex')
+>>> plaintext = '00112233445566778899AABBCCDDEEFF'.decode('hex')
+>>> ciphertext = 
'1FA68B0A8112B447AEF34BD8FB5A7B829D3E862371D2CFE5'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Wrap 128 bits of Key Data with a 192-bit KEK (test vector 4.2 from RFC 3394).
+
+>>> key = '000102030405060708090A0B0C0D0E0F1011121314151617'.decode('hex')
+>>> plaintext = '00112233445566778899AABBCCDDEEFF'.decode('hex')
+>>> ciphertext = 
'96778B25AE6CA435F92B5B97C050AED2468AB8A17AD84E5D'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Wrap 128 bits of Key Data with a 256-bit KEK (test vector 4.3 from RFC 3394).
+
+>>> key = 
'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F'.decode('hex')
+>>> plaintext = '00112233445566778899AABBCCDDEEFF'.decode('hex')
+>>> ciphertext = 
'64E8C3F9CE0F5BA263E9777905818A2A93C8191E7D6E8AE7'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Wrap 192 bits of Key Data with a 192-bit KEK (test vector 4.4 from RFC 3394).
+
+>>> key = '000102030405060708090A0B0C0D0E0F1011121314151617'.decode('hex')
+>>> plaintext = 
'00112233445566778899AABBCCDDEEFF0001020304050607'.decode('hex')
+>>> ciphertext = 
'031D33264E15D33268F24EC260743EDCE1C6C7DDEE725A936BA814915C6762D2'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Wrap 192 bits of Key Data with a 256-bit KEK (test vector 4.5 from RFC 3394).
+
+>>> key = 
'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F'.decode('hex')
+>>> plaintext = 
'00112233445566778899AABBCCDDEEFF0001020304050607'.decode('hex')
+>>> ciphertext = 
'A8F9BC1612C68B3FF6E6F4FBE30E71E4769C8B80A32CB8958CD5D17D6B254DA1'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Wrap 256 bits of Key Data with a 256-bit KEK (test vector 4.6 from RFC 3394).
+
+>>> key = 
'000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F'.decode('hex')
+>>> plaintext = 
'00112233445566778899AABBCCDDEEFF000102030405060708090A0B0C0D0E0F'.decode('hex')
+>>> ciphertext = 
'28C9F404C4B810F4CBCCB35CFB87F8263F5786E2D80ED326CBC7F0E71A99F43BFB988B9B7A02DD21'.decode('hex')
+>>> wrap(plaintext, key) == ciphertext
+True
+>>> unwrap(ciphertext, key) == plaintext
+True
+
+
+Mangling the ciphertext and unwrapping results in an exception:
+
+>>> ciphertext = 'XX' + ciphertext[2:]
+>>> unwrap(ciphertext, key)
+Traceback (most recent call last):
+    ...
+DecryptionError: IV does not match
+>>> ciphertext = ciphertext[2:]
+>>> unwrap(ciphertext, key)
+Traceback (most recent call last):
+    ...
+DecryptionError: Ciphertext length wrong

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

commit 7164d892734dd2cf7a4358ddb31f532e1f781721
Author: Arthur de Jong <arthur@arthurdejong.org>
Date:   Thu May 29 20:59:29 2014 +0200

    Always put a space between RFC and number

diff --git a/README b/README
index cf660d3..a833747 100644
--- a/README
+++ b/README
@@ -2,14 +2,14 @@ Python PSKC module
 ==================
 
 A Python module to handle Portable Symmetric Key Container (PSKC) files as
-defined in `RFC6030 <https://tools.ietf.org/html/rfc6030>`_. PSKC files are
+defined in `RFC 6030 <https://tools.ietf.org/html/rfc6030>`_. PSKC files are
 used to transport and provision symmetric keys and key related meta data to
 different types of crypto modules. The format is commonly used for one-time
 password tokens or other authentication devices.
 
-The goal of this module is mainly to provide parsing of PSKC files in
-order to extract secret keys for use in an OTP authentication system. At a
-later time support for writing files may be added.
+The goal of this module is mainly to provide parsing of PSKC files in order
+to extract secret keys for use in an OTP authentication system. At a later
+time support for writing files may be added.
 
 http://arthurdejong.org/python-pskc/
 
@@ -37,9 +37,9 @@ Security considerations
 -----------------------
 
 This code handles private key material and is written in Python. No
-precautions have been taken to lock pages in memory to prevent swapping.
-Also no attempt is currently made to security dispose of memory that may
-have held private key material.
+precautions have been taken to lock pages in memory to prevent swapping. Also
+no attempt is currently made to security dispose of memory that may have held
+private key material.
 
 
 Copyright
diff --git a/docs/usage.rst b/docs/usage.rst
index 026df66..0aab59e 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -24,8 +24,9 @@ class::
 
    .. attribute:: version
 
-      The PSKC format version used. Only version ``1.0`` is currently specified
-      in `RFC6030 <https://tools.ietf.org/html/rfc6030#section-1.2>`__.
+      The PSKC format version used. Only version ``1.0`` is currently
+      specified in
+      `RFC 6030 <https://tools.ietf.org/html/rfc6030#section-1.2>`__.
 
    .. attribute:: id
 
@@ -122,7 +123,7 @@ file.
       A reference to a pre-shared key profile agreed upon between the sending
       and receiving parties. The profile information itself is not
       transmitted within the container.
-      See `RFC6030 <https://tools.ietf.org/html/rfc6030#section-4.4>`__.
+      See `RFC 6030 <https://tools.ietf.org/html/rfc6030#section-4.4>`__.
 
    .. attribute:: key_reference
 
@@ -142,7 +143,8 @@ file.
    .. attribute:: manufacturer
 
       The name of the manufacturer of the device to which the key is
-      provisioned. `RFC6030 
<https://tools.ietf.org/html/rfc6030#section-4.3.1>`__
+      provisioned.
+      `RFC 6030 <https://tools.ietf.org/html/rfc6030#section-4.3.1>`__
       prescribes that the value is of the form ``oath.prefix`` for `OATH
       Manufacturer Prefixes 
<http://www.openauthentication.org/oath-id/prefixes/>`_
       or ``iana.organisation`` for `IANA Private Enterprise Numbers
diff --git a/pskc/__init__.py b/pskc/__init__.py
index 60a5e3b..71e2f89 100644
--- a/pskc/__init__.py
+++ b/pskc/__init__.py
@@ -20,10 +20,10 @@
 
 """Python module for handling PSKC files
 
-This Python library handles Portable Symmetric Key Container (PSKC) files
-as defined in RFC6030. PSKC files are used to transport and provision
-symmetric keys (seed files) to different types of crypto modules, commonly
-one-time password tokens or other authentication devices.
+This Python library handles Portable Symmetric Key Container (PSKC) files as
+defined in RFC 6030. PSKC files are used to transport and provision symmetric
+keys (seed files) to different types of crypto modules, commonly one-time
+password tokens or other authentication devices.
 
 The main goal of this module is to be able to extract keys from PSKC files
 for use in an OTP authentication system.
@@ -37,9 +37,9 @@ The following prints all keys, decrypting using a password:
 ...     print key.serial, key.secret
 987654321 12345678901234567890
 
-The module should be able to handle most common PSKC files. Checking
-embedded signatures, asymmetric keys and writing files are on the wishlist
-(patches welcome).
+The module should be able to handle most common PSKC files. Checking embedded
+signatures, asymmetric keys and writing files are on the wishlist (patches
+welcome).
 """
 
 import sys
diff --git a/tests/rfc6030-figure10.pskcxml b/tests/rfc6030-figure10.pskcxml
index dcc86fe..6ae75d9 100644
--- a/tests/rfc6030-figure10.pskcxml
+++ b/tests/rfc6030-figure10.pskcxml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Bulk Provisioning Example from RFC6030.
+  Bulk Provisioning Example from RFC 6030.
 -->
 
 <KeyContainer Version="1.0"
diff --git a/tests/rfc6030-figure2.pskcxml b/tests/rfc6030-figure2.pskcxml
index 40cefa5..a5d7258 100644
--- a/tests/rfc6030-figure2.pskcxml
+++ b/tests/rfc6030-figure2.pskcxml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Basic PSKC Key Container example from RFC6030 (Figure 2).
+  Basic PSKC Key Container example from RFC 6030 (Figure 2).
 -->
 
 <KeyContainer Version="1.0"
diff --git a/tests/rfc6030-figure3.pskcxml b/tests/rfc6030-figure3.pskcxml
index 417131d..a27e91e 100644
--- a/tests/rfc6030-figure3.pskcxml
+++ b/tests/rfc6030-figure3.pskcxml
@@ -1,7 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  PSKC Key Container Example with Supplementary Data from RFC6030 (Figure 3).
+  PSKC Key Container Example with Supplementary Data from RFC 6030
+  (Figure 3).
 -->
 
 <KeyContainer Version="1.0"
diff --git a/tests/rfc6030-figure4.pskcxml b/tests/rfc6030-figure4.pskcxml
index 981ccb2..d3a3aff 100644
--- a/tests/rfc6030-figure4.pskcxml
+++ b/tests/rfc6030-figure4.pskcxml
@@ -2,7 +2,7 @@
 
 <!--
   Example of a PSKC Document Transmitting an HOTP Key via Key Derivation
-  Values from RFC6030 (Figure 4).
+  Values from RFC 6030 (Figure 4).
 -->
 
 <KeyContainer Version="1.0"
diff --git a/tests/rfc6030-figure5.pskcxml b/tests/rfc6030-figure5.pskcxml
index 88f7c6e..c614624 100644
--- a/tests/rfc6030-figure5.pskcxml
+++ b/tests/rfc6030-figure5.pskcxml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Non-Encrypted HOTP Secret Key with PIN example from RFC6030 (Figure 5).
+  Non-Encrypted HOTP Secret Key with PIN example from RFC 6030 (Figure 5).
 -->
 
 <KeyContainer Version="1.0"
diff --git a/tests/rfc6030-figure6.pskcxml b/tests/rfc6030-figure6.pskcxml
index 950c620..26474df 100644
--- a/tests/rfc6030-figure6.pskcxml
+++ b/tests/rfc6030-figure6.pskcxml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Figure 6 example from RFC6030 that shows key material encrypted using
+  Figure 6 example from RFC 6030 that shows key material encrypted using
   AES-128-CBC with pre-shared key (12345678901234567890123456789012 hex).
   The MAC key used (1122334455667788990011223344556677889900 hex) is
   encrypted with the same key.
diff --git a/tests/rfc6030-figure7.pskcxml b/tests/rfc6030-figure7.pskcxml
index b37c575..b031f6a 100644
--- a/tests/rfc6030-figure7.pskcxml
+++ b/tests/rfc6030-figure7.pskcxml
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!--
-  Figure 7 example from RFC6030 that has a PSKC document using encryption
-  based on passphrase-based keys. The pre-shared password is 'qwerty', The
-  derived encryption key is 651e63cd57008476af1ff6422cd02e41 hex and the
-  OTP secret is 12345678901234567890 hex.
+  Figure 7 example from RFC 6030 that has a PSKC document using encryption
+  based on passphrase-based keys. The pre-shared password is 'qwerty', the
+  derived encryption key is 651e63cd57008476af1ff6422cd02e41 hex and the OTP
+  secret is 12345678901234567890 hex.
 -->
 
 <pskc:KeyContainer
diff --git a/tests/test_rfc6030.doctest b/tests/test_rfc6030.doctest
index 776321d..7a94940 100644
--- a/tests/test_rfc6030.doctest
+++ b/tests/test_rfc6030.doctest
@@ -1,4 +1,4 @@
-test_rfc6030.doctest - test for examples from RFC6030
+test_rfc6030.doctest - test for examples from RFC 6030
 
 Copyright (C) 2014 Arthur de Jong
 
@@ -21,8 +21,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 >>> from pskc import PSKC
 
 
-This tests Figure 2 from RFC6030. It is a basic key container example with
-a simple plain text secret key.
+This tests Figure 2 from RFC 6030. It is a basic key container example with a
+simple plain text secret key.
 
 >>> pskc = PSKC('tests/rfc6030-figure2.pskcxml')
 >>> [key.secret for key in pskc.keys]
@@ -38,9 +38,8 @@ a simple plain text secret key.
 '1234'
 
 
-This tests Figure 3 from RFC6030. Relative to Figure 2 this includes
-device, cryptographic module and user identification as well as some more
-parameters.
+This tests Figure 3 from RFC 6030. Relative to Figure 2 this includes device,
+cryptographic module and user identification as well as some more parameters.
 
 >>> pskc = PSKC('tests/rfc6030-figure3.pskcxml')
 >>> pskc.id
@@ -72,9 +71,9 @@ parameters.
 'UID=jsmith,DC=example-bank,DC=net'
 
 
-This tests Figure 4 from RFC6030. In this case the key value itself is not
-contained but can be derived using the serial and out-of-band agreements
-on the meanings of key_profile and key_reference.
+This tests Figure 4 from RFC 6030. In this case the key value itself is not
+contained but can be derived using the serial and out-of-band agreements on
+the meanings of key_profile and key_reference.
 
 >>> pskc = PSKC('tests/rfc6030-figure4.pskcxml')
 >>> key = pskc.keys[0]
@@ -88,7 +87,8 @@ on the meanings of key_profile and key_reference.
 0
 
 
-This tests the key policy properties as illustrated in Figure 5 from RFC6030.
+This tests the key policy properties as illustrated in Figure 5 from RFC
+6030.
 
 >>> pskc = PSKC('tests/rfc6030-figure5.pskcxml')
 >>> len(pskc.keys)
@@ -140,9 +140,9 @@ False
 '1234'
 
 
-This tests key encryption based on pre-shared keys as illustrated in
-Figure 6 from RFC6030. The first attempt at extracting the key will fail
-due to the encryption.
+This tests key encryption based on pre-shared keys as illustrated in Figure 6
+from RFC 6030. The first attempt at extracting the key will fail due to the
+encryption.
 
 >>> pskc = PSKC('tests/rfc6030-figure6.pskcxml')
 >>> key = pskc.keys[0]
@@ -163,8 +163,8 @@ DecryptionError: No key available
 True
 
 
-This tests a derived master key using PBKDF2 as seen in Figure 7 from
-RFC6030.
+This tests a derived master key using PBKDF2 as seen in Figure 7 from RFC
+6030.
 
 >>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
 >>> pskc.encryption.key_name
@@ -181,7 +181,7 @@ RFC6030.
 True
 
 
-This tests bulk provisioning as shown in Figure 10 From RFC6030.
+This tests bulk provisioning as shown in Figure 10 From RFC 6030.
 
 >>> pskc = PSKC('tests/rfc6030-figure10.pskcxml')
 >>> all(key.manufacturer == 'TokenVendorAcme' for key in pskc.keys)

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

Summary of changes:
 README                                             |   14 +-
 docs/usage.rst                                     |   10 +-
 pskc/__init__.py                                   |   14 +-
 pskc/aeskw.py                                      |  123 ++++++++++++
 pskc/encryption.py                                 |    9 +
 pskc/exceptions.py                                 |    5 +
 tests/{tripledes-cbc.pskcxml => kw-aes128.pskcxml} |   12 +-
 tests/{aes192-cbc.pskcxml => kw-aes192.pskcxml}    |    9 +-
 tests/{aes192-cbc.pskcxml => kw-aes256.pskcxml}    |    9 +-
 tests/rfc6030-figure10.pskcxml                     |    2 +-
 tests/rfc6030-figure2.pskcxml                      |    2 +-
 tests/rfc6030-figure3.pskcxml                      |    3 +-
 tests/rfc6030-figure4.pskcxml                      |    2 +-
 tests/rfc6030-figure5.pskcxml                      |    2 +-
 tests/rfc6030-figure6.pskcxml                      |    2 +-
 tests/rfc6030-figure7.pskcxml                      |    8 +-
 tests/test_aeskw.doctest                           |  196 ++++++++++++++++++++
 tests/test_encryption.doctest                      |   28 +++
 tests/test_rfc6030.doctest                         |   32 ++--
 19 files changed, 424 insertions(+), 58 deletions(-)
 create mode 100644 pskc/aeskw.py
 copy tests/{tripledes-cbc.pskcxml => kw-aes128.pskcxml} (58%)
 copy tests/{aes192-cbc.pskcxml => kw-aes192.pskcxml} (68%)
 copy tests/{aes192-cbc.pskcxml => kw-aes256.pskcxml} (65%)
 create mode 100644 tests/test_aeskw.doctest


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