summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDwayne Litzenberger <dlitz@dlitz.net>2013-05-04 23:29:37 -0700
committerDwayne Litzenberger <dlitz@dlitz.net>2013-05-05 01:45:23 -0700
commitc273b6c237303cf6d09f5d33bdb58113df4dc679 (patch)
treea51642bea9b0b02b1043646a8eeba5d42e0c0a41
parent4ce0dd74b36af9f96747da650aed84bbe93c76df (diff)
downloadpycrypto-bugfixes-wip.tar.gz
[Fix LP#1176482] Restore previous behavior of the block cipher 'IV' attributebugfixes-wip
PEP 272 describes the behavior of the block cipher 'IV' attribute as follows: IV Contains the initial value which will be used to start a cipher feedback mode; it will always be a string exactly one block in length. After encrypting or decrypting a string, this value is updated to reflect the modified feedback text. It is read-only, and cannot be assigned a new value. In versions of PyCrypto prior to 2.6, the implementation of the 'IV' attribute matched this description, except that the attribute was also writable and could modify the behavior of the cipher. In PyCrypto 2.6, this behavior was changed: The 'IV' attribute was no longer updated during encryptions and decryptions, and writing to it no longer had any effect. PyCrypto 2.5 and below: >>> from Crypto.Cipher import AES >>> ciph = AES.new("\0"*16, AES.MODE_CBC, "ABCDEFGHIJKLMNOP") >>> ciph.encrypt("\0"*16) 'a\xd7\x82X\xeb\x1a\xbdo\xffG\x9d\x1d\xab\xb6\x10;' >>> ciph.IV 'a\xd7\x82X\xeb\x1a\xbdo\xffG\x9d\x1d\xab\xb6\x10;' PyCrypto 2.6: >>> from Crypto.Cipher import AES >>> ciph = AES.new("\0"*16, AES.MODE_CBC, "ABCDEFGHIJKLMNOP") >>> ciph.encrypt("\0"*16) 'a\xd7\x82X\xeb\x1a\xbdo\xffG\x9d\x1d\xab\xb6\x10;' >>> ciph.IV 'ABCDEFGHIJKLMNOP' The bug was introduced at the same time that we introduced Python wrappers around the native C objects. Bug introduced: commit 6f9fe103a582999c397f7bc8a2248613a207b780 Bug report: https://bugs.launchpad.net/pycrypto/+bug/1176482 This commit restores the previous (PyCrypto 2.5) behavior.
-rw-r--r--lib/Crypto/Cipher/blockalgo.py60
-rw-r--r--lib/Crypto/SelfTest/Cipher/common.py147
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