diff --git a/Changelog.rst b/Changelog.rst index 59dbbfc8..c2883504 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -13,9 +13,11 @@ New features * For CFB/OPENPGP, `encrypt` and `decrypt` do not require the plaintext or ciphertext pieces to have length multiple of the CFB segment size. * Dedicated tests for all cipher modes, including NIST test vectors -* CCM/EAX/GCM/SIV cipher objects expose attribute `nonce`. +* CTR/CCM/EAX/GCM/SIV cipher objects expose attribute `nonce`. * CCM cipher checks if the declared lengths of the associated data and of the message match the actual ones. +* CTR cipher accepts parameter `nonce` and possibly `initial_value` in + alternative to `counter`. Resolved issues --------------- diff --git a/lib/Crypto/Cipher/AES.py b/lib/Crypto/Cipher/AES.py index 15a545ba..167bb8e5 100644 --- a/lib/Crypto/Cipher/AES.py +++ b/lib/Crypto/Cipher/AES.py @@ -172,7 +172,8 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 16 bytes long. nonce : byte string - (*Only* `MODE_CCM`, `MODE_EAX`, `MODE_GCM`, `MODE_SIV`, `MODE_OCB`). + (*Only* `MODE_CCM`, `MODE_EAX`, `MODE_GCM`, `MODE_SIV`, `MODE_OCB`, + `MODE_CTR`). A mandatory value that must never be reused for any other encryption. @@ -184,10 +185,10 @@ def new(key, mode, *args, **kwargs): For `MODE_OCB`, its length must be in the range ``[1..15]``. It is recommended to use 15 bytes. + For `MODE_CTR`, its length must be in the range ``[0..15]``. + For the other modes, there are no restrictions on its length, but it is recommended to use at least 16 bytes. - counter : object - (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. segment_size : integer (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. @@ -204,6 +205,9 @@ def new(key, mode, *args, **kwargs): assoc_len : integer (*Only* `MODE_CCM`). Length of the associated data. If not specified, all data is internally buffered. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. use_aesni : boolean Use AES-NI if available. diff --git a/lib/Crypto/Cipher/ARC2.py b/lib/Crypto/Cipher/ARC2.py index d07648c8..cee4c765 100644 --- a/lib/Crypto/Cipher/ARC2.py +++ b/lib/Crypto/Cipher/ARC2.py @@ -132,8 +132,6 @@ def new(key, mode, *args, **kwargs): The initialization vector to use for encryption or decryption. - It is ignored for `MODE_ECB` and `MODE_CTR`. - For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption and `block_size` +2 bytes for decryption (in the latter case, it is actually the *encrypted* IV which was prefixed to the ciphertext). @@ -141,9 +139,12 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 8 bytes long. nonce : byte string - (*Only* `MODE_EAX`). + (*Only* `MODE_EAX` and `MODE_CTR`). A mandatory value that must never be reused for any other encryption. - There are no restrictions on its length, but it is recommended to + + For `MODE_CTR`, its length must be in the range ``[0..7]``. + + For `MODE_EAX`, there are no restrictions, but it is recommended to use at least 16 bytes. counter : object (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. @@ -154,6 +155,9 @@ def new(key, mode, *args, **kwargs): (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. It must be a multiple of 8. If not specified, it will be assumed to be 8. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. effective_keylen : integer Maximum cryptographic strength of the key, in bits. It can vary from 0 to 1024. The default value is 1024. diff --git a/lib/Crypto/Cipher/Blowfish.py b/lib/Crypto/Cipher/Blowfish.py index 4ed045f0..c22e03cf 100644 --- a/lib/Crypto/Cipher/Blowfish.py +++ b/lib/Crypto/Cipher/Blowfish.py @@ -116,8 +116,6 @@ def new(key, mode, *args, **kwargs): The initialization vector to use for encryption or decryption. - It is ignored for `MODE_ECB` and `MODE_CTR`. - For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption and `block_size` +2 bytes for decryption (in the latter case, it is actually the *encrypted* IV which was prefixed to the ciphertext). @@ -125,9 +123,12 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 8 bytes long. nonce : byte string - (*Only* `MODE_EAX`). + (*Only* `MODE_EAX` and `MODE_CTR`). A mandatory value that must never be reused for any other encryption. - There are no restrictions on its length, but it is recommended to + + For `MODE_CTR`, its length must be in the range ``[0..7]``. + + For `MODE_EAX`, there are no restrictions, but it is recommended to use at least 16 bytes. counter : object (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. @@ -138,6 +139,9 @@ def new(key, mode, *args, **kwargs): (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. It must be a multiple of 8. If not specified, it will be assumed to be 8. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. :Return: a Blowfish cipher object, of the applicable mode. """ diff --git a/lib/Crypto/Cipher/CAST.py b/lib/Crypto/Cipher/CAST.py index 20960fa5..fe7c53d0 100644 --- a/lib/Crypto/Cipher/CAST.py +++ b/lib/Crypto/Cipher/CAST.py @@ -129,9 +129,12 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 8 bytes long. nonce : byte string - (*Only* `MODE_EAX`). + (*Only* `MODE_EAX` and `MODE_CTR`). A mandatory value that must never be reused for any other encryption. - There are no restrictions on its length, but it is recommended to + + For `MODE_CTR`, its length must be in the range ``[0..7]``. + + For `MODE_EAX`, there are no restrictions, but it is recommended to use at least 16 bytes. counter : object (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. @@ -142,6 +145,9 @@ def new(key, mode, *args, **kwargs): (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. It must be a multiple of 8. If not specified, it will be assumed to be 8. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. :Return: a CAST cipher object, of the applicable mode. """ diff --git a/lib/Crypto/Cipher/DES.py b/lib/Crypto/Cipher/DES.py index 1850b763..ec208555 100644 --- a/lib/Crypto/Cipher/DES.py +++ b/lib/Crypto/Cipher/DES.py @@ -123,9 +123,12 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 8 bytes long. nonce : byte string - (*Only* `MODE_EAX`). + (*Only* `MODE_EAX` and `MODE_CTR`). A mandatory value that must never be reused for any other encryption. - There are no restrictions on its length, but it is recommended to + + For `MODE_CTR`, its length must be in the range ``[0..7]``. + + For `MODE_EAX`, there are no restrictions, but it is recommended to use at least 16 bytes. counter : object (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. @@ -136,6 +139,9 @@ def new(key, mode, *args, **kwargs): (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. It must be a multiple of 8. If not specified, it will be assumed to be 8. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. :Return: a DES cipher, of the applicable mode. """ diff --git a/lib/Crypto/Cipher/DES3.py b/lib/Crypto/Cipher/DES3.py index 7a6e3915..07a76b76 100644 --- a/lib/Crypto/Cipher/DES3.py +++ b/lib/Crypto/Cipher/DES3.py @@ -45,13 +45,11 @@ as `AES`. As an example, encryption can be done as follows: >>> from Crypto.Cipher import DES3 - >>> from Crypto import Random - >>> from Crypto.Util import Counter + >>> from Crypto.Random import get_random_bytes >>> >>> key = b'Sixteen byte key' - >>> nonce = Random.new().read(DES3.block_size/2) - >>> ctr = Counter.new(DES3.block_size*8/2, prefix=nonce) - >>> cipher = DES3.new(key, DES3.MODE_CTR, counter=ctr) + >>> nonce = get_random_bytes(DES3.block_size/2) + >>> cipher = DES3.new(key, DES3.MODE_CTR, nonce=nonce) >>> plaintext = b'We are no longer the knights who say ni!' >>> msg = nonce + cipher.encrypt(plaintext) @@ -138,9 +136,12 @@ def new(key, mode, *args, **kwargs): For all other modes, it must be 8 bytes long. nonce : byte string - (*Only* `MODE_EAX`). + (*Only* `MODE_EAX` and `MODE_CTR`). A mandatory value that must never be reused for any other encryption. - There are no restrictions on its length, but it is recommended to + + For `MODE_CTR`, its length must be in the range ``[0..7]``. + + For `MODE_EAX`, there are no restrictions, but it is recommended to use at least 16 bytes. counter : object (*Only* `MODE_CTR`). An object created by `Crypto.Util.Counter`. @@ -151,6 +152,9 @@ def new(key, mode, *args, **kwargs): (*Only* `MODE_CFB`).The number of bits the plaintext and ciphertext are segmented in. It must be a multiple of 8. If not specified, it will be assumed to be 8. + initial_value : integer + (*Only* `MODE_CTR`). The initial value for the counter within + the counter block. By default it is 0. :Attention: it is important that all 8 byte subkeys are different, otherwise TDES would degrade to single `DES`. diff --git a/lib/Crypto/Cipher/_mode_ccm.py b/lib/Crypto/Cipher/_mode_ccm.py index b2d5ccd1..9f130771 100644 --- a/lib/Crypto/Cipher/_mode_ccm.py +++ b/lib/Crypto/Cipher/_mode_ccm.py @@ -36,7 +36,6 @@ __all__ = ['CcmMode'] from Crypto.Util.py3compat import byte_string, b, bchr, bord, unhexlify -from Crypto.Util import Counter from Crypto.Util.strxor import strxor from Crypto.Util.number import long_to_bytes @@ -163,11 +162,9 @@ class CcmMode(object): # Start CTR cipher, by formatting the counter (A.3) q = 15 - len(nonce) # length of Q, the encoded message length - prefix = bchr(q - 1) + nonce - ctr = Counter.new(128 - len(prefix) * 8, prefix, initial_value=0) self._cipher = self._factory.new(key, self._factory.MODE_CTR, - counter=ctr, + nonce=bchr(q - 1) + nonce, **cipher_params) # S_0, step 6 in 6.1 for j=0 diff --git a/lib/Crypto/Cipher/_mode_ctr.py b/lib/Crypto/Cipher/_mode_ctr.py index 0ffee1b2..29c33858 100644 --- a/lib/Crypto/Cipher/_mode_ctr.py +++ b/lib/Crypto/Cipher/_mode_ctr.py @@ -30,7 +30,9 @@ from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer, create_string_buffer, get_raw_buffer, SmartPointer, c_size_t, expect_byte_string) -from Crypto.Util.py3compat import * +from Crypto.Random import get_random_bytes +from Crypto.Util.py3compat import b, bchr +from Crypto.Util.number import long_to_bytes raw_ctr_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ctr", """ int CTR_start_operation(void *cipher, @@ -111,6 +113,10 @@ class CtrMode(object): in little endian mode. If False, it is big endian. """ + if len(initial_counter_block) == prefix_len + counter_len: + #: Nonce; not available if there is a fixed suffix + self.nonce = initial_counter_block[:prefix_len] + expect_byte_string(initial_counter_block) self._state = VoidPointer() result = raw_ctr_lib.CTR_start_operation(block_cipher.get(), @@ -136,7 +142,7 @@ class CtrMode(object): #: The block size of the underlying cipher, in bytes. self.block_size = len(initial_counter_block) - self._next = [ self.encrypt, self.decrypt ] + self._next = [self.encrypt, self.decrypt] def encrypt(self, plaintext): """Encrypt data with the key and the parameters set at initialization. @@ -169,7 +175,7 @@ class CtrMode(object): if self.encrypt not in self._next: raise TypeError("encrypt() cannot be called after decrypt()") - self._next = [ self.encrypt ] + self._next = [self.encrypt] expect_byte_string(plaintext) ciphertext = create_string_buffer(len(plaintext)) @@ -179,7 +185,8 @@ class CtrMode(object): c_size_t(len(plaintext))) if result: if result == 0x60002: - raise OverflowError("The counter has wrapped around in CTR mode") + raise OverflowError("The counter has wrapped around in" + " CTR mode") raise ValueError("Error %X while encrypting in CTR mode" % result) return get_raw_buffer(ciphertext) @@ -213,7 +220,7 @@ class CtrMode(object): if self.decrypt not in self._next: raise TypeError("decrypt() cannot be called after encrypt()") - self._next = [ self.decrypt ] + self._next = [self.decrypt] expect_byte_string(ciphertext) plaintext = create_string_buffer(len(ciphertext)) @@ -223,7 +230,8 @@ class CtrMode(object): c_size_t(len(ciphertext))) if result: if result == 0x60002: - raise OverflowError("The counter has wrapped around in CTR mode") + raise OverflowError("The counter has wrapped around in" + " CTR mode") raise ValueError("Error %X while decrypting in CTR mode" % result) return get_raw_buffer(plaintext) @@ -236,14 +244,29 @@ def _create_ctr_cipher(factory, **kwargs): The underlying block cipher, a module from ``Crypto.Cipher``. :Keywords: - iv : byte string - The IV to use for CBC. + nonce : binary string + The fixed part at the beginning of the counter block - the rest is + the counter number that gets increased when processing the next block. + The nonce must be such that no two messages are encrypted under the + same key and the same nonce. - IV : byte string - Alias for ``iv``. + The nonce must be shorter than the block size (it can have + zero length). + + If this parameter is not present, a random nonce will be created with + length equal to half the block size. No random nonce shorter than + 64 bits will be created though - you must really think through all + security consequences of using such a short block size. + + initial_value : posive integer + The initial value for the counter. If not present, the cipher will + start counting from 0. The value is incremented by one for each block. + The counter number is encoded in big endian mode. counter : object - Instance of ``Crypto.Util.Counter``. + Instance of ``Crypto.Util.Counter``, which allows full customization + of the counter block. This parameter is incompatible to both ``nonce`` + and ``initial_value``. Any other keyword will be passed to the underlying block cipher. See the relevant documentation for details (at least ``key`` will need @@ -251,11 +274,43 @@ def _create_ctr_cipher(factory, **kwargs): """ cipher_state = factory._create_base_cipher(kwargs) - try: - counter = kwargs.pop("counter") - except KeyError: - # Required by unit test - raise TypeError("Missing 'counter' parameter for CTR mode") + + counter = kwargs.pop("counter", None) + nonce = kwargs.pop("nonce", None) + initial_value = kwargs.pop("initial_value", None) + if kwargs: + raise TypeError("Invalid parameters for CTR mode: %s" % str(kwargs)) + + if counter is not None and (nonce, initial_value) != (None, None): + raise TypeError("'counter' and 'nonce'/'initial_value'" + " are mutually exclusive") + + if counter is None: + # Crypto.Util.Counter is not used + if nonce is None: + if factory.block_size < 16: + raise TypeError("Impossible to create a safe nonce for short" + " block sizes") + nonce = get_random_bytes(factory.block_size // 2) + + if initial_value is None: + initial_value = 0 + + if len(nonce) >= factory.block_size: + raise ValueError("Nonce is too long") + + counter_len = factory.block_size - len(nonce) + if (1 << (counter_len * 8)) - 1 < initial_value: + raise ValueError("Initial counter value is too large") + + return CtrMode(cipher_state, + # initial_counter_block + nonce + long_to_bytes(initial_value, counter_len), + len(nonce), # prefix + counter_len, + False) # little_endian + + # Crypto.Util.Counter is used # 'counter' used to be a callable object, but now it is # just a dictionary for backward compatibility. @@ -267,7 +322,8 @@ def _create_ctr_cipher(factory, **kwargs): initial_value = _counter.pop("initial_value") little_endian = _counter.pop("little_endian") except KeyError: - raise TypeError("Incorrect counter object (use Crypto.Util.Counter.new)") + raise TypeError("Incorrect counter object" + " (use Crypto.Util.Counter.new)") # Compute initial counter block words = [] @@ -281,10 +337,8 @@ def _create_ctr_cipher(factory, **kwargs): if len(initial_counter_block) != factory.block_size: raise ValueError("Size of the counter block (% bytes) must match" - " block size (%d)", (len(initial_counter_block), factory.block_size)) + " block size (%d)" % (len(initial_counter_block), + factory.block_size)) - if kwargs: - raise TypeError("Unknown parameters for CTR mode: %s" - % str(kwargs)) return CtrMode(cipher_state, initial_counter_block, - len(prefix), counter_len, little_endian) + len(prefix), counter_len, little_endian) diff --git a/lib/Crypto/Cipher/_mode_eax.py b/lib/Crypto/Cipher/_mode_eax.py index 736596e4..58478fdf 100644 --- a/lib/Crypto/Cipher/_mode_eax.py +++ b/lib/Crypto/Cipher/_mode_eax.py @@ -34,9 +34,8 @@ EAX mode. __all__ = ['EaxMode'] -from Crypto.Util.py3compat import byte_string, bchr, bord, unhexlify +from Crypto.Util.py3compat import byte_string, bchr, bord, unhexlify, b -from Crypto.Util import Counter from Crypto.Util.strxor import strxor from Crypto.Util.number import long_to_bytes, bytes_to_long @@ -109,12 +108,10 @@ class EaxMode(object): # MAC of the nonce is also the initial counter for CTR encryption counter_int = bytes_to_long(self._omac[0].digest()) - counter_obj = Counter.new( - self.block_size * 8, - initial_value=counter_int) self._cipher = factory.new(key, factory.MODE_CTR, - counter=counter_obj, + initial_value=counter_int, + nonce=b(""), **cipher_params) def update(self, assoc_data): diff --git a/lib/Crypto/Cipher/_mode_gcm.py b/lib/Crypto/Cipher/_mode_gcm.py index 4bff593f..4c627eb5 100644 --- a/lib/Crypto/Cipher/_mode_gcm.py +++ b/lib/Crypto/Cipher/_mode_gcm.py @@ -36,7 +36,6 @@ __all__ = ['GcmMode'] from Crypto.Util.py3compat import b, bchr, byte_string, bord, unhexlify -from Crypto.Util import Counter from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.Hash import BLAKE2s from Crypto.Random import get_random_bytes @@ -189,20 +188,20 @@ class GcmMode(object): .digest()) # Step 3 - Prepare GCTR cipher for encryption/decryption - ctr = Counter.new(128, initial_value=self._j0 + 1) self._cipher = factory.new(key, self._factory.MODE_CTR, - counter=ctr, + initial_value=self._j0 + 1, + nonce=b(""), **cipher_params) # Step 5 - Bootstrat GHASH self._signer = _GHASH(hash_subkey) # Step 6 - Prepare GCTR cipher for GMAC - ctr = Counter.new(128, initial_value=self._j0) self._tag_cipher = factory.new(key, self._factory.MODE_CTR, - counter=ctr, + initial_value=self._j0, + nonce=b(""), **cipher_params) # Cache for data to authenticate diff --git a/lib/Crypto/Cipher/_mode_siv.py b/lib/Crypto/Cipher/_mode_siv.py index a01f4a15..9ec487ca 100644 --- a/lib/Crypto/Cipher/_mode_siv.py +++ b/lib/Crypto/Cipher/_mode_siv.py @@ -36,9 +36,8 @@ __all__ = ['SivMode'] from binascii import hexlify -from Crypto.Util.py3compat import byte_string, bord, unhexlify +from Crypto.Util.py3compat import byte_string, bord, unhexlify, b -from Crypto.Util import Counter from Crypto.Util.number import long_to_bytes, bytes_to_long from Crypto.Protocol.KDF import _S2V from Crypto.Hash import BLAKE2s @@ -125,14 +124,11 @@ class SivMode(object): """Create a new CTR cipher from the MAC in SIV mode""" tag_int = bytes_to_long(mac_tag) - init_counter = tag_int ^ (tag_int & 0x8000000080000000L) - ctr = Counter.new(self.block_size * 8, - initial_value=init_counter) - return self._factory.new( self._subkey_cipher, self._factory.MODE_CTR, - counter=ctr, + initial_value=tag_int ^ (tag_int & 0x8000000080000000L), + nonce=b(""), **self._cipher_params) def update(self, component): diff --git a/lib/Crypto/SelfTest/Cipher/test_CTR.py b/lib/Crypto/SelfTest/Cipher/test_CTR.py index 55d1cb38..de5abb16 100644 --- a/lib/Crypto/SelfTest/Cipher/test_CTR.py +++ b/lib/Crypto/SelfTest/Cipher/test_CTR.py @@ -43,8 +43,10 @@ class CtrTests(unittest.TestCase): key_128 = get_tag_random("key_128", 16) key_192 = get_tag_random("key_192", 24) - ctr_64 = Counter.new(32, prefix=get_tag_random("iv_64", 4)) - ctr_128 = Counter.new(64, prefix=get_tag_random("iv_128", 8)) + nonce_32 = get_tag_random("nonce_32", 4) + nonce_64 = get_tag_random("nonce_64", 8) + ctr_64 = Counter.new(32, prefix=nonce_32) + ctr_128 = Counter.new(64, prefix=nonce_64) def test_loopback_128(self): cipher = AES.new(self.key_128, AES.MODE_CTR, counter=self.ctr_128) @@ -64,10 +66,74 @@ class CtrTests(unittest.TestCase): pt2 = cipher.decrypt(ct) self.assertEqual(pt, pt2) - def test_counter_is_required(self): - self.assertRaises(TypeError, AES.new, self.key_128, AES.MODE_CTR) + def test_invalid_counter_parameter(self): + # Counter object is required for ciphers with short block size + self.assertRaises(TypeError, DES3.new, self.key_192, AES.MODE_CTR) + # Positional arguments are not allowed (Counter must be passed as + # keyword) self.assertRaises(TypeError, AES.new, self.key_128, AES.MODE_CTR, self.ctr_128) + def test_nonce_attribute(self): + # Nonce attribute is the prefix passed to Counter (DES3) + cipher = DES3.new(self.key_192, DES3.MODE_CTR, counter=self.ctr_64) + self.assertEqual(cipher.nonce, self.nonce_32) + + # Nonce attribute is the prefix passed to Counter (AES) + cipher = AES.new(self.key_128, AES.MODE_CTR, counter=self.ctr_128) + self.assertEqual(cipher.nonce, self.nonce_64) + + # Nonce attribute is not defined if suffix is used in Counter + counter = Counter.new(64, prefix=self.nonce_32, suffix=self.nonce_32) + cipher = AES.new(self.key_128, AES.MODE_CTR, counter=counter) + self.failIf(hasattr(cipher, "nonce")) + + def test_nonce_parameter(self): + # Nonce parameter becomes nonce attribute + cipher1 = AES.new(self.key_128, AES.MODE_CTR, nonce=self.nonce_64) + self.assertEqual(cipher1.nonce, self.nonce_64) + + counter = Counter.new(64, prefix=self.nonce_64, initial_value=0) + cipher2 = AES.new(self.key_128, AES.MODE_CTR, counter=counter) + self.assertEqual(cipher1.nonce, cipher2.nonce) + + pt = get_tag_random("plaintext", 65536) + self.assertEqual(cipher1.encrypt(pt), cipher2.encrypt(pt)) + + # Nonce is implicitly created (for AES) when no parameters are passed + nonce1 = AES.new(self.key_128, AES.MODE_CTR).nonce + nonce2 = AES.new(self.key_128, AES.MODE_CTR).nonce + self.assertNotEqual(nonce1, nonce2) + self.assertEqual(len(nonce1), 8) + + # Nonce can be zero-length + cipher = AES.new(self.key_128, AES.MODE_CTR, nonce=b("")) + self.assertEqual(b(""), cipher.nonce) + + # Nonce and Counter are mutually exclusive + self.assertRaises(TypeError, AES.new, self.key_128, AES.MODE_CTR, + counter=self.ctr_128, nonce=self.nonce_64) + + def test_initial_value_parameter(self): + # Test with nonce parameter + cipher1 = AES.new(self.key_128, AES.MODE_CTR, + nonce=self.nonce_64, initial_value=0xFFFF) + counter = Counter.new(64, prefix=self.nonce_64, initial_value=0xFFFF) + cipher2 = AES.new(self.key_128, AES.MODE_CTR, counter=counter) + pt = get_tag_random("plaintext", 65536) + self.assertEqual(cipher1.encrypt(pt), cipher2.encrypt(pt)) + + # Test without nonce parameter + cipher1 = AES.new(self.key_128, AES.MODE_CTR, + initial_value=0xFFFF) + counter = Counter.new(64, prefix=cipher1.nonce, initial_value=0xFFFF) + cipher2 = AES.new(self.key_128, AES.MODE_CTR, counter=counter) + pt = get_tag_random("plaintext", 65536) + self.assertEqual(cipher1.encrypt(pt), cipher2.encrypt(pt)) + + # Initial_value and Counter are mutually exclusive + self.assertRaises(TypeError, AES.new, self.key_128, AES.MODE_CTR, + counter=self.ctr_128, initial_value=0) + def test_iv_with_matching_length(self): self.assertRaises(ValueError, AES.new, self.key_128, AES.MODE_CTR, counter=Counter.new(120))