summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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