crypton-1.1.4: cbits/crypton_chacha.c
/*
* Copyright (c) 2014-2015 Vincent Hanquez <vincent@snarc.org>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <stdint.h>
#include <string.h>
#include "crypton_chacha.h"
#include "crypton_bitfn.h"
#include "crypton_align.h"
#include <stdio.h>
#define QR(a,b,c,d) \
a += b; d = rol32(d ^ a,16); \
c += d; b = rol32(b ^ c,12); \
a += b; d = rol32(d ^ a, 8); \
c += d; b = rol32(b ^ c, 7);
#define ALIGNED64(PTR) \
(((uintptr_t)(const void *)(PTR)) % 8 == 0)
static const uint8_t sigma[16] = "expand 32-byte k";
static const uint8_t tau[16] = "expand 16-byte k";
static void chacha_core(int rounds, block *out, const crypton_chacha_state *in)
{
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
int i;
x0 = in->d[0]; x1 = in->d[1]; x2 = in->d[2]; x3 = in->d[3];
x4 = in->d[4]; x5 = in->d[5]; x6 = in->d[6]; x7 = in->d[7];
x8 = in->d[8]; x9 = in->d[9]; x10 = in->d[10]; x11 = in->d[11];
x12 = in->d[12]; x13 = in->d[13]; x14 = in->d[14]; x15 = in->d[15];
for (i = rounds; i > 0; i -= 2) {
QR(x0, x4, x8, x12);
QR(x1, x5, x9, x13);
QR(x2, x6, x10, x14);
QR(x3, x7, x11, x15);
QR(x0, x5, x10, x15);
QR(x1, x6, x11, x12);
QR(x2, x7, x8, x13);
QR(x3, x4, x9, x14);
}
x0 += in->d[0]; x1 += in->d[1]; x2 += in->d[2]; x3 += in->d[3];
x4 += in->d[4]; x5 += in->d[5]; x6 += in->d[6]; x7 += in->d[7];
x8 += in->d[8]; x9 += in->d[9]; x10 += in->d[10]; x11 += in->d[11];
x12 += in->d[12]; x13 += in->d[13]; x14 += in->d[14]; x15 += in->d[15];
out->d[0] = cpu_to_le32(x0);
out->d[1] = cpu_to_le32(x1);
out->d[2] = cpu_to_le32(x2);
out->d[3] = cpu_to_le32(x3);
out->d[4] = cpu_to_le32(x4);
out->d[5] = cpu_to_le32(x5);
out->d[6] = cpu_to_le32(x6);
out->d[7] = cpu_to_le32(x7);
out->d[8] = cpu_to_le32(x8);
out->d[9] = cpu_to_le32(x9);
out->d[10] = cpu_to_le32(x10);
out->d[11] = cpu_to_le32(x11);
out->d[12] = cpu_to_le32(x12);
out->d[13] = cpu_to_le32(x13);
out->d[14] = cpu_to_le32(x14);
out->d[15] = cpu_to_le32(x15);
}
static void hchacha_core(int rounds, uint32_t *out, const crypton_chacha_state *in)
{
uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15;
int i;
x0 = in->d[0]; x1 = in->d[1]; x2 = in->d[2]; x3 = in->d[3];
x4 = in->d[4]; x5 = in->d[5]; x6 = in->d[6]; x7 = in->d[7];
x8 = in->d[8]; x9 = in->d[9]; x10 = in->d[10]; x11 = in->d[11];
x12 = in->d[12]; x13 = in->d[13]; x14 = in->d[14]; x15 = in->d[15];
for (i = rounds; i > 0; i -= 2) {
QR(x0, x4, x8, x12);
QR(x1, x5, x9, x13);
QR(x2, x6, x10, x14);
QR(x3, x7, x11, x15);
QR(x0, x5, x10, x15);
QR(x1, x6, x11, x12);
QR(x2, x7, x8, x13);
QR(x3, x4, x9, x14);
}
/* HChaCha doesn't perform the final addition */
out[0] = cpu_to_le32(x0);
out[1] = cpu_to_le32(x1);
out[2] = cpu_to_le32(x2);
out[3] = cpu_to_le32(x3);
out[4] = cpu_to_le32(x12);
out[5] = cpu_to_le32(x13);
out[6] = cpu_to_le32(x14);
out[7] = cpu_to_le32(x15);
}
/* Common initialization logic for ChaCha20 and HChaCha20 */
static void chacha_init_key_state(crypton_chacha_state *st,
uint32_t keylen, const uint8_t *key)
{
const uint8_t *constants = (keylen == 32) ? sigma : tau;
ASSERT_ALIGNMENT(constants, 4);
st->d[0] = load_le32_aligned(constants + 0);
st->d[1] = load_le32_aligned(constants + 4);
st->d[2] = load_le32_aligned(constants + 8);
st->d[3] = load_le32_aligned(constants + 12);
st->d[4] = load_le32(key + 0);
st->d[5] = load_le32(key + 4);
st->d[6] = load_le32(key + 8);
st->d[7] = load_le32(key + 12);
/* we repeat the key on 128 bits */
if (keylen == 32)
key += 16;
st->d[8] = load_le32(key + 0);
st->d[9] = load_le32(key + 4);
st->d[10] = load_le32(key + 8);
st->d[11] = load_le32(key + 12);
}
/* only 2 valids values are 256 (32) and 128 (16) */
void crypton_chacha_init_core(crypton_chacha_state *st,
uint32_t keylen, const uint8_t *key,
uint32_t ivlen, const uint8_t *iv)
{
chacha_init_key_state(st, keylen, key);
st->d[12] = 0;
switch (ivlen) {
case 8:
st->d[13] = 0;
st->d[14] = load_le32(iv + 0);
st->d[15] = load_le32(iv + 4);
break;
case 12:
st->d[13] = load_le32(iv + 0);
st->d[14] = load_le32(iv + 4);
st->d[15] = load_le32(iv + 8);
default:
return;
}
}
void crypton_hchacha_init_core(crypton_chacha_state *st,
const uint8_t *key,
const uint8_t *iv)
{
/* keylen is always 32 here */
chacha_init_key_state(st, 32, key);
/* fill the last 4 uint32s with the 128-bit nonce */
st->d[12] = load_le32(iv + 0);
st->d[13] = load_le32(iv + 4);
st->d[14] = load_le32(iv + 8);
st->d[15] = load_le32(iv + 12);
}
void crypton_chacha_init(crypton_chacha_context *ctx, uint8_t nb_rounds,
uint32_t keylen, const uint8_t *key,
uint32_t ivlen, const uint8_t *iv)
{
memset(ctx, 0, sizeof(*ctx));
ctx->nb_rounds = nb_rounds;
crypton_chacha_init_core(&ctx->st, keylen, key, ivlen, iv);
}
void crypton_hchacha(uint8_t nb_rounds, const uint8_t *keyin, const uint8_t *iv,
uint8_t *keyout)
{
crypton_chacha_state st;
crypton_hchacha_init_core(&st, keyin, iv);
/* output to uint32_t* and do a memcpy to avoid
violating the strict aliasing rule */
uint32_t keyout32[8];
hchacha_core(nb_rounds, keyout32, &st);
memset(&st, 0, sizeof(st));
memcpy(keyout, keyout32, 32);
memset(keyout32, 0, 32);
}
/* XChaCha: 256-bit key, 192-bit nonce version of ChaCha.*/
void crypton_xchacha_init(crypton_chacha_context *ctx, uint8_t nb_rounds,
const uint8_t *key, const uint8_t *iv)
{
memset(ctx, 0, sizeof(*ctx));
ctx->nb_rounds = nb_rounds;
/* perform HChaCha with the key and the first 16 bytes of the nonce
to get the subkey */
uint8_t subkey[32];
crypton_hchacha(nb_rounds, key, iv, subkey);
/* perform regular ChaCha with the generated subkey and the last 8 bytes
of the input IV */
crypton_chacha_init_core(&ctx->st, 32, subkey, 8, iv + 16);
}
void crypton_chacha_combine(uint8_t *dst, crypton_chacha_context *ctx, const uint8_t *src, uint32_t bytes)
{
block out;
crypton_chacha_state *st;
int i;
if (!bytes)
return;
/* xor the previous buffer first (if any) */
if (ctx->prev_len > 0) {
int to_copy = (ctx->prev_len < bytes) ? ctx->prev_len : bytes;
for (i = 0; i < to_copy; i++)
dst[i] = src[i] ^ ctx->prev[ctx->prev_ofs+i];
memset(ctx->prev + ctx->prev_ofs, 0, to_copy);
ctx->prev_len -= to_copy;
ctx->prev_ofs += to_copy;
src += to_copy;
dst += to_copy;
bytes -= to_copy;
}
if (bytes == 0)
return;
st = &ctx->st;
/* xor new 64-bytes chunks and store the left over if any */
for (; bytes >= 64; bytes -= 64, src += 64, dst += 64) {
/* generate new chunk and update state */
chacha_core(ctx->nb_rounds, &out, st);
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
for (i = 0; i < 64; ++i)
dst[i] = src[i] ^ out.b[i];
}
if (bytes > 0) {
/* generate new chunk and update state */
chacha_core(ctx->nb_rounds, &out, st);
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
/* xor as much as needed */
for (i = 0; i < bytes; i++)
dst[i] = src[i] ^ out.b[i];
/* copy the left over in the buffer */
ctx->prev_len = 64 - bytes;
ctx->prev_ofs = i;
for (; i < 64; i++) {
ctx->prev[i] = out.b[i];
}
}
}
uint64_t crypton_chacha_counter64(crypton_chacha_state *st)
{
uint64_t result = ((uint64_t) le32_to_cpu(st->d[12]))
| (((uint64_t) le32_to_cpu(st->d[13])) << 32);
return result;
}
uint32_t crypton_chacha_counter32(crypton_chacha_state *st)
{
return le32_to_cpu(st->d[12]);
}
void crypton_chacha_set_counter64(crypton_chacha_state *st, uint64_t block_counter)
{
uint64_t current_counter;
current_counter = ((uint64_t) le32_to_cpu(st->d[12]))
| (((uint64_t) le32_to_cpu(st->d[13])) << 32);
if (current_counter == block_counter)
return;
st->d[12] = cpu_to_le32((uint32_t) block_counter);
st->d[13] = cpu_to_le32((uint32_t) (block_counter >> 32));
}
void crypton_chacha_set_counter32(crypton_chacha_state *st, uint32_t block_counter)
{
uint32_t current_counter = le32_to_cpu(st->d[12]);
if (current_counter == block_counter)
return;
st->d[12] = cpu_to_le32(block_counter);
}
void crypton_chacha_generate(uint8_t *dst, crypton_chacha_context *ctx, uint32_t bytes)
{
crypton_chacha_state *st;
block out;
int i;
if (!bytes)
return;
/* xor the previous buffer first (if any) */
if (ctx->prev_len > 0) {
int to_copy = (ctx->prev_len < bytes) ? ctx->prev_len : bytes;
for (i = 0; i < to_copy; i++)
dst[i] = ctx->prev[ctx->prev_ofs+i];
memset(ctx->prev + ctx->prev_ofs, 0, to_copy);
ctx->prev_len -= to_copy;
ctx->prev_ofs += to_copy;
dst += to_copy;
bytes -= to_copy;
}
if (bytes == 0)
return;
st = &ctx->st;
if (ALIGNED64(dst)) {
/* xor new 64-bytes chunks and store the left over if any */
for (; bytes >= 64; bytes -= 64, dst += 64) {
/* generate new chunk and update state */
chacha_core(ctx->nb_rounds, (block *) dst, st);
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
}
} else {
/* xor new 64-bytes chunks and store the left over if any */
for (; bytes >= 64; bytes -= 64, dst += 64) {
/* generate new chunk and update state */
chacha_core(ctx->nb_rounds, &out, st);
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
for (i = 0; i < 64; ++i)
dst[i] = out.b[i];
}
}
if (bytes > 0) {
/* generate new chunk and update state */
chacha_core(ctx->nb_rounds, &out, st);
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
/* xor as much as needed */
for (i = 0; i < bytes; i++)
dst[i] = out.b[i];
/* copy the left over in the buffer */
ctx->prev_len = 64 - bytes;
ctx->prev_ofs = i;
for (; i < 64; i++)
ctx->prev[i] = out.b[i];
}
}
void crypton_chacha_generate_simple_block(uint8_t *dst, crypton_chacha_state *st, uint8_t rounds)
{
if (ALIGNED64(dst)) {
chacha_core(rounds, (block *) dst, st);
} else {
block out;
int i;
chacha_core(rounds, &out, st);
for (i = 0; i < 64; ++i) {
dst[i] = out.b[i];
}
}
uint32_t t0 = le32_to_cpu(st->d[12]);
st->d[12] = cpu_to_le32(t0 + 1);
if (st->d[12] == 0) {
uint32_t t1 = le32_to_cpu(st->d[13]);
st->d[13] = cpu_to_le32(t1 + 1);
}
}
void crypton_chacha_random(uint32_t rounds, uint8_t *dst, crypton_chacha_state *st, uint32_t bytes)
{
block out;
if (!bytes)
return;
for (; bytes >= 16; bytes -= 16, dst += 16) {
chacha_core(rounds, &out, st);
memcpy(dst, out.b + 40, 16);
crypton_chacha_init_core(st, 32, out.b, 8, out.b + 32);
}
if (bytes) {
chacha_core(rounds, &out, st);
memcpy(dst, out.b + 40, bytes);
crypton_chacha_init_core(st, 32, out.b, 8, out.b + 32);
}
}