On Sat, Mar 15, 2014 at 12:07 AM, Niels Möller nisse@lysator.liu.se wrote:
--- /dev/null +++ b/ccm.c +/* Pad an unaligned CBC-MAC digest with zero, or initialize B0 if no adata. */ +static void +ccm_pad(struct ccm_ctx *ctx, void *cipher, nettle_crypt_func *f) +{
- if (ctx->blen) f(cipher, CCM_BLOCK_SIZE, ctx->tag.b, ctx->tag.b);
- ctx->blen = 0;
+}
Can you explain what this is intended for? It's called unconditionally by ccm_encrypt and ccm_decrypt, and I imagine it's a nop for all calls but the first (otherwise, ccm_encrypt (..., 16, msg...); ccm_encrypt (..., 16, msg+16...); would seem to give different output than ccm_encrypt(...32, msg))?
It is intended that _update and encrypt_/_decrypt can be called multiple times, as long as the total length equals the corresponding value passed to _set_nonce, right?
The Input to the CBC-MAC used in CCM mode takes the form: L(adata) | adata | padding | plaintext | padding
Where L(adata) is a variable length transformation that encodes the length of adata, and padding is the minimum number of 0-bits necessary to align the input to a multiple of the block size (note that input to the CBC-MAC is just an XOR with the previous block, so padding becomes a NOP)
Because ccm_update() might be called multiple times, with data that doesn't necessarily leave the input aligned to the block size, blen is used to count the number of bytes that were already added to block, but haven't been processed by another iteration of the CBC-MAC. If the last call to left the input data aligned to the block size, then blen will be zero. In other words, blen is the length of data input to the CBC-MAC modulus the block size.
Once we find the end of the adata (this is currently done on the first call to ccm_encrypt() or ccm_decrypt()), we need to insert the padding before the plaintext, which is what ccm_pad() does. Then, because the CTR functions must always in a multiple of the block size, blen should always be zero except for the last block of the message.
+void +ccm_update(struct ccm_ctx *ctx, void *cipher, nettle_crypt_func *f,
size_t length, const uint8_t *data)
+{
- const uint8_t *end = data + length;
- /* nop */
- if (!length) return;
- /* On the first call, encrypt B0 and encode L(a) */
- if (ctx->blen < 0) {
- if (!ctx->alen) ctx->alen = length; /* If alen unknown, set it now. */
- ctx->tag.b[CCM_OFFSET_FLAGS] |= CCM_FLAG_ADATA;
Why the special handling of zero ctx->alen here? As far as I can see, this can happen only if ccm_set_nonce is called with alength = 0, and then nevertheless calls ccm_update with length > 0, which seems like an invalid use of the api.
Since the IV only contains a single bit that indicates whether adata exists at all, in theory the alength parameter could be omitted from set_nonce() entirely and then handled in ccm_update(), which is what this check is doing.
This is more or less an artifact of an attempt to support the AEAD API. Note that if you call ccm_set_nonce() with alength==0, and then pass the entire adata to ccm_update(), you would still get the correct authentication tag and ciphertext because of this special check on alen.
- f(cipher, CCM_BLOCK_SIZE, ctx->tag.b, ctx->tag.b);
- ctx->blen = 0;
- if (ctx->alen >= (0x01ULL << 32)) {
/* Encode L(a) as 0xff || 0xff || <64-bit integer> */
ctx->tag.b[ctx->blen++] ^= 0xff;
ctx->tag.b[ctx->blen++] ^= 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 56) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 48) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 40) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 32) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 24) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 16) & 0xff;
- }
- else if (ctx->alen >= ((0x1ULL << 16) - (0x1ULL << 8))) {
/* Encode L(a) as 0xff || 0xfe || <32-bit integer> */
ctx->tag.b[ctx->blen++] ^= 0xff;
ctx->tag.b[ctx->blen++] ^= 0xfe;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 24) & 0xff;
ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 16) & 0xff;
- }
- ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 8) & 0xff;
- ctx->tag.b[ctx->blen++] ^= (ctx->alen >> 0) & 0xff;
- }
Is it possible to move this initial processing to ccm_set_nonce? The alength *is* known there, and that would let you eliminate the ctx->blen < 0 cases, and maybe you could eliminate the alen state variable too (or keep it for sanity checking only).
Yes, that could be done, but this operation requires the use of the cipher encrypt function, which would need to be added to the set_nonce() API.
+void +ccm_digest(struct ccm_ctx *ctx, void *cipher, nettle_crypt_func *f,
size_t length, uint8_t *digest)
+{
- int i = CCM_BLOCK_SIZE - CCM_FLAG_GET_L(ctx->ctr.b[CCM_OFFSET_FLAGS]);
Maybe it would be nicer to stick to a one-way encoding of the ctr block (done by ccm_build_iv, if I understand correctly), and never decode that format? One would need to store the needed information in some simpler "uncoded" way in the ctx. That's a judgment call, adding redundant info to the context is also a bit ugly.
Yes, this was a bit of a judgement call, whether it was uglier to store the nonce length in the ctx, or to parse it out of the flags byte of the counter. There are other possible ways to do this, but they all require extra storage in the ctx, which I was hoping to avoid.
+void +ccm_encrypt_message(void *cipher, nettle_crypt_func *f,
size_t nlength, const uint8_t *nonce,
size_t alength, const uint8_t *adata,
size_t tlength, uint8_t *tag,
size_t mlength, uint8_t *dst, const uint8_t *src)
+{
- struct ccm_ctx ctx;
- ccm_set_nonce(&ctx, nlength, nonce, alength, mlength, tlength);
- ccm_update(&ctx, cipher, f, alength, adata);
- ccm_encrypt(&ctx, cipher, f, mlength, dst, src);
- ccm_digest(&ctx, cipher, f, tlength, tag);
+}
I think this function should append the tag to the ciphertext (assuming ccm is specified as an aead with a single output string?).
CCM is indeed specified as an AEAD function with a single output string, for example, RFC 5166 specifies that the ciphertext is always exactly 16 bytes longer than the plaintext, so this might be a good way to simplify the function call API.
+void +ccm_decrypt_message(void *cipher, nettle_crypt_func *f,
size_t nlength, const uint8_t *nonce,
size_t alength, const uint8_t *adata,
size_t tlength, uint8_t *tag,
size_t mlength, uint8_t *dst, const uint8_t *src)
+{
- struct ccm_ctx ctx;
- ccm_set_nonce(&ctx, nlength, nonce, alength, mlength, tlength);
- ccm_update(&ctx, cipher, f, alength, adata);
- ccm_decrypt(&ctx, cipher, f, mlength, dst, src);
- ccm_digest(&ctx, cipher, f, tlength, tag);
+}
I think this function should have a tag *input* (possibly reading it at te end of the cryptotext), compare it to the tag it computes, and return 1 on success, 0 for failure.
If the tag is appended to the cryptotext, I'm not sure if the mlength should be the message length with or without the tag (i.e., the length of the clear text message, or of the encrypted and authenticated message).
In the language of the CCM publications, mlen is always the length of the plaintext message. clen would be the length of the ciphertext, and can always be determined by: clen = mlen + tlen
Maybe it's best to let it be the length of the clear text message, same as passed to ccm_encrypt_message. In any case, the caller must be aware of both lengths.
Generally, I would prefer that the length provided should be the length of data that actually gets written (ie: ciphertext in this case). If a naive programmer using this API just passes buffer size as a sizeof() they might miss that the encrypt function would write passed the end of the buffer and cause an overflow.
One *could* pass both message length, and derive the tag length as the difference. But I'm afraid that style won't generalize nicely to other aead algorithms.
I didn't see any similar functions for the EAX or CGM modes, so I'm not sure that we really need to be concerned with breaking a precedent here.
diff --git a/ccm.h b/ccm.h new file mode 100644 index 0000000..76b4cc4 +/* Per-message state */ +struct ccm_ctx {
- union nettle_block16 ctr; /* Counter for CTR encryption. */
- union nettle_block16 tag; /* CBC-MAC message tag. */
- uint64_t alen; /* Length of adata set during the nonce generation. */
- int blen; /* <0 on init or # of bytes since the last aligned block */
+};
In nettle, length is usually not abbreviated to "len". If negative blen values can be eliminated, it should be changed to unsigned.
I'll change the names accordingly with the next patch.
The negative value of blen is currently only used as a special value to flag the first time that ccm_update() gets called so that we can encrypt the IV to the CBC-MAC (B0) and generate the L(a). These operations could be moved into ccm_set_nonce(), provided that ccm_set_nonce() is also provided with the cipher encrypt function.
--- /dev/null +++ b/testsuite/ccm-test.c
- /* Ensure we get the same answers using the all-in-one API. */
- if (repeat <= 1) {
- memset(en_data, 0, len); memset(de_data, 0, len);
- memset(en_digest, 0, tlen); memset(de_digest, 0, tlen);
- ccm_encrypt_message(ctx, cipher->encrypt, nonce->length, nonce->data,
authdata->length, authdata->data, tlen, en_digest, len, en_data, cleartext->data);
- ccm_decrypt_message(ctx, cipher->encrypt, nonce->length, nonce->data,
authdata->length, authdata->data, tlen, de_digest, len, de_data, ciphertext->data);
If the all-in-one interface is changed as suggested above, adding a return value for ccm_decrypt_message, one should also check that ccm_decrypt_message returns 1 for the correct data, and 0 if any of message, adata or or tag is corrupted.
Of course, I'll make sure that any changes to the API will be tested in the ccm-test program.
Cheers, Owen