~ubuntu-branches/ubuntu/wily/python-pskc/wily

« back to all changes in this revision

Viewing changes to pskc/encryption.py

  • Committer: Package Import Robot
  • Author(s): Arthur de Jong
  • Date: 2014-06-20 14:50:59 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20140620145059-cf3iwj9rnwagig43
Tags: 0.2-1
* New upstream release:
  - raise exceptions on parsing, decryption and other problems
  - support more encryption algorithms (AES128-CBC, AES192-CBC, AES256-CBC,
    TripleDES-CBC, KW-AES128, KW-AES192, KW-AES256 and KW-TripleDES) and be
    more lenient in accepting algorithm URIs
  - support all HMAC algorithms that Python's hashlib module has hash
    functions for (HMAC-MD5, HMAC-SHA1, HMAC-SHA224, HMAC-SHA256,
    HMAC-SHA384 and HMAC-SHA512)
  - support PRF attribute of PBKDF2 algorithm
* Build and install Sphinx documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
28
28
"""
29
29
 
30
30
 
31
 
import base64
32
 
 
33
 
from Crypto.Cipher import AES
34
 
from Crypto.Protocol.KDF import PBKDF2
35
 
 
36
 
 
37
 
AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'
 
31
def unpad(value):
 
32
    """Remove padding from the plaintext."""
 
33
    return value[0:-ord(value[-1])]
38
34
 
39
35
 
40
36
class EncryptedValue(object):
55
51
 
56
52
    def parse(self, encrypted_value):
57
53
        """Read encrypted data from the <EncryptedValue> XML tree."""
58
 
        from pskc.parse import g_e_v, namespaces
 
54
        from pskc.parse import find, findbin
59
55
        if encrypted_value is None:
60
56
            return
61
 
        encryption_method = encrypted_value.find(
62
 
            'xenc:EncryptionMethod', namespaces=namespaces)
63
 
        self.algorithm = encryption_method.attrib.get('Algorithm')
64
 
        value = g_e_v(encrypted_value, 'xenc:CipherData/xenc:CipherValue')
65
 
        if value is not None:
66
 
            self.cipher_value = base64.b64decode(value)
 
57
        encryption_method = find(encrypted_value, 'xenc:EncryptionMethod')
 
58
        if encryption_method is not None:
 
59
            self.algorithm = encryption_method.attrib.get('Algorithm')
 
60
        self.cipher_value = findbin(
 
61
            encrypted_value, 'xenc:CipherData/xenc:CipherValue')
67
62
 
68
63
    def decrypt(self):
69
64
        """Decrypt the linked value and return the plaintext value."""
 
65
        from pskc.exceptions import DecryptionError
 
66
        if self.cipher_value is None:
 
67
            return
70
68
        key = self.encryption.key
71
 
        ciphertext = self.cipher_value
72
 
        if key is None or ciphertext is None:
73
 
            return
74
 
        if self.algorithm == AES128_CBC:
75
 
            iv = ciphertext[:AES.block_size]
 
69
        if key is None:
 
70
            raise DecryptionError('No key available')
 
71
        if self.algorithm is None:
 
72
            raise DecryptionError('No algorithm specified')
 
73
        if self.algorithm.endswith('#aes128-cbc') or \
 
74
           self.algorithm.endswith('#aes192-cbc') or \
 
75
           self.algorithm.endswith('#aes256-cbc'):
 
76
            from Crypto.Cipher import AES
 
77
            if len(key) * 8 != int(self.algorithm[-7:-4]) or \
 
78
               len(key) not in AES.key_size:
 
79
                raise DecryptionError('Invalid key length')
 
80
            iv = self.cipher_value[:AES.block_size]
 
81
            ciphertext = self.cipher_value[AES.block_size:]
76
82
            cipher = AES.new(key, AES.MODE_CBC, iv)
77
 
            plaintext = cipher.decrypt(ciphertext[AES.block_size:])
78
 
            return plaintext[0:-ord(plaintext[-1])]
79
 
 
80
 
 
81
 
PBKDF2_URIS = [
82
 
    'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5#pbkdf2',
83
 
    'http://www.rsasecurity.com/rsalabs/pkcs/schemas/pkcs-5v2-0#pbkdf2',
84
 
    'http://www.w3.org/2009/xmlenc11#pbkdf2',
85
 
]
 
83
            return unpad(cipher.decrypt(ciphertext))
 
84
        elif self.algorithm.endswith('#tripledes-cbc'):
 
85
            from Crypto.Cipher import DES3
 
86
            if len(key) not in DES3.key_size:
 
87
                raise DecryptionError('Invalid key length')
 
88
            iv = self.cipher_value[:DES3.block_size]
 
89
            ciphertext = self.cipher_value[DES3.block_size:]
 
90
            cipher = DES3.new(key, DES3.MODE_CBC, iv)
 
91
            return unpad(cipher.decrypt(ciphertext))
 
92
        elif self.algorithm.endswith('#kw-aes128') or \
 
93
                self.algorithm.endswith('#kw-aes192') or \
 
94
                self.algorithm.endswith('#kw-aes256'):
 
95
            from pskc.aeskw import unwrap
 
96
            from Crypto.Cipher import AES
 
97
            if len(key) * 8 != int(self.algorithm[-3:]) or \
 
98
               len(key) not in AES.key_size:
 
99
                raise DecryptionError('Invalid key length')
 
100
            return unwrap(self.cipher_value, key)
 
101
        elif self.algorithm.endswith('#kw-tripledes'):
 
102
            from pskc.tripledeskw import unwrap
 
103
            from Crypto.Cipher import DES3
 
104
            if len(key) not in DES3.key_size:
 
105
                raise DecryptionError('Invalid key length')
 
106
            return unwrap(self.cipher_value, key)
 
107
        else:
 
108
            raise DecryptionError('Unsupported algorithm: %r' % self.algorithm)
86
109
 
87
110
 
88
111
class KeyDerivation(object):
94
117
      pbkdf2_salt: salt value
95
118
      pbkdf2_iterations: number of iterations to use
96
119
      pbkdf2_key_length: required key lengt
97
 
      pbkdf2_prf: name of pseudorandom function used (HMAC-SHA1 is assumed)
 
120
      pbkdf2_prf: name of pseudorandom function used
98
121
    """
99
122
 
100
123
    def __init__(self, key_deriviation=None):
108
131
 
109
132
    def parse(self, key_deriviation):
110
133
        """Read derivation parameters from a <KeyDerivationMethod> element."""
111
 
        from pskc.parse import g_e_v, g_e_i, namespaces
 
134
        from pskc.parse import find, findint, findbin
112
135
        if key_deriviation is None:
113
136
            return
114
 
        self.algorithm = key_deriviation.attrib.get('Algorithm')
 
137
        self.algorithm = key_deriviation.get('Algorithm')
115
138
        # PBKDF2 properties
116
 
        pbkdf2 = key_deriviation.find(
117
 
            'xenc11:PBKDF2-params', namespaces=namespaces)
 
139
        pbkdf2 = find(key_deriviation, 'xenc11:PBKDF2-params')
118
140
        if pbkdf2 is None:
119
 
            pbkdf2 = key_deriviation.find(
120
 
                'pkcs5:PBKDF2-params', namespaces=namespaces)
 
141
            pbkdf2 = find(key_deriviation, 'pkcs5:PBKDF2-params')
121
142
        if pbkdf2 is not None:
122
143
            # get used salt
123
 
            value = g_e_v(pbkdf2, 'Salt/Specified')
124
 
            if value is not None:
125
 
                self.pbkdf2_salt = base64.b64decode(value)
 
144
            self.pbkdf2_salt = findbin(pbkdf2, 'Salt/Specified')
126
145
            # required number of iterations
127
 
            self.pbkdf2_iterations = g_e_i(pbkdf2, 'IterationCount')
 
146
            self.pbkdf2_iterations = findint(pbkdf2, 'IterationCount')
128
147
            # key length
129
 
            self.pbkdf2_key_length = g_e_i(pbkdf2, 'KeyLength')
 
148
            self.pbkdf2_key_length = findint(pbkdf2, 'KeyLength')
130
149
            # pseudorandom function used
131
 
            prf = pbkdf2.find('PRF', namespaces=namespaces)
 
150
            prf = find(pbkdf2, 'PRF')
132
151
            if prf is not None:
133
 
                self.pbkdf2_prf = prf.attrib.get('Algorithm')
 
152
                self.pbkdf2_prf = prf.get('Algorithm')
134
153
 
135
 
    def generate(self, password):
 
154
    def derive(self, password):
136
155
        """Derive a key from the password."""
137
 
        if self.algorithm in PBKDF2_URIS:
138
 
            # TODO: support pseudorandom function (prf)
 
156
        from pskc.exceptions import KeyDerivationError
 
157
        if self.algorithm is None:
 
158
            raise KeyDerivationError('No algorithm specified')
 
159
        if self.algorithm.endswith('#pbkdf2'):
 
160
            from Crypto.Protocol.KDF import PBKDF2
 
161
            from pskc.mac import get_hmac
 
162
            prf = None
 
163
            if self.pbkdf2_prf:
 
164
                prf = get_hmac(self.pbkdf2_prf)
 
165
                if prf is None:
 
166
                    raise KeyDerivationError(
 
167
                        'Pseudorandom function unsupported: %r' %
 
168
                        self.pbkdf2_prf)
139
169
            return PBKDF2(
140
170
                password, self.pbkdf2_salt, dkLen=self.pbkdf2_key_length,
141
 
                count=self.pbkdf2_iterations, prf=None)
 
171
                count=self.pbkdf2_iterations, prf=prf)
 
172
        else:
 
173
            raise KeyDerivationError(
 
174
                'Unsupported algorithm: %r' % self.algorithm)
142
175
 
143
176
 
144
177
class Encryption(object):
166
199
 
167
200
    def parse(self, key_info):
168
201
        """Read encryption information from the <EncryptionKey> XML tree."""
169
 
        from pskc.parse import g_e_v, namespaces
 
202
        from pskc.parse import find, findall, findtext
170
203
        if key_info is None:
171
204
            return
172
 
        self.id = key_info.attrib.get('Id')
173
 
        for name in key_info.findall('ds:KeyName', namespaces=namespaces):
174
 
            self.key_names.append(g_e_v(name, '.'))
175
 
        for name in key_info.findall(
176
 
                'xenc11:DerivedKey/xenc11:MasterKeyName',
177
 
                namespaces=namespaces):
178
 
            self.key_names.append(g_e_v(name, '.'))
179
 
        self.derivation.parse(key_info.find(
180
 
            'xenc11:DerivedKey/xenc11:KeyDerivationMethod',
181
 
            namespaces=namespaces))
 
205
        self.id = key_info.get('Id')
 
206
        for name in findall(key_info, 'ds:KeyName'):
 
207
            self.key_names.append(findtext(name, '.'))
 
208
        for name in findall(
 
209
                key_info, 'xenc11:DerivedKey/xenc11:MasterKeyName'):
 
210
            self.key_names.append(findtext(name, '.'))
 
211
        self.derivation.parse(find(
 
212
            key_info, 'xenc11:DerivedKey/xenc11:KeyDerivationMethod'))
182
213
 
183
214
    @property
184
215
    def key_name(self):
188
219
 
189
220
    def derive_key(self, password):
190
221
        """Derive a key from the password."""
191
 
        self.key = self.derivation.generate(password)
 
222
        self.key = self.derivation.derive(password)