blob: 3d244d4e0fb8b79bf597535a232acd132407caae [file] [log] [blame]
/*
* cifra - embedded cryptography library
* Written in 2016 by Joseph Birr-Pixton <jpixton@gmail.com>
*
* To the extent possible under law, the author(s) have dedicated all
* copyright and related and neighboring rights to this software to the
* public domain worldwide. This software is distributed without any
* warranty.
*
* You should have received a copy of the CC0 Public Domain Dedication
* along with this software. If not, see
* <http://creativecommons.org/publicdomain/zero/1.0/>.
*/
#include "handy.h"
#include "prp.h"
#include "modes.h"
#include "blockwise.h"
#include "bitops.h"
#include "gf128.h"
#include "tassert.h"
#include <string.h>
/* How many L_n values to compute at schedule time. */
#define MAX_L 4
/* We and RFC7253 assume 128-bit blocks. */
#define BLOCK 16
typedef struct
{
const cf_prp *prp;
void *prpctx; /* Our PRP */
uint8_t *out; /* Output pointer for block processing */
cf_gf128 L_star; /* Zero block ciphertext */
cf_gf128 L_dollar; /* L_$ is double of L_* */
cf_gf128 L[MAX_L]; /* L[0] is double of L_$, L[1] is double of L[0], etc. */
cf_gf128 offset; /* Offset_i */
cf_gf128 checksum; /* Checksum_i */
uint32_t i; /* Block index, 1-based */
} ocb;
typedef struct
{
ocb *o; /* OCB context (contains PRP, etc.) */
cf_gf128 sum; /* Current Sum_i */
cf_gf128 offset; /* Current Offset_i */
uint32_t i; /* Block index, 1-based */
} ocb_hash;
static void ocb_init(ocb *o, const cf_prp *prp, void *prpctx,
const uint8_t *nonce, size_t nnonce,
size_t ntag)
{
o->prp = prp;
o->prpctx = prpctx;
assert(o->prp->blocksz == BLOCK);
/* L_* = ENCIPHER(K, zeros(128)) */
uint8_t L_star_bytes[BLOCK] = { 0 };
prp->encrypt(prpctx, L_star_bytes, L_star_bytes);
cf_gf128_frombytes_be(L_star_bytes, o->L_star);
/* L_$ = double(L_*) */
cf_gf128_double(o->L_star, o->L_dollar);
/* L_0 = double(L_$) etc. */
cf_gf128_double(o->L_dollar, o->L[0]);
for (int i = 1; i < MAX_L; i++)
cf_gf128_double(o->L[i - 1], o->L[i]);
/* Compute nonce-dependent and per-encryption vars */
assert(nnonce > 0 && nnonce < BLOCK);
uint8_t full_nonce[BLOCK] = { 0 };
full_nonce[0] = ((ntag * 8) & 0x7f) << 1;
full_nonce[BLOCK - 1 - nnonce] |= 0x01;
memcpy(full_nonce + BLOCK - nnonce, nonce, nnonce);
uint8_t bottom = full_nonce[BLOCK - 1] & 0x3f;
/* Make Ktop */
full_nonce[BLOCK - 1] &= 0xc0;
uint8_t Ktop[BLOCK + 8];
prp->encrypt(prpctx, full_nonce, Ktop);
/* Stretch Ktop */
for (int i = 0; i < 8; i++)
Ktop[i + BLOCK] = Ktop[i] ^ Ktop[i + 1];
/* Outputs */
uint8_t offset[BLOCK];
copy_bytes_unaligned(offset, Ktop, BLOCK, bottom);
cf_gf128_frombytes_be(offset, o->offset);
memset(o->checksum, 0, sizeof o->checksum);
}
static void ocb_start_cipher(ocb *o, uint8_t *output)
{
o->i = 1;
o->out = output;
}
static void ocb_add_Ln(ocb *o, uint32_t n, cf_gf128 out)
{
/* Do we have a precomputed L term? */
if (n < MAX_L)
{
cf_gf128_add(o->L[n], out, out);
return;
}
/* Compute more terms of L. */
cf_gf128 accum;
memcpy(accum, o->L[MAX_L - 1], sizeof accum);
for (uint32_t i = MAX_L - 1; i < n; i++)
{
cf_gf128 next;
cf_gf128_double(accum, next);
memcpy(accum, next, sizeof accum);
}
cf_gf128_add(accum, out, out);
}
static void ocb_hash_init(ocb_hash *h)
{
memset(h->offset, 0, sizeof h->offset);
memset(h->sum, 0, sizeof h->sum);
h->i = 1;
}
static void ocb_hash_sum(ocb *o, const uint8_t *block,
cf_gf128 sum, const cf_gf128 offset)
{
uint8_t offset_bytes[BLOCK];
cf_gf128_tobytes_be(offset, offset_bytes);
uint8_t block_tmp[BLOCK];
xor_bb(block_tmp, block, offset_bytes, sizeof block_tmp);
o->prp->encrypt(o->prpctx, block_tmp, block_tmp);
cf_gf128 tmp;
cf_gf128_frombytes_be(block_tmp, tmp);
cf_gf128_add(sum, tmp, sum);
}
static void ocb_hash_block(void *vctx, const uint8_t *block)
{
ocb_hash *h = vctx;
/* Offset_i = Offset_{i - 1} xor L{ntz(i)} */
ocb_add_Ln(h->o, count_trailing_zeroes(h->i), h->offset);
/* Sum_i = Sum_{i - 1} xor ENCIPHER(K, A_i xor Offset_i) */
ocb_hash_sum(h->o, block, h->sum, h->offset);
h->i++;
}
static void ocb_process_header(ocb *o, const uint8_t *header, size_t nheader,
uint8_t out[BLOCK])
{
ocb_hash ctx = { o };
ocb_hash_init(&ctx);
uint8_t partial[BLOCK];
size_t npartial = 0;
cf_blockwise_accumulate(partial, &npartial,
o->prp->blocksz,
header, nheader,
ocb_hash_block,
&ctx);
if (npartial)
{
/* Offset_* = Offset_m xor L_* */
cf_gf128_add(ctx.offset, o->L_star, ctx.offset);
/* CipherInput = (A_* || 1 || zeros(127 - bitlen(A_*))) xor Offset_* */
memset(partial + npartial, 0, sizeof(partial) - npartial);
partial[npartial] = 0x80;
/* Sum = Sum_m xor ENCIPHER(K, CipherInput) */
ocb_hash_sum(ctx.o, partial, ctx.sum, ctx.offset);
}
cf_gf128_tobytes_be(ctx.sum, out);
mem_clean(&ctx, sizeof ctx);
}
static void ocb_encrypt_block(void *vctx, const uint8_t *block)
{
ocb *o = vctx;
/* Offset_i = Offset_{i - 1} xor L{ntz(i)} */
ocb_add_Ln(o, count_trailing_zeroes(o->i), o->offset);
/* C_i = Offset_i xor ENCIPHER(K, P_i xor Offset_i) */
uint8_t offset_bytes[BLOCK];
cf_gf128_tobytes_be(o->offset, offset_bytes);
uint8_t block_tmp[BLOCK];
xor_bb(block_tmp, block, offset_bytes, sizeof block_tmp);
o->prp->encrypt(o->prpctx, block_tmp, block_tmp);
xor_bb(o->out, block_tmp, offset_bytes, sizeof block_tmp);
o->out += sizeof block_tmp;
/* Checksum_i = Checksum_{i - 1} xor P_i */
cf_gf128 P;
cf_gf128_frombytes_be(block, P);
cf_gf128_add(o->checksum, P, o->checksum);
o->i++;
}
void cf_ocb_encrypt(const cf_prp *prp, void *prpctx,
const uint8_t *plain, size_t nplain,
const uint8_t *header, size_t nheader,
const uint8_t *nonce, size_t nnonce,
uint8_t *cipher, /* the same size as nplain */
uint8_t *tag, size_t ntag)
{
ocb o;
ocb_init(&o, prp, prpctx, nonce, nnonce, ntag);
/* Process blocks. The blockwise machinery takes care of
* splitting the input into 128-bit blocks, and calling
* a function on each one. */
uint8_t partial[BLOCK];
size_t npartial = 0;
ocb_start_cipher(&o, cipher);
cf_blockwise_accumulate(partial, &npartial,
prp->blocksz,
plain, nplain,
ocb_encrypt_block,
&o);
/* Move along plain and cipher. */
plain += (o.out - cipher);
cipher = o.out;
/* If we have remaining data to pad and process,
* it's in partial. */
if (npartial)
{
/* Offset_* = Offset_m xor L_* */
cf_gf128_add(o.offset, o.L_star, o.offset);
/* Pad = ENCIPHER(K, Offset_*) */
uint8_t pad[BLOCK];
cf_gf128_tobytes_be(o.offset, pad);
o.prp->encrypt(o.prpctx, pad, pad);
/* C_* = P_* xor Pad[1..bitlen(P_*)] */
xor_bb(cipher, partial, pad, npartial);
mem_clean(pad, sizeof pad);
/* Checksum_* = Checksum_m xor (P_* || 1 || zeros(127 - bitlen(P_*))) */
memset(partial + npartial, 0, sizeof(partial) - npartial);
partial[npartial] = 0x80;
cf_gf128 last_block;
cf_gf128_frombytes_be(partial, last_block);
cf_gf128_add(o.checksum, last_block, o.checksum);
mem_clean(last_block, sizeof last_block);
}
/* Compute: Tag = ENCIPHER(K, Checksum_m xor Offset_m xor L_$) xor HASH(K, A) */
cf_gf128 full_tag;
for (size_t i = 0; i < 4; i++)
full_tag[i] = o.checksum[i] ^ o.offset[i] ^ o.L_dollar[i];
/* Convert tag to bytes for encryption */
uint8_t tag_bytes[BLOCK];
cf_gf128_tobytes_be(full_tag, tag_bytes);
/* ENCIPHER(...) */
o.prp->encrypt(o.prpctx, tag_bytes, tag_bytes);
/* Compute HASH(K, A). */
uint8_t hash_a[BLOCK];
ocb_process_header(&o, header, nheader, hash_a);
/* ... xor HASH(K, A) */
xor_bb(tag_bytes, tag_bytes, hash_a, sizeof tag_bytes);
/* Copy out tag to caller. */
memcpy(tag, tag_bytes, ntag);
mem_clean(&o, sizeof o);
}
static void ocb_decrypt_block(void *vctx, const uint8_t *block)
{
ocb *o = vctx;
/* Offset_i = Offset_{i - 1} xor L{ntz(i)} */
ocb_add_Ln(o, count_trailing_zeroes(o->i), o->offset);
/* P_i = Offset_i xor DECIPHER(K, C_i xor Offset_i) */
uint8_t offset_bytes[BLOCK];
cf_gf128_tobytes_be(o->offset, offset_bytes);
uint8_t block_tmp[BLOCK];
xor_bb(block_tmp, block, offset_bytes, sizeof block_tmp);
o->prp->decrypt(o->prpctx, block_tmp, block_tmp);
xor_bb(o->out, block_tmp, offset_bytes, sizeof block_tmp);
/* Checksum_i = Checksum_{i - 1} xor P_i */
cf_gf128 P;
cf_gf128_frombytes_be(o->out, P);
o->out += sizeof block_tmp;
cf_gf128_add(o->checksum, P, o->checksum);
o->i++;
}
int cf_ocb_decrypt(const cf_prp *prp, void *prpctx,
const uint8_t *cipher, size_t ncipher,
const uint8_t *header, size_t nheader,
const uint8_t *nonce, size_t nnonce,
const uint8_t *tag, size_t ntag,
uint8_t *plain)
{
ocb o;
ocb_init(&o, prp, prpctx, nonce, nnonce, ntag);
/* Do blockwise decryption */
uint8_t partial[BLOCK];
size_t npartial = 0;
ocb_start_cipher(&o, plain);
cf_blockwise_accumulate(partial, &npartial,
prp->blocksz,
cipher, ncipher,
ocb_decrypt_block,
&o);
if (npartial)
{
/* Offset_* = Offset_m xor L_* */
cf_gf128_add(o.offset, o.L_star, o.offset);
/* Pad = ENCIPHER(K, Offset_*) */
uint8_t pad[BLOCK];
cf_gf128_tobytes_be(o.offset, pad);
o.prp->encrypt(o.prpctx, pad, pad);
/* P_* = C_* xor Pad[1..bitlen(C_*)] */
xor_bb(partial, partial, pad, npartial);
mem_clean(pad, sizeof pad);
memcpy(o.out, partial, npartial);
/* Checksum_* = Checksum_m xor (P_* || 1 || zeros(127 - bitlen(P_*))) */
memset(partial + npartial, 0, sizeof(partial) - npartial);
partial[npartial] = 0x80;
cf_gf128 last_block;
cf_gf128_frombytes_be(partial, last_block);
cf_gf128_add(o.checksum, last_block, o.checksum);
mem_clean(last_block, sizeof last_block);
}
/* Compute: Tag = ENCIPHER(K, Checksum_m xor Offset_m xor L_$) xor HASH(K, A) */
cf_gf128 full_tag;
for (size_t i = 0; i < 4; i++)
full_tag[i] = o.checksum[i] ^ o.offset[i] ^ o.L_dollar[i];
/* Convert tag to bytes for encryption */
uint8_t tag_bytes[BLOCK];
cf_gf128_tobytes_be(full_tag, tag_bytes);
/* ENCIPHER(...) */
o.prp->encrypt(o.prpctx, tag_bytes, tag_bytes);
/* Compute HASH(K, A). */
uint8_t hash_a[BLOCK];
ocb_process_header(&o, header, nheader, hash_a);
/* ... xor HASH(K, A) */
xor_bb(tag_bytes, tag_bytes, hash_a, sizeof tag_bytes);
/* Check against caller's tag. */
int err;
if (mem_eq(tag, tag_bytes, ntag))
{
err = 0;
} else {
err = 1;
mem_clean(plain, ncipher);
}
mem_clean(&o, sizeof o);
mem_clean(tag_bytes, sizeof tag_bytes);
mem_clean(full_tag, sizeof full_tag);
return err;
}