1
$\begingroup$

From what I understand, CTR works by taking a counter, concatenating it with a nonce, and encrypting the result, which gets XORed to the ciphertext. For a 128-bit block cipher (ie. AES), my understanding is that the first 8 bytes of the CTR block is the nonce, and the second half (bytes 8-16) is the counter. CTR is a secure mode of operation, but what if I modified it like this:

The nonce is 16 bytes, the counter is 16 bytes. The counter block is equal to nonce XOR counter.

Basically, he counter blocks are just XOR of nonce and counter. Would this be secure, or would this defeat the idea of CTR and be insecure?

Edit: For more clarity, here's CTR mode and my modified mode:

CTR:

nonce = 12345678
counter = 00000001
CTR block = 1234567800000001

My scheme:

nonce = 1234567890abcdef
counter = 0000000000000001
CTR block = 1234567890abcdeW
$\endgroup$
4
  • $\begingroup$ It is useful information, but it doesn't answer my question. I've updated my question with a simple example. $\endgroup$ – HACKERALERT Mar 11 at 17:34
  • $\begingroup$ Yes. The nonce is constant, the counter increases, just like it is supposed to in CTR mode. I'm just wondering if it's okay to XOR the nonce and counter, instead of concatenating, like how CTR does. $\endgroup$ – HACKERALERT Mar 11 at 17:38
  • $\begingroup$ The next counter is just the previous counter + 1 $\endgroup$ – HACKERALERT Mar 11 at 17:45
  • $\begingroup$ Related Disadvantages of AES-CTR? and (Why) should I avoid using a randomized IV for CTR mode? $\endgroup$ – kelalaka Mar 11 at 21:17
5
$\begingroup$

CTR works by taking a counter, concatenating it with a nonce, and encrypting the result

Not quite. CTR works by taking a value that is unique per block encrypted with the same key and encrypting it. How this value (the counter block) is constructed is not standardized, and all that matters for security is its uniqueness (and independence of the encryption key).

One way to use CTR mode is to keep a count of blocks (a number: 0, 1, 2, …) that are encrypted with that key, and use a representation of that number as the counter block: typically base 256 big-endian or base 256 little-endian, if you see the counter block as an array of 8-bit bytes. That works when there is a single entity using that key, it only ever encrypts one message at a time, and it remembers where it left off between blocks.

This simple approach breaks down if multiple entities want to encrypt, or if even one entity wants to encrypt multiple messages in parallel, since there's no practical way to both keep counter values consecutive within a single message (without that, you'd need to save all the counter values with the message and not just the initial value) and avoid repeating counter values (which would break security).

The best solution is to never encrypt two messages with the same key, and instead do two steps to encrypt a message: first derive a key from a per-message nonce, then use that single-use key to encrypt the message. You would then send the derivation nonce with the message, and you wouldn't need to send the initial counter value since you can simply always start at 0.

If you want to encrypt multiple messages with the same key, you need a way to have disjoints sets of counter values per message. A common way to do this is to break the counter block into two parts: a per-message nonce, and an in-message counter. This limits the possible length of messages to the number of blocks that can be represented with the in-message counter, for example a 32-bit in-message counter means a maximum of $2^32$ blocks (so $2^{36}$ bytes for ciphers such as AES with a 128-bit block size). The per-message nonce needs to be unique per message, for example a counter of messages sent.

Another common approach that avoids saving state (such as the number of messages sent) is to start each message with a random counter value and increment it throughout the message. This is fine as long as you don't send too much data with the same key, where "too much data" means you mustn't get close to the birthday bound, i.e. the square root of the number of possible blocks. For a 128-bit block size, this means don't get close to $2^{64}\,\text{blocks} = 2^{68}\,\text{bytes}$ of data.

Xoring a per-message nonce with the in-message counter may or may not work depending on how the nonce is generated. For example, if the nonce is a count of messages sent, it's horribly bad, because it leads to repeated counter block values. In base 2, and just representing the last three bits of the counter blocks, you'd have:

  • First message: first counter block $000 \oplus 000 = 000$, second counter block $000 \oplus 001 = 001$, …
  • Second message: first counter block $001 \oplus 000 = 001$, second counter block $001 \oplus 001 = 000$, …

As you can see, counter blocks are repeated, and that defeats the security completely.

If your nonce is a random 128-bit value, it's similar to the common method of starting with a random 128-bit value and incrementing it, but with an order of enumerating counter blocks that depends on the per-message initial counter value. I think this has the same statistical properties, but I haven't done the math. It complicates the implementation very slightly. I don't see any particular benefit.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.