ARM-software / psa-api

Documentation source and development of the PSA Certified API
https://arm-software.github.io/psa-api/
Other
58 stars 28 forks source link

crypto: problematic specification of IV for PSA_ALG_CTR #181

Closed gilles-peskine-arm closed 8 months ago

gilles-peskine-arm commented 8 months ago

The specification of PSA_ALG_CTR has inconsistent provisions regarding the counter block.

CTR mode does not have a single specification. It is really a family of modes that arrange for the counter value uniqueness in different ways.

During the counter block update operation, the counter block is treated as a single big-endian encoded integer and the update operation increments this integer by 1.

This is one way to do it, and it's what Mbed TLS does.

A counter block value must only be used once across all messages encrypted using the same key value. This is typically achieved by splitting the counter block into a nonce, which is unique among all message encrypted with the key, and a counter which is incremented for each block of a message.

This is not how it works: it's only compatible with the paragraph cited above if the none part is 0 bytes, but then the nonce part is not a nonce since reuse is allowed (as long as the resulting counter value is unique across blocks).

For example, when using AES-CTR encryption, which uses a 16-byte block, the application can provide a 12-byte nonce when setting the IV. This leaves 4 bytes for the counter, allowing up to 2^32 blocks (64GB) of message data to be encrypted in each message.

This example does not comply to the specification above. Note that the length passed to set_iv is irrelevant to the internal operation of the cipher.

Also:

A call to psa_cipher_set_iv() on a multi-part cipher operation requires an IV that is between 1 and n bytes in length, where n is the cipher block length. The counter block is initialized using the IV, and padded with zero bytes up to the block length.

We might as well allow a 0-byte IV to start the counter at 0.

athoelke commented 8 months ago

Note that the link address to the specification text is incorrect it should be https://arm-software.github.io/psa-api/crypto/1.1/api/ops/ciphers.html#c.PSA_ALG_CTR

athoelke commented 8 months ago

CTR mode does not have a single specification. It is really a family of modes that arrange for the counter value uniqueness in different ways.

During the counter block update operation, the counter block is treated as a single big-endian encoded integer and the update operation increments this integer by 1.

This is one way to do it, and it's what Mbed TLS does.

The Crypto API must precisely define the way to update the counter block for the PSA_ALG_CTR algorithm, in order to ensure that data encrypted using this algorithm can be exchanged with other implementations.

Counter block update could be done differently. That would require defining a similar, but incompatible, algorithm identifier, or providing a mechanism to parameterise the CTR algorithm. These can be added to the API if needed.

A counter block value must only be used once across all messages encrypted using the same key value. This is typically achieved by splitting the counter block into a nonce, which is unique among all message encrypted with the key, and a counter which is incremented for each block of a message.

This is not how it works: it's only compatible with the paragraph cited above if the none part is 0 bytes, but then the nonce part is not a nonce since reuse is allowed (as long as the resulting counter value is unique across blocks).

This is a paraphrasing of §Appendix B in SP800-38A.

If the application ensures that it does not exceed the maximum message size for the nonce size that it uses, then this is compatible. For a block cipher with block size b bytes, and n < b bytes of nonce, the maximum message size is 28(b - n) blocks.

For example, when using AES-CTR encryption, which uses a 16-byte block, the application can provide a 12-byte nonce when setting the IV. This leaves 4 bytes for the counter, allowing up to 2^32 blocks (64GB) of message data to be encrypted in each message.

This example does not comply to the specification above. Note that the length passed to set_iv is irrelevant to the internal operation of the cipher.

We could specify that the implementation must enforce that the counter update does not affect any of the bytes that were provided in the calls to psa_cipher_set_iv(). This could be a hard stop if the message limit is exceeded, or just cause the counter to wrap (as per §Appendix B5.1). However, this is then incompatible with applications that want to start the counter blocks with a nonce and a counter starting at the value 1: [nonce]12 || [1]4.

Note that such a change would not change the value of the counter blocks or ciphertext, other than when the application exceeds the message length limit.

Alternatively, we could add an API for the application to set the size of the counter element within the counter block. This would fix both of the issues with attempting to achieve this without changing the API.

Also:

We might as well allow a 0-byte IV to start the counter at 0.

Agreed. Also, the spec does not say that an IV must be set, or what happens if it is not set. However, this is a very bad idea, as theoretically limits the key to a single safe use. Perhaps we need a warning about the use of very short nonces/IVs here?

athoelke commented 8 months ago

It might be best to rework the text from the perspective of the application developer, and provide the information about the IV behavior is response to use cases:

athoelke commented 8 months ago

Also: We might as well allow a 0-byte IV to start the counter at 0.

Agreed. Also, the spec does not say that an IV must be set, or what happens if it is not set. However, this is a very bad idea, as theoretically limits the key to a single safe use. Perhaps we need a warning about the use of very short nonces/IVs here?

Do we really want to relax the IV length to permit zero bytes? - given the fairly catastrophic effect this has on the security of the key?

gilles-peskine-arm commented 8 months ago

When a key is single-use, an all-bits-zero IV is as good as any, and it can be conveyed as `{iv=NULL; iv_length=0;}.

On the other hand, passing an empty IV is a plausible mistake in the case of a multiple-use key. (“I don't know what to put here. Does NULL work? Yes, sweet.”) So I suppose we should keep rejecting that. If you do want an all-bits-zero IV for a single-use key, you have to write it explicitly like {IV=""; iv_length=1} and that is a more explicit decision.

So let's keep rejecting a 0-length IV.

athoelke commented 8 months ago

On the other hand, passing an empty IV is a plausible mistake in the case of a multiple-use key. (“I don't know what to put here. Does NULL work? Yes, sweet.”) So I suppose we should keep rejecting that. If you do want an all-bits-zero IV for a single-use key, you have to write it explicitly like {IV=""; iv_length=1} and that is a more explicit decision.

That's a good argument for requiring the application to explicitly select a constant (zero) IV, rather than accidently do so.