Skip to content

Commit adefcaf

Browse files
committed
Fix IV handling for 3DES, test consecutive calls
Correctly set IV for consecutive 3DES encrypt/decrypt invocations. Test that the results of three invocations yield the same result as a combined single one, cross-check this across three implementations.
1 parent c9748b8 commit adefcaf

File tree

2 files changed

+85
-6
lines changed

2 files changed

+85
-6
lines changed

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(1, 12), integers(1, 12)) # 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)