/* Spook Reference Implementation v1
*
* Written in 2019 at UCLouvain (Belgium) by Olivier Bronchain, Gaetan Cassiers
* and Charles Momin.
* 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 .
*/
#include
#include
#include
#include "s1p.h"
#include "parameters.h"
#include "primitives.h"
#include "utils.h"
#define CAPACITY_BYTES 32
#define RATE_BYTES (SHADOW_NBYTES - CAPACITY_BYTES)
// Working mode for block compression.
typedef enum {
AD,
PLAINTEXT,
CIPHERTEXT
} compress_mode;
static void compress_block(unsigned char *state, unsigned char *out,
const unsigned char *d, compress_mode mode,
unsigned long long offset, unsigned long long n);
static unsigned long long compress_data(unsigned char *state,
unsigned char *out,
const unsigned char *d,
unsigned long long dlen,
compress_mode mode);
static void init_sponge_state(unsigned char state[SHADOW_NBYTES],
const unsigned char *k, const unsigned char *p,
const unsigned char *n);
void init_keys(const unsigned char **k, unsigned char p[P_NBYTES],
const unsigned char *k_glob) {
*k = k_glob;
#if MULTI_USER
memcpy(p, k_glob + CLYDE128_NBYTES, P_NBYTES);
p[P_NBYTES - 1] &= 0x7F; // set last p bit to 0
p[P_NBYTES - 1] |= 0x40; // set next to last p bit to 0
#else
memset(p, 0, P_NBYTES);
#endif // MULTI_USER
}
static void init_sponge_state(unsigned char state[SHADOW_NBYTES],
const unsigned char *k, const unsigned char *p,
const unsigned char *n) {
// init state
memset(state, 0, SHADOW_NBYTES);
memcpy(state + CLYDE128_NBYTES, p, P_NBYTES);
memcpy(state + CLYDE128_NBYTES + P_NBYTES, n, CRYPTO_NPUBBYTES);
// TBC
unsigned char padded_nonce[CLYDE128_NBYTES] = { 0 };
memcpy(padded_nonce, n, CRYPTO_NPUBBYTES);
unsigned char *b = state;
clyde128_encrypt(b, padded_nonce, p, k);
// initial permutation
shadow(state);
}
void s1p_encrypt(unsigned char *c, unsigned long long *clen,
const unsigned char *ad, unsigned long long adlen,
const unsigned char *m, unsigned long long mlen,
const unsigned char *k, const unsigned char *p,
const unsigned char *n) {
// permutation state
unsigned char state[SHADOW_NBYTES];
init_sponge_state(state, k, p, n);
// compress associated data
compress_data(state, NULL, ad, adlen, AD);
// compress message
unsigned long long c_bytes = 0;
if (mlen > 0) {
state[RATE_BYTES] ^= 0x01;
c_bytes = compress_data(state, c, m, mlen, PLAINTEXT);
}
// tag
state[CLYDE128_NBYTES + CLYDE128_NBYTES - 1] |= 0x80;
clyde128_encrypt(c + c_bytes, state, state + CLYDE128_NBYTES, k);
*clen = c_bytes + CLYDE128_NBYTES;
}
int s1p_decrypt(unsigned char *m, unsigned long long *mlen,
const unsigned char *ad, unsigned long long adlen,
const unsigned char *c, unsigned long long clen,
const unsigned char *k, const unsigned char *p,
const unsigned char *n) {
// permutation state
unsigned char state[SHADOW_NBYTES];
init_sponge_state(state, k, p, n);
// compress associated data
compress_data(state, NULL, ad, adlen, AD);
// compress message
unsigned long long m_bytes = 0;
if (clen > CLYDE128_NBYTES) {
state[RATE_BYTES] ^= 0x01;
m_bytes = compress_data(state, m, c, clen - CLYDE128_NBYTES, CIPHERTEXT);
}
// NOTE: We use here so-called "inverse-based tag verification",
// that is, we apply the inverse Clyde TBC to the tag extracted from
// the ciphertext and check the result against the output of Shadow.
// This way of verifying the tag is interesting in some specific cases
// such as side-channel protected implementations.
// In general implementations, the tag verification SHOULD be done in a
// more conventional way: encrypting the output of Shadow with Clyde and
// verifying the tag against the resulting value.
// This is more efficient as it does not require to implement the inverse
// Clyde.
// For more details, see the Spook specification at https://spook.dev.
unsigned char inv_tag[CLYDE128_NBYTES];
unsigned char *u = state;
state[(2 * CLYDE128_NBYTES) - 1] |= 0x80;
clyde128_decrypt(inv_tag, c + m_bytes, state + CLYDE128_NBYTES, k);
int tag_ok = 1;
for (int i = 0; i < CLYDE128_NBYTES; i++) {
tag_ok &= (u[i] == inv_tag[i]);
}
if (tag_ok) {
*mlen = m_bytes;
return 0;
} else {
// Reset output buffer to avoid unintended unauthenticated plaintext
// release.
memset(m, 0, clen - CLYDE128_NBYTES);
*mlen = 0;
return -1;
}
}
// Compress a block into the state. Length of the block is n and buffers are
// accessed starting at offset. Input block is d, output is written into
// buffer out if mode is PLAINTEXT or CIPHERTEXT.
// Only the XOR operation is performed, not XORing of padding constants.
static void compress_block(unsigned char *state, unsigned char *out,
const unsigned char *d, compress_mode mode,
unsigned long long offset, unsigned long long n) {
if (mode == CIPHERTEXT) {
xor_bytes(out + offset, state, d + offset, n);
memcpy(state, d + offset, n);
} else {
xor_bytes(state, state, d + offset, n);
if (mode == PLAINTEXT) {
memcpy(out + offset, state, n);
}
}
}
// Compress a block into the state (in duplex-sponge mode).
// Input data buffer is d with length dlen.
// Output is written into buffer out if mode is PLAINTEXT or CIPHERTEXT.
// Padding is handled if needed.
static unsigned long long compress_data(unsigned char *state,
unsigned char *out,
const unsigned char *d,
unsigned long long dlen,
compress_mode mode) {
unsigned long long i;
for (i = 0; i < dlen / RATE_BYTES; i++) {
compress_block(state, out, d, mode, i * RATE_BYTES, RATE_BYTES);
shadow(state);
}
int rem = dlen % RATE_BYTES;
if (rem != 0) {
compress_block(state, out, d, mode, i * RATE_BYTES, rem);
state[rem] ^= 0x01;
state[RATE_BYTES] ^= 0x02;
shadow(state);
}
return i * RATE_BYTES + rem;
}