Replying to myself:
The context struct and the set_key function is essential to be able to do any optimizations using key-dependant tables.
It actually gets a bit complicated. I think we need several context/state structs.
First, the key. The only way the key is directly used is for encrypting things using the underlying block cipher. So it makes sense to represent the key as
const void *cipher_ctx; nettle_crypt_func *encrypt;
(except that for historical reasons, the first argument for nettle_crypt_func is not const).
Next, we have tables derived from the key. These are needed for otpimized implementations of ghash, and should be reused when several messages are processed using the same key. E.g., this could be done as
struct gcm128_ctx { const void *cipher_ctx; nettle_crypt_func *encrypt;
uint8_t H[16]; /* Hash subkey */ uint8_t M0[16][256]; /* Key-dependent table. */ };
void gcm128_ctx_init(struct gcm128_ctx *ctx, const void *cipher_ctx, nettle_crypt_func *encrypt);
(It would be more consistent with the rest of nettle to not store those pointers in gcm128_ctx, since its supposed to be ok to memcpy context structs around. One could either pass them as arguments to all functions, or inline an aes_ctx if aes is all we care about. I include them here, to simplify function prototypes).
Functions to process complete gcm messages can take this ctx as argument. The main ghash iteration would also take this context struct as argument,
/* Set state = (state XOR data) dot H */ void ghash_update(const struct gcm128_ctx *ctx, uint8_t *state, const uint8_t *data);
For streaming operations, we also need a per-message state struct, something like
struct gcm128_msg { uint8_t Y[16]; /* counter */ uint8_t J[16]; /* encryption of that */ uint8_t J0[16]; /* first encrypted counter block to be used for constructing the digest. */ uint8_t hash[16]; /* hashing state */
unsigned index; /* Index when doing partial blocks */ uint32_t auth_length; uint32_t msg_length; };
void gcm128_msg_init(struct gcm128_msg *msg, const struct gcm128_ctx *ctx, uint32_t iv_length, const uint8_t *iv);
/* Process auxillary auth data */ void gcm128_msg_auth(struct gcm128_msg *msg, const struct gcm128_ctx *ctx, uint32_t length, const uint8_t *data);
/* Only difference between encrypt and decrypt is if data is hashed before or after xoring with the key stream. */ void gcm128_msg_encrypt(struct gcm128_msg *msg, const struct gcm128_ctx *ctx, uint32_t length, uint8_t *dst, const uint8_t *src);
void gcm128_msg_decrypt(struct gcm128_msg *msg, const struct gcm128_ctx *ctx, uint32_t length, uint8_t *dst, const uint8_t *src);
gcm128_msg_digest(struct gcm128_msg *msg, const struct gcm128_ctx *ctx, uint32_t length, uint8_t *dst);
Comments? It gets more complicated than almost anything currently in Nettle.
Regards, /Niels