make hsalsa20 a public API function

This commit is contained in:
Karel Miko 2026-04-16 17:04:51 +02:00
parent 02788482af
commit c8ac4fc11c
4 changed files with 115 additions and 38 deletions

View file

@ -1493,11 +1493,30 @@ If you define \textit{LTC\_XSALSA20} to include \textit{XSalsa20} in a minimal
As always, never ever use the same key + nonce/IV pair more than once.
\vspace{1mm}
\subsection{HSalsa20}
\textit{HSalsa20} is the key derivation function underlying \textit{XSalsa20}. It applies the
Salsa20 core (without the final addition step) to a 256-bit key and a 128-bit input,
producing a 256-bit derived key. It is also useful as a standalone KDF, for example in NaCl-style
\textit{crypto\_box} constructions where it derives a symmetric key from an X25519 shared secret.
\index{xsalsa20\_hsalsa20()}
\begin{verbatim}
int xsalsa20_hsalsa20(unsigned char *out, unsigned long outlen,
const unsigned char *key, unsigned long keylen,
const unsigned char *in, unsigned long inlen,
int rounds);
\end{verbatim}
This derives a 32-byte subkey from a 32-byte \textit{key} and a 16-byte \textit{in} using
\textit{rounds} Salsa20 rounds (0 = default 20). The output is stored in \textit{out}
(\textit{outlen} must be 32, \textit{keylen} must be 32, \textit{inlen} must be 16).
\vspace{1mm}
For more information about Salsa20 see
\url{https://en.wikipedia.org/wiki/Salsa20}.
\vspace{1mm}
For more information about XSalsa20 see
For more information about XSalsa20 and HSalsa20 see
\url{https://cr.yp.to/snuffle/xsalsa-20081128.pdf}.
\vspace{1mm}

View file

@ -1116,6 +1116,10 @@ int salsa20_memory(const unsigned char *key, unsigned long keylen, unsigned
#ifdef LTC_XSALSA20
int xsalsa20_hsalsa20(unsigned char *out, unsigned long outlen,
const unsigned char *key, unsigned long keylen,
const unsigned char *in, unsigned long inlen,
int rounds);
int xsalsa20_setup(salsa20_state *st, const unsigned char *key, unsigned long keylen,
const unsigned char *nonce, unsigned long noncelen,
int rounds);

View file

@ -39,6 +39,65 @@ static void s_xsalsa20_doubleround(ulong32 *x, int rounds)
#undef QUARTERROUND
/**
HSalsa20: derive a 256-bit subkey from a 256-bit key and 128-bit input.
This is the Salsa20 core (double-rounds) without the final addition step,
extracting output from state positions {0,5,10,15,6,7,8,9}.
@param out [out] The derived 32-byte subkey
@param outlen The length of the output buffer, must be 32 (octets)
@param key The secret key
@param keylen The length of the secret key, must be 32 (octets)
@param in The 16-byte input (nonce or constant)
@param inlen The length of the input, must be 16 (octets)
@param rounds Number of rounds (must be evenly divisible by 2, default is 20)
@return CRYPT_OK if successful
*/
int xsalsa20_hsalsa20(unsigned char *out, unsigned long outlen,
const unsigned char *key, unsigned long keylen,
const unsigned char *in, unsigned long inlen,
int rounds)
{
const char * const constants = "expand 32-byte k";
const int sti[] = {0, 5, 10, 15, 6, 7, 8, 9};
ulong32 x[16];
int i;
LTC_ARGCHK(out != NULL);
LTC_ARGCHK(outlen == 32);
LTC_ARGCHK(key != NULL);
LTC_ARGCHK(keylen == 32);
LTC_ARGCHK(in != NULL);
LTC_ARGCHK(inlen == 16);
if (rounds == 0) rounds = 20;
LTC_ARGCHK(rounds % 2 == 0);
LOAD32L(x[ 0], constants + 0);
LOAD32L(x[ 5], constants + 4);
LOAD32L(x[10], constants + 8);
LOAD32L(x[15], constants + 12);
LOAD32L(x[ 1], key + 0);
LOAD32L(x[ 2], key + 4);
LOAD32L(x[ 3], key + 8);
LOAD32L(x[ 4], key + 12);
LOAD32L(x[11], key + 16);
LOAD32L(x[12], key + 20);
LOAD32L(x[13], key + 24);
LOAD32L(x[14], key + 28);
LOAD32L(x[ 6], in + 0);
LOAD32L(x[ 7], in + 4);
LOAD32L(x[ 8], in + 8);
LOAD32L(x[ 9], in + 12);
s_xsalsa20_doubleround(x, rounds);
for (i = 0; i < 8; ++i) {
STORE32L(x[sti[i]], out + 4 * i);
}
zeromem(x, sizeof(x));
return CRYPT_OK;
}
/**
Initialize an XSalsa20 context
@param st [out] The destination of the XSalsa20 state
@ -54,46 +113,18 @@ int xsalsa20_setup(salsa20_state *st, const unsigned char *key, unsigned long ke
int rounds)
{
const char * const constants = "expand 32-byte k";
const int sti[] = {0, 5, 10, 15, 6, 7, 8, 9}; /* indices used to build subkey fm x */
ulong32 x[64]; /* input to & output fm doubleround */
unsigned char subkey[32];
int i;
int err;
LTC_ARGCHK(st != NULL);
LTC_ARGCHK(key != NULL);
LTC_ARGCHK(keylen == 32);
LTC_ARGCHK(nonce != NULL);
LTC_ARGCHK(noncelen == 24);
LTC_ARGCHK(st != NULL);
LTC_ARGCHK(nonce != NULL);
LTC_ARGCHK(noncelen == 24);
if (rounds == 0) rounds = 20;
LTC_ARGCHK(rounds % 2 == 0); /* number of rounds must be evenly divisible by 2 */
/* load the state to "hash" the key */
LOAD32L(x[ 0], constants + 0);
LOAD32L(x[ 5], constants + 4);
LOAD32L(x[10], constants + 8);
LOAD32L(x[15], constants + 12);
LOAD32L(x[ 1], key + 0);
LOAD32L(x[ 2], key + 4);
LOAD32L(x[ 3], key + 8);
LOAD32L(x[ 4], key + 12);
LOAD32L(x[11], key + 16);
LOAD32L(x[12], key + 20);
LOAD32L(x[13], key + 24);
LOAD32L(x[14], key + 28);
LOAD32L(x[ 6], nonce + 0);
LOAD32L(x[ 7], nonce + 4);
LOAD32L(x[ 8], nonce + 8);
LOAD32L(x[ 9], nonce + 12);
/* HSalsa20: derive subkey from key and first 16 bytes of nonce */
if ((err = xsalsa20_hsalsa20(subkey, 32, key, keylen, nonce, 16, rounds)) != CRYPT_OK) goto cleanup;
/* use modified salsa20 doubleround (no final addition) */
s_xsalsa20_doubleround(x, rounds);
/* extract the subkey */
for (i = 0; i < 8; ++i) {
STORE32L(x[sti[i]], subkey + 4 * i);
}
/* load the final initial state */
/* load the final initial state with the derived subkey */
LOAD32L(st->input[ 0], constants + 0);
LOAD32L(st->input[ 5], constants + 4);
LOAD32L(st->input[10], constants + 8);
@ -114,12 +145,12 @@ int xsalsa20_setup(salsa20_state *st, const unsigned char *key, unsigned long ke
st->ksleft = 0;
st->ivlen = 24; /* set switch to say nonce/IV has been loaded */
cleanup:
#ifdef LTC_CLEAN_STACK
zeromem(x, sizeof(x));
zeromem(subkey, sizeof(subkey));
#endif
return CRYPT_OK;
return err;
}

View file

@ -28,6 +28,29 @@ int xsalsa20_test(void)
return CRYPT_NOP;
#else
/***************************************************************************
* TV0: HSalsa20 known-answer test
* From the NaCl test suite / https://cr.yp.to/snuffle/xsalsa-20081128.pdf
*/
{
const unsigned char key[] = {
0x1b,0x27,0x55,0x64,0x73,0xe9,0x85,0xd4,0x62,0xcd,0x51,0x19,0x7a,0x9a,0x46,0xc7,
0x60,0x09,0x54,0x9e,0xac,0x64,0x74,0xf2,0x06,0xc4,0xee,0x08,0x44,0xf6,0x83,0x89
};
const unsigned char in[] = {
0x69,0x69,0x6e,0xe9,0x55,0xb6,0x2b,0x73,0xcd,0x62,0xbd,0xa8,0x75,0xfc,0x73,0xd6
};
const unsigned char expected[] = {
0xdc,0x90,0x8d,0xda,0x0b,0x93,0x44,0xa9,0x53,0x62,0x9b,0x73,0x38,0x20,0x77,0x88,
0x80,0xf3,0xce,0xb4,0x21,0xbb,0x61,0xb9,0x1c,0xbd,0x4c,0x3e,0x66,0x25,0x6c,0xe4
};
unsigned char out[32];
int err;
if ((err = xsalsa20_hsalsa20(out, 32, key, 32, in, 16, 20)) != CRYPT_OK) return err;
if (ltc_compare_testvector(out, 32, expected, 32, "XSALSA20-TV0 (HSalsa20)", 0)) return CRYPT_FAIL_TESTVECTOR;
}
/***************************************************************************
* verify a round trip:
*/