Skip to content

Commit 671b6e2

Browse files
committed
Support Python 3
This enables support for Python 3 together with Python 2 support with a single codebase. On Python 3 key data is passed around as bytestrings which makes the doctests a little harder to maintain across Python versions.
1 parent 68b20e2 commit 671b6e2

16 files changed

+166
-125
lines changed

pskc/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# __init__.py - main module
22
# coding: utf-8
33
#
4-
# Copyright (C) 2014 Arthur de Jong
4+
# Copyright (C) 2014-2015 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -34,7 +34,7 @@
3434
>>> pskc = PSKC('tests/rfc6030-figure7.pskcxml')
3535
>>> pskc.encryption.derive_key('qwerty')
3636
>>> for key in pskc.keys:
37-
... print key.serial, key.secret
37+
... print('%s %s' % (key.serial, str(key.secret.decode())))
3838
987654321 12345678901234567890
3939
4040
The module should be able to handle most common PSKC files. Checking embedded

pskc/crypto/aeskw.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# aeskw.py - implementation of AES key wrapping
22
# coding: utf-8
33
#
4-
# Copyright (C) 2014 Arthur de Jong
4+
# Copyright (C) 2014-2015 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -20,6 +20,8 @@
2020

2121
"""Implement key wrapping as described in RFC 3394 and RFC 5649."""
2222

23+
import binascii
24+
2325
from Crypto.Cipher import AES
2426
from Crypto.Util.number import bytes_to_long, long_to_bytes
2527
from Crypto.Util.strxor import strxor
@@ -31,8 +33,8 @@ def _split(value):
3133
return value[:8], value[8:]
3234

3335

34-
RFC3394_IV = 'a6a6a6a6a6a6a6a6'.decode('hex')
35-
RFC5649_IV = 'a65959a6'.decode('hex')
36+
RFC3394_IV = binascii.a2b_hex('a6a6a6a6a6a6a6a6')
37+
RFC5649_IV = binascii.a2b_hex('a65959a6')
3638

3739

3840
def wrap(plaintext, key, iv=None, pad=None):
@@ -54,7 +56,7 @@ def wrap(plaintext, key, iv=None, pad=None):
5456
raise EncryptionError('Plaintext length wrong')
5557
if mli % 8 != 0 and pad is not False:
5658
r = (mli + 7) // 8
57-
plaintext += ((r * 8) - mli) * '\0'
59+
plaintext += ((r * 8) - mli) * b'\0'
5860

5961
if iv is None:
6062
if len(plaintext) != mli or pad is True:
@@ -63,7 +65,7 @@ def wrap(plaintext, key, iv=None, pad=None):
6365
iv = RFC3394_IV
6466

6567
encrypt = AES.new(key).encrypt
66-
n = len(plaintext) / 8
68+
n = len(plaintext) // 8
6769

6870
if n == 1:
6971
# RFC 5649 shortcut
@@ -76,7 +78,7 @@ def wrap(plaintext, key, iv=None, pad=None):
7678
for i in range(n):
7779
A, R[i] = _split(encrypt(A + R[i]))
7880
A = strxor(A, long_to_bytes(n * j + i + 1, 8))
79-
return A + ''.join(R)
81+
return A + b''.join(R)
8082

8183

8284
def unwrap(ciphertext, key, iv=None, pad=None):
@@ -95,7 +97,7 @@ def unwrap(ciphertext, key, iv=None, pad=None):
9597
raise DecryptionError('Ciphertext length wrong')
9698

9799
decrypt = AES.new(key).decrypt
98-
n = len(ciphertext) / 8 - 1
100+
n = len(ciphertext) // 8 - 1
99101

100102
if n == 1:
101103
A, plaintext = _split(decrypt(ciphertext))
@@ -107,16 +109,16 @@ def unwrap(ciphertext, key, iv=None, pad=None):
107109
for i in reversed(range(n)):
108110
A = strxor(A, long_to_bytes(n * j + i + 1, 8))
109111
A, R[i] = _split(decrypt(A + R[i]))
110-
plaintext = ''.join(R)
112+
plaintext = b''.join(R)
111113

112114
if iv is None:
113115
if A == RFC3394_IV and pad is not True:
114116
return plaintext
115117
elif A[:4] == RFC5649_IV and pad is not False:
116118
mli = bytes_to_long(A[4:])
117-
# check padding length is valid and only contains zeros
119+
# check padding length is valid and plaintext only contains zeros
118120
if 8 * (n - 1) < mli <= 8 * n and \
119-
all(x == '\0' for x in plaintext[mli:]):
121+
plaintext.endswith((len(plaintext) - mli) * b'\0'):
120122
return plaintext[:mli]
121123
elif A == iv:
122124
return plaintext

pskc/crypto/tripledeskw.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# tripledeskw.py - implementation of Triple DES key wrapping
22
# coding: utf-8
33
#
4-
# Copyright (C) 2014 Arthur de Jong
4+
# Copyright (C) 2014-2015 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -20,6 +20,8 @@
2020

2121
"""Implement Triple DES key wrapping as described in RFC 3217."""
2222

23+
import binascii
24+
2325
from Crypto import Random
2426
from Crypto.Cipher import DES3
2527
from Crypto.Hash import SHA
@@ -32,7 +34,7 @@ def _cms_hash(value):
3234
return SHA.new(value).digest()[:8]
3335

3436

35-
RFC3217_IV = '4adda22c79e82105'.decode('hex')
37+
RFC3217_IV = binascii.a2b_hex('4adda22c79e82105')
3638

3739

3840
def wrap(plaintext, key, iv=None):
@@ -48,7 +50,7 @@ def wrap(plaintext, key, iv=None):
4850
cipher = DES3.new(key, DES3.MODE_CBC, iv)
4951
tmp = iv + cipher.encrypt(plaintext + _cms_hash(plaintext))
5052
cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
51-
return cipher.encrypt(''.join(reversed(tmp)))
53+
return cipher.encrypt(tmp[::-1])
5254

5355

5456
def unwrap(ciphertext, key):
@@ -59,7 +61,7 @@ def unwrap(ciphertext, key):
5961
if len(ciphertext) % DES3.block_size != 0:
6062
raise DecryptionError('Ciphertext length wrong')
6163
cipher = DES3.new(key, DES3.MODE_CBC, RFC3217_IV)
62-
tmp = ''.join(reversed(cipher.decrypt(ciphertext)))
64+
tmp = cipher.decrypt(ciphertext)[::-1]
6365
cipher = DES3.new(key, DES3.MODE_CBC, tmp[:8])
6466
tmp = cipher.decrypt(tmp[8:])
6567
if tmp[-8:] == _cms_hash(tmp[:-8]):

pskc/encryption.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
def unpad(value):
3232
"""Remove padding from the plaintext."""
33-
return value[0:-ord(value[-1])]
33+
return value[0:-ord(value[-1:])]
3434

3535

3636
class EncryptedValue(object):

pskc/key.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# key.py - module for handling keys from pskc files
22
# coding: utf-8
33
#
4-
# Copyright (C) 2014 Arthur de Jong
4+
# Copyright (C) 2014-2015 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -102,7 +102,10 @@ def from_text(self, value):
102102

103103
def to_text(self, value):
104104
"""Convert the value to an unencrypted string representation."""
105-
return base64.b64encode(value)
105+
# force conversion to bytestring on Python 3
106+
if not isinstance(value, type(b'')):
107+
value = value.encode()
108+
return base64.b64encode(value).decode()
106109

107110
def from_bin(self, value):
108111
"""Convert the unencrypted binary to native representation."""

pskc/policy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# policy.py - module for handling PSKC policy information
22
# coding: utf-8
33
#
4-
# Copyright (C) 2014 Arthur de Jong
4+
# Copyright (C) 2014-2015 Arthur de Jong
55
#
66
# This library is free software; you can redistribute it and/or
77
# modify it under the terms of the GNU Lesser General Public
@@ -181,4 +181,4 @@ def pin(self):
181181
"""PIN value referenced by PINKeyId if any."""
182182
key = self.pin_key
183183
if key:
184-
return key.secret
184+
return str(key.secret.decode())

pskc/xml.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def find(tree, *matches):
6666
"""Find a child element that matches any of the patterns (or None)."""
6767
for match in matches:
6868
try:
69-
return iter(findall(tree, match)).next()
69+
return next(iter(findall(tree, match)))
7070
except StopIteration:
7171
pass
7272

@@ -138,7 +138,7 @@ def mk_elem(parent, tag=None, text=None, empty=False, **kwargs):
138138
empty = True
139139
# don't create empty elements
140140
if not empty and text is None and \
141-
all(x is None for x in kwargs.itervalues()):
141+
all(x is None for x in kwargs.values()):
142142
return
143143
# replace namespace identifier with URL
144144
if ':' in tag:
@@ -152,7 +152,7 @@ def mk_elem(parent, tag=None, text=None, empty=False, **kwargs):
152152
if text is not None:
153153
element.text = _format(text)
154154
# set kwargs as attributes
155-
for k, v in kwargs.iteritems():
155+
for k, v in kwargs.items():
156156
if v is not None:
157157
element.set(k, _format(v))
158158
return element

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# setup.py - python-pskc installation script
44
#
5-
# Copyright (C) 2014 Arthur de Jong
5+
# Copyright (C) 2014-2015 Arthur de Jong
66
#
77
# This library is free software; you can redistribute it and/or
88
# modify it under the terms of the GNU Lesser General Public
@@ -51,7 +51,9 @@
5151
'Programming Language :: Python :: 2',
5252
'Programming Language :: Python :: 2.6',
5353
'Programming Language :: Python :: 2.7',
54-
'Topic :: Security :: Cryptography',
54+
'Programming Language :: Python :: 3',
55+
'Programming Language :: Python :: 3.4',
56+
'Programming Language :: Python :: 3.5',
5557
'Topic :: Software Development :: Libraries :: Python Modules',
5658
'Topic :: Text Processing :: Markup :: XML',
5759
],

0 commit comments

Comments
 (0)