diff options
-rw-r--r-- | lib/Crypto/Cipher/blockalgo.py | 60 | ||||
-rw-r--r-- | lib/Crypto/SelfTest/Cipher/common.py | 147 |
2 files changed, 194 insertions, 13 deletions
diff --git a/lib/Crypto/Cipher/blockalgo.py b/lib/Crypto/Cipher/blockalgo.py index 8f78cc8..f88e620 100644 --- a/lib/Crypto/Cipher/blockalgo.py +++ b/lib/Crypto/Cipher/blockalgo.py @@ -133,7 +133,7 @@ def _getParameter(name, index, args, kwargs, default=None): param = args[index] return param or default -class BlockAlgo: +class BlockAlgo(object): """Class modelling an abstract block cipher.""" def __init__(self, factory, key, *args, **kwargs): @@ -142,7 +142,6 @@ class BlockAlgo: if self.mode != MODE_OPENPGP: self._cipher = factory.new(key, *args, **kwargs) - self.IV = self._cipher.IV else: # OPENPGP mode. For details, see 13.9 in RCC4880. # @@ -153,15 +152,15 @@ class BlockAlgo: self._done_first_block = False self._done_last_block = False - self.IV = _getParameter('IV', 1, args, kwargs) - if not self.IV: + self._IV = _getParameter('IV', 1, args, kwargs) + if not self._IV: # XXX - When MODE_OPENPGP was introduced in PyCrypto 2.6, it # expected a lowercase 'iv' kwarg, but every other block cipher # module expected uppercase 'IV'. For backward-compatibility, # we'll support the old form for a release or two, then remove # it. - self.IV = _getParameter('iv', 1, args, kwargs) - if self.IV: + self._IV = _getParameter('iv', 1, args, kwargs) + if self._IV: warnings.warn("lowercase 'iv' kwarg will be removed in a future version of PyCrypto", LowercaseIV_DeprecationWarning, 4) else: @@ -173,21 +172,21 @@ class BlockAlgo: segment_size=self.block_size*8) # The cipher will be used for... - if len(self.IV) == self.block_size: + if len(self._IV) == self.block_size: # ... encryption self._encrypted_IV = IV_cipher.encrypt( - self.IV + self.IV[-2:] + # Plaintext + self._IV + self._IV[-2:] + # Plaintext b('\x00')*(self.block_size-2) # Padding )[:self.block_size+2] - elif len(self.IV) == self.block_size+2: + elif len(self._IV) == self.block_size+2: # ... decryption - self._encrypted_IV = self.IV - self.IV = IV_cipher.decrypt(self.IV + # Ciphertext + self._encrypted_IV = self._IV + self._IV = IV_cipher.decrypt(self._IV + # Ciphertext b('\x00')*(self.block_size-2) # Padding )[:self.block_size+2] - if self.IV[-2:] != self.IV[-4:-2]: + if self._IV[-2:] != self._IV[-4:-2]: raise ValueError("Failed integrity check for OPENPGP IV") - self.IV = self.IV[:-2] + self._IV = self._IV[:-2] else: raise ValueError("Length of IV must be %d or %d bytes for MODE_OPENPGP" % (self.block_size, self.block_size+2)) @@ -197,6 +196,41 @@ class BlockAlgo: self._encrypted_IV[-self.block_size:], segment_size=self.block_size*8) + def _BlockAlgo__getIV(self): # Internal getter for the 'IV' attribute + if self.mode in (MODE_ECB, MODE_CTR): + raise AttributeError("No IV in this cipher mode") + elif self.mode == MODE_OPENPGP: + return self._IV + else: + return self._cipher.IV + + def _BlockAlgo__setIV(self, value): # Internal setter for the 'IV' attribute + if self.mode in (MODE_ECB, MODE_CTR): + # It wouldn't do anything anyway + raise AttributeError("No IV in this cipher mode") + elif self.mode == MODE_OPENPGP: + # It wouldn't do anything anyway + raise AttributeError("Can't set IV in this cipher mode") + else: + self._cipher.IV = value + + if sys.version_info[0] == 2 and sys.version_info[1] == 1: + # Python 2.1 doesn't have properties + def __getattr__(self, name): + if name == 'IV': + return self.__getIV() + else: + raise AttributeError(name) + + def __setattr__(self, name, value): + if name == 'IV': + self.__setIV(value) + else: + self.__dict__[name] = value + else: + IV = property(_BlockAlgo__getIV, _BlockAlgo__setIV) + del _BlockAlgo__getIV, _BlockAlgo__setIV + def encrypt(self, plaintext): """Encrypt data with the key and the parameters set at initialization. diff --git a/lib/Crypto/SelfTest/Cipher/common.py b/lib/Crypto/SelfTest/Cipher/common.py index 26fabed..eacc2b5 100644 --- a/lib/Crypto/SelfTest/Cipher/common.py +++ b/lib/Crypto/SelfTest/Cipher/common.py @@ -29,7 +29,10 @@ __revision__ = "$Id$" import sys import unittest from binascii import a2b_hex, b2a_hex +if sys.version_info[0] == 2 and sys.version_info[1] == 1: + from Crypto.Util.py21compat import * from Crypto.Util.py3compat import * +from Crypto.Util.strxor import strxor # For compatibility with Python 2.1 and Python 2.2 if sys.hexversion < 0x02030000: @@ -289,6 +292,149 @@ class IVLengthTest(unittest.TestCase): def _dummy_counter(self): return "\0" * self.module.block_size +class IVAttributeTest(unittest.TestCase): + def __init__(self, module, params): + unittest.TestCase.__init__(self) + self.module = module + self.key = b(params['key']) + + def shortDescription(self): + return "Check that the IV attribute is updated after encryption and decryption" + + def runTest(self): + self._run_tests_on_module(low_level=True) + self._run_tests_on_module(low_level=False) + + def _run_tests_on_module(self, low_level): + from Crypto import Random + k = a2b_hex(self.key) + iv = Random.get_random_bytes(self.module.block_size) + zero = b("\0")*self.module.block_size + plaintext = Random.get_random_bytes(self.module.block_size) + + ## + ## ECB mode + ## + + # ECB mode doesn't have an IV, but only test the high-level API, since + # the low-level code currently behaves differently. + if not low_level: + cipher = self.module.new(k, self.module.MODE_ECB) + self.assertRaises(AttributeError, getattr, cipher, 'IV') + self.assertRaises(AttributeError, setattr, cipher, 'IV', iv) + + ## + ## CBC mode + ## + + # .IV should initially be the real IV + cipher = self.module.new(k, self.module.MODE_CBC, iv) + if low_level: + cipher = cipher._cipher + self.assertEqual(b2a_hex(iv), b2a_hex(cipher.IV)) + + # After encryption, the IV should equal the previous ciphertext block + ciphertext = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(cipher.IV)) + + # Modifying the IV should affect encryption + cipher.IV = iv + ciphertext2 = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(ciphertext2)) + + # Modifying the IV should affect decryption + cipher = self.module.new(k, self.module.MODE_CBC, zero) + if low_level: + cipher = cipher._cipher + cipher.IV = iv + plaintext2 = cipher.decrypt(ciphertext) + self.assertEqual(b2a_hex(plaintext), b2a_hex(plaintext2)) + + # After decryption, the IV should equal the previous ciphetext block + self.assertEqual(b2a_hex(ciphertext), b2a_hex(cipher.IV)) + + ## + ## CFB mode + ## + + # .IV should initially be the real IV + cipher = self.module.new(k, self.module.MODE_CFB, iv) + if low_level: + cipher = cipher._cipher + self.assertEqual(b2a_hex(iv), b2a_hex(cipher.IV)) + + # After encryption, the IV should equal the previous ciphertext block + ciphertext = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(cipher.IV)) + + # Modifying the IV should affect encryption + cipher.IV = iv + ciphertext2 = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(ciphertext2)) + + # Modifying the IV should affect decryption + cipher = self.module.new(k, self.module.MODE_CFB, zero) + if low_level: + cipher = cipher._cipher + cipher.IV = iv + plaintext2 = cipher.decrypt(ciphertext) + self.assertEqual(b2a_hex(plaintext), b2a_hex(plaintext2)) + + # After decryption, the IV should equal the previous ciphetext block + self.assertEqual(b2a_hex(ciphertext), b2a_hex(cipher.IV)) + + ## + ## OFB mode + ## + + # .IV should initially be the real IV + cipher = self.module.new(k, self.module.MODE_OFB, iv) + if low_level: + cipher = cipher._cipher + self.assertEqual(b2a_hex(iv), b2a_hex(cipher.IV)) + + # After encryption, the IV should equal the previous ciphertext block XOR the previous plaintext block + ciphertext = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(strxor(ciphertext, plaintext)), b2a_hex(cipher.IV)) + + # Modifying the IV should affect encryption + cipher.IV = iv + ciphertext2 = cipher.encrypt(plaintext) + self.assertEqual(b2a_hex(ciphertext), b2a_hex(ciphertext2)) + + # Modifying the IV should affect decryption + cipher = self.module.new(k, self.module.MODE_OFB, zero) + if low_level: + cipher = cipher._cipher + cipher.IV = iv + plaintext2 = cipher.decrypt(ciphertext) + self.assertEqual(b2a_hex(plaintext), b2a_hex(plaintext2)) + + # After decryption, the IV should equal the previous ciphetext block + self.assertEqual(b2a_hex(strxor(ciphertext, plaintext2)), b2a_hex(cipher.IV)) + + ## + ## CTR mode + ## + + # The CTR-mode nonce is accessible via the counter object, not the + # cipher itself. + if not low_level: + cipher = self.module.new(k, self.module.MODE_CTR, counter=lambda: "\0"*16) + self.assertRaises(AttributeError, getattr, cipher, 'IV') + self.assertRaises(AttributeError, setattr, cipher, 'IV', iv) + + ## + ## OPENPGP mode + ## + + # There is no low-level MODE_OPENPGP + if not low_level: + # OPENPGP mode doesn't allow you to set the IV. + cipher = self.module.new(k, self.module.MODE_OPENPGP, iv) + self.assertEqual(b2a_hex(iv), b2a_hex(cipher.IV)) + self.assertRaises(AttributeError, setattr, cipher, 'IV', zero) + def make_block_tests(module, module_name, test_data, additional_params=dict()): tests = [] extra_tests_added = 0 @@ -337,6 +483,7 @@ def make_block_tests(module, module_name, test_data, additional_params=dict()): RoundtripTest(module, params), PGPTest(module, params), IVLengthTest(module, params), + IVAttributeTest(module, params), ] extra_tests_added = 1 |