Skip to content

Commit d62e1d3

Browse files
authored
Merge pull request #377 from t184256/3des-fixes
3des fixes
2 parents c9748b8 + 9fd8465 commit d62e1d3

File tree

3 files changed

+109
-26
lines changed

3 files changed

+109
-26
lines changed

tlslite/utils/openssl_tripledes.py

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,33 +15,37 @@ class OpenSSL_TripleDES(TripleDES):
1515

1616
def __init__(self, key, mode, IV):
1717
TripleDES.__init__(self, key, mode, IV, "openssl")
18-
self.key = key
19-
self.IV = IV
18+
self._IV, self._key = IV, key
19+
self._context = None
20+
self._encrypt = None
2021

21-
def _createContext(self, encrypt):
22-
context = m2.cipher_ctx_new()
22+
def _init_context(self, encrypt=True):
2323
cipherType = m2.des_ede3_cbc()
24-
m2.cipher_init(context, cipherType, self.key, self.IV, encrypt)
25-
return context
24+
self._context = m2.cipher_ctx_new()
25+
m2.cipher_init(self._context, cipherType, self._key, self._IV,
26+
int(encrypt))
27+
m2.cipher_set_padding(self._context, 0)
28+
self._encrypt = encrypt
2629

2730
def encrypt(self, plaintext):
31+
if self._context is None:
32+
self._init_context(encrypt=True)
33+
else:
34+
assert self._encrypt, '.encrypt() not allowed after .decrypt()'
2835
TripleDES.encrypt(self, plaintext)
29-
context = self._createContext(1)
30-
ciphertext = m2.cipher_update(context, plaintext)
31-
m2.cipher_ctx_free(context)
32-
self.IV = ciphertext[-self.block_size:]
36+
ciphertext = m2.cipher_update(self._context, plaintext)
3337
return bytearray(ciphertext)
3438

3539
def decrypt(self, ciphertext):
40+
if self._context is None:
41+
self._init_context(encrypt=False)
42+
else:
43+
assert not self._encrypt, \
44+
'.decrypt() not allowed after .encrypt()'
3645
TripleDES.decrypt(self, ciphertext)
37-
context = self._createContext(0)
38-
#I think M2Crypto has a bug - it fails to decrypt and return the last block passed in.
39-
#To work around this, we append sixteen zeros to the string, below:
40-
plaintext = m2.cipher_update(context, ciphertext+(b'\0'*16))
41-
42-
#If this bug is ever fixed, then plaintext will end up having a garbage
43-
#plaintext block on the end. That's okay - the below code will ignore it.
44-
plaintext = plaintext[:len(ciphertext)]
45-
m2.cipher_ctx_free(context)
46-
self.IV = ciphertext[-self.block_size:]
46+
plaintext = m2.cipher_update(self._context, ciphertext)
4747
return bytearray(plaintext)
48+
49+
def __del__(self):
50+
if self._context is not None:
51+
m2.cipher_ctx_free(self._context)

tlslite/utils/python_tripledes.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,10 @@ def __init__(self, key, iv=None):
417417
self.name = "3des"
418418
self.implementation = "python"
419419

420+
self.__key1.iv = self.iv
421+
self.__key2.iv = self.iv
422+
self.__key3.iv = self.iv
423+
420424
def encrypt(self, data):
421425
"""Encrypt data and return bytes.
422426
@@ -437,9 +441,6 @@ def encrypt(self, data):
437441
raise ValueError("Invalid data length, must be a multiple "
438442
"of {0} bytes".format(self.block_size))
439443

440-
self.__key1.iv = self.iv
441-
self.__key2.iv = self.iv
442-
self.__key3.iv = self.iv
443444
i = 0
444445
result = []
445446
while i < len(data):
@@ -473,9 +474,6 @@ def decrypt(self, data):
473474
raise ValueError("Invalid data length, must be a multiple "
474475
"of {0} bytes".format(self.block_size))
475476

476-
self.__key1.iv = self.iv
477-
self.__key2.iv = self.iv
478-
self.__key3.iv = self.iv
479477
i = 0
480478
result = []
481479
while i < len(data):
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright (c) 2019, Alexander Sosedkin
2+
#
3+
# See the LICENSE file for legal information regarding use of this file.
4+
5+
# compatibility with Python 2.6, for that we need unittest2 package,
6+
# which is not available on 3.3 or 3.4
7+
try:
8+
import unittest2 as unittest
9+
except ImportError:
10+
import unittest
11+
12+
import sys
13+
14+
from hypothesis import given, assume, settings
15+
from hypothesis.strategies import binary, integers, tuples
16+
17+
from tlslite.utils import cryptomath
18+
19+
import tlslite.utils.python_tripledes
20+
py_3des = tlslite.utils.python_tripledes.new
21+
22+
23+
HYP_SETTINGS = {'deadline': None} if sys.version_info > (2, 7) else {}
24+
25+
26+
class TestTripleDES(unittest.TestCase):
27+
_given = given(binary(min_size=24, max_size=24), # key
28+
binary(min_size=8, max_size=8), # iv
29+
binary(min_size=13*8, max_size=13*8), # plaintext
30+
(tuples(integers(0, 13), integers(0, 13)) # split points
31+
.filter(lambda split_pts: split_pts[0] <= split_pts[1])
32+
.map(lambda lengths: [i * 8 for i in lengths])))
33+
34+
def split_test(self, key, iv, plaintext, split_points, make_impl=py_3des):
35+
i, j = split_points
36+
37+
ciphertext = make_impl(key, iv).encrypt(plaintext)
38+
self.assertEqual(make_impl(key, iv).decrypt(ciphertext), plaintext)
39+
40+
impl = make_impl(key, iv)
41+
pl1, pl2, pl3 = plaintext[:i], plaintext[i:j], plaintext[j:]
42+
ci1, ci2, ci3 = impl.encrypt(pl1), impl.encrypt(pl2), impl.encrypt(pl3)
43+
self.assertEqual(ci1 + ci2 + ci3, ciphertext)
44+
45+
impl = make_impl(key, iv)
46+
pl1, pl2, pl3 = impl.decrypt(ci1), impl.decrypt(ci2), impl.decrypt(ci3)
47+
self.assertEqual(pl1 + pl2 + pl3, plaintext)
48+
49+
return ciphertext
50+
51+
@_given
52+
@settings(**HYP_SETTINGS)
53+
def test_python(self, key, iv, plaintext, split_points):
54+
self.split_test(key, iv, plaintext, split_points)
55+
56+
@unittest.skipIf(not cryptomath.m2cryptoLoaded, "requires M2Crypto")
57+
@_given
58+
@settings(**HYP_SETTINGS)
59+
def test_python_vs_mcrypto(self, key, iv, plaintext, split_points):
60+
import tlslite.utils.openssl_tripledes
61+
m2_3des = lambda k, iv: tlslite.utils.openssl_tripledes.new(k, 2, iv)
62+
63+
py_res = self.split_test(key, iv, plaintext, split_points, py_3des)
64+
m2_res = self.split_test(key, iv, plaintext, split_points, m2_3des)
65+
self.assertEqual(py_res, m2_res)
66+
67+
@unittest.skipIf(not cryptomath.pycryptoLoaded, "requires pycrypto")
68+
@_given
69+
@settings(**HYP_SETTINGS)
70+
def test_python_vs_pycrypto(self, key, iv, plaintext, split_points):
71+
import tlslite.utils.pycrypto_tripledes
72+
pc_3des = lambda k, iv: tlslite.utils.pycrypto_tripledes.new(k, 2, iv)
73+
74+
try:
75+
py_res = self.split_test(key, iv, plaintext, split_points, py_3des)
76+
pc_res = self.split_test(key, iv, plaintext, split_points, pc_3des)
77+
self.assertEqual(py_res, pc_res)
78+
except ValueError as e:
79+
# pycrypto deliberately rejects weak 3DES keys, skip such keys
80+
assume(e.args != ('Triple DES key degenerates to single DES',))
81+
raise

0 commit comments

Comments
 (0)