Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions doc/crypt.tex
Original file line number Diff line number Diff line change
Expand Up @@ -2556,6 +2556,7 @@ \subsection{Encryption / Decryption}
\begin{verbatim}
int siv_encrypt_memory( int cipher,
const unsigned char *key, unsigned long keylen,
unsigned long adnum,
const unsigned char *ad[], unsigned long adlen[],
const unsigned char *pt, unsigned long ptlen,
unsigned char *ct, unsigned long *ctlen);
Expand All @@ -2571,13 +2572,13 @@ \subsection{Encryption / Decryption}

The AAD is passed as array of pointers in \textit{ad}. The length of each AAD is passed as array of
\textit{unsigned long} in \textit{adlen}.
As soon as an array element of \textit{ad} is hit which equals \texttt{NULL} or an array element of \textit{adlen}
is hit which equals \texttt{0}, processing of the AAD is stopped.
The number of AAD elements is passed in \textit{adnum}, when it equals \texttt{0} both \textit{ad} and \textit{adlen} may be \texttt{NULL}.

\index{siv\_decrypt\_memory()}
\begin{verbatim}
int siv_decrypt_memory( int cipher,
const unsigned char *key, unsigned long keylen,
unsigned long adnum,
const unsigned char *ad[], unsigned long adlen[],
const unsigned char *ct, unsigned long ctlen,
unsigned char *pt, unsigned long *ptlen);
Expand Down Expand Up @@ -2609,19 +2610,21 @@ \subsection{Encryption / Decryption}
* but a string is on most platforms defined as a "signed" `char*`. */
if ((err = siv_encrypt_memory(find_cipher("aes"),
((unsigned char[32]) {0x0}), 32,
3,
((const unsigned char*[]) {(void*)"aad0", (void*)"aad1",
(void*)"NONCE", NULL}),
((unsigned long[]) {4, 4, 5, 0}),
(void*)"NONCE"}),
((unsigned long[]) {4, 4, 5}),
plain, plainlen,
ct, &ctlen)) != CRYPT_OK) {
whine_and_pout(err);
}

if ((err = siv_decrypt_memory(find_cipher("aes"),
((unsigned char[32]) {0x0}), 32,
3,
((const unsigned char*[]) {(void*)"aad0", (void*)"aad1",
(void*)"NONCE", NULL}),
((unsigned long[]) {4, 4, 5, 0}),
(void*)"NONCE"}),
((unsigned long[]) {4, 4, 5}),
ct, ctlen,
plain, &plainlen)) != CRYPT_OK) {
whine_and_pout(err);
Expand All @@ -2641,13 +2644,15 @@ \subsection{One--Shot Packet}
const unsigned char *key, unsigned long keylen,
const unsigned char *in, unsigned long inlen,
unsigned char *out, unsigned long *outlen,
unsigned long adnum,
...);
\end{verbatim}

This will execute a SIV operation of the \textit{direction} (\texttt{LTC\_ENCRYPT} resp. \texttt{LTC\_DECRYPT})
using the \textit{cipher} with the \textit{key} of len \textit{keylen}.
The AAD is optionally passed as varargs of the form \textit{(const unsigned char*, unsigned long)}, which musst be
NULL terminated.
Exactly \textit{adnum} such pairs are read; the terminating \texttt{NULL} does not delimit the list; see \ref{SIV_zero_vs_empty_ad}.
The input is passed via the \textit{in} argument of length \textit{inlen}.
The output is stored in the buffer pointer to by the \textit{out} argument where the length is passed as \textit{outlen}.
\textit{outlen} shall contain the initial size of the buffer behind \textit{out} when calling the function and on
Expand Down Expand Up @@ -2677,15 +2682,15 @@ \subsection{One--Shot Packet}
((unsigned char[32]) {0x0}), 32,
plain, plainlen,
ct, &ctlen,
"aad0", 4uL, "aad1", 4uL, "NONCE", 5uL, NULL)) != CRYPT_OK) {
3, "aad0", 4uL, "aad1", 4uL, "NONCE", 5uL, NULL)) != CRYPT_OK) {
whine_and_pout(err);
}

if ((err = siv_memory(find_cipher("aes"), LTC_DECRYPT,
((unsigned char[32]) {0x0}), 32,
ct, ctlen,
plain, &plainlen,
"aad0", 4uL, "aad1", 4uL, "NONCE", 5uL, NULL)) != CRYPT_OK) {
3, "aad0", 4uL, "aad1", 4uL, "NONCE", 5uL, NULL)) != CRYPT_OK) {
whine_and_pout(err);
}

Expand All @@ -2694,6 +2699,29 @@ \subsection{One--Shot Packet}
\end{verbatim}
\end{small}

\subsection{No Associated Data vs. Empty Associated Data}
\label{SIV_zero_vs_empty_ad}

RFC 5297 computes the synthetic IV as $S2V(K_1, AD_1, \ldots, AD_m, P)$ i.e. S2V is fed a \textit{list} of strings
and the result depends on the number of components not only on their contents.

Passing \textbf{no} associated data ($m = 0$) is therefore \textbf{not} the same as passing \textbf{one empty}
associated data string ($m = 1$). For the same key and plaintext the two produce a different ciphertext and one
will not decrypt as the other. This is also why \textit{adnum} has to be passed explicitly: neither a \texttt{NULL}
pointer nor a length of \texttt{0} can terminate the list as both are valid contents of a component.

\begin{small}
\begin{verbatim}
/* no associated data, m = 0 */
siv_encrypt_memory(cipher, key, keylen, 0, NULL, NULL, pt, ptlen, ct, &ctlen);
siv_memory(cipher, LTC_ENCRYPT, key, keylen, pt, ptlen, ct, &ctlen, 0, NULL);

/* one empty associated data string, m = 1 */
siv_encrypt_memory(cipher, key, keylen, 1, ((const unsigned char*[]) {NULL}), ((unsigned long[]) {0}), pt, ptlen, ct, &ctlen);
siv_memory(cipher, LTC_ENCRYPT, key, keylen, pt, ptlen, ct, &ctlen, 1, NULL, 0, NULL);
\end{verbatim}
\end{small}

\mysection{GCM-SIV}
\label{GCM-SIV}

Expand Down
72 changes: 61 additions & 11 deletions src/encauth/siv/siv.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,16 @@ static int s_siv_S2V(int cipher,
return err;
}

do {
/* With zero AD components the loop runs zero times. */
while (n < adnum) {
if (n >= s_siv_max_aad_components) {
return CRYPT_INPUT_TOO_LONG;
}
if ((err = s_siv_S2V_dbl_xor_cmac(&ctx, ad ? ad[n] : NULL, adlen ? adlen[n] : 0, &D)) != CRYPT_OK) {
return err;
}
n++;
} while(n < adnum);
}

return s_siv_S2V_T(&ctx, in, inlen, &D, V);
}
Expand Down Expand Up @@ -470,23 +471,19 @@ int siv_memory( int cipher, int direction,
goto err_out;
}
va_start(args, adnum);
do {
if (adnum) {
aad = va_arg(args, const unsigned char*);
aadlen = va_arg(args, unsigned long);
} else {
aad = NULL;
aadlen = 0;
}
/* With zero AD components the loop runs zero times. */
while (n < adnum) {
if (n >= s_siv_max_aad_components) {
err = CRYPT_INPUT_TOO_LONG;
goto err_out2;
}
aad = va_arg(args, const unsigned char*);
aadlen = va_arg(args, unsigned long);
if ((err = s_siv_S2V_dbl_xor_cmac(&ctx, aad, aadlen, &D)) != CRYPT_OK) {
goto err_out2;
}
n++;
} while (n < adnum);
}

if ((err = s_siv_S2V_T(&ctx, in_work, in_work_len, &D, &siv.V)) != CRYPT_OK) {
goto err_out2;
Expand Down Expand Up @@ -614,6 +611,19 @@ int siv_test(void)
0x3d, 0x58, 0x8e, 0x85, 0x64, 0xa5, 0xfe };


/* Zero AD components vs. a single *empty* AD component (vectors were cross-checked against OpenSSL 3.5.6) */
const unsigned char output_A1_zeroad[] =
{ 0xf1, 0xc5, 0xfd, 0xea, 0xc1, 0xf1, 0x5a, 0x26,
0x77, 0x9c, 0x15, 0x01, 0xf9, 0xfb, 0x75, 0x88,
0x27, 0xe9, 0x46, 0xc6, 0x69, 0x08, 0x8a, 0xb0,
0x6d, 0xa5, 0x8c, 0x5c, 0x83, 0x1c };
const unsigned char output_A1_emptyad[] =
{ 0xd1, 0x02, 0x2f, 0x5b, 0x36, 0x64, 0xe5, 0xa4,
0xdf, 0xaf, 0x90, 0xf8, 0x5b, 0xe6, 0xf2, 0x8a,
0xb6, 0x6c, 0xff, 0x6b, 0x8e, 0xca, 0x0b, 0x79,
0xf0, 0x83, 0xb3, 0x9a, 0x09, 0x01 };
const unsigned char *ad_emptyad[] = { NULL, NULL };
unsigned long adlen_emptyad[] = { 0, 0 };

#define PL_PAIR(n) n, sizeof(n)
struct {
Expand All @@ -632,6 +642,8 @@ int siv_test(void)
{ PL_PAIR(Key_A2), PL_PAIR(Plaintext_A2), 3, &ad_A2, &adlen_A2, PL_PAIR(output_A2), "RFC5297 - A.2. Nonce-Based Authenticated Encryption Example" },
{ PL_PAIR(Key_A2), PL_PAIR(Plaintext_A2), 3, &ad_ossl0, &adlen_ossl0, PL_PAIR(output_ossl0), "OpenSSL based example 0" },
{ PL_PAIR(Key_A2), PL_PAIR(Plaintext_A2), 3, &ad_ossl1, &adlen_ossl1, PL_PAIR(output_ossl1), "OpenSSL based example 1" },
{ PL_PAIR(Key_A1), PL_PAIR(Plaintext_A1), 0, NULL, NULL, PL_PAIR(output_A1_zeroad), "Zero AD components (ad = adlen = NULL)" },
{ PL_PAIR(Key_A1), PL_PAIR(Plaintext_A1), 1, &ad_emptyad, &adlen_emptyad, PL_PAIR(output_A1_emptyad), "One empty AD component" },
};
#undef PL_PAIR

Expand Down Expand Up @@ -736,6 +748,44 @@ int siv_test(void)
return CRYPT_FAIL_TESTVECTOR;
}

n++;

/* Testcase 0x4 - siv_memory() - zero AD components - encrypt */
buflen = sizeof(buf);
if ((err = siv_memory(cipher, LTC_ENCRYPT, Key_A1, sizeof(Key_A1), Plaintext_A1, sizeof(Plaintext_A1), buf, &buflen, 0, NULL)) != CRYPT_OK) {
return err;
}
if (ltc_compare_testvector(buf, buflen, output_A1_zeroad, sizeof(output_A1_zeroad), "siv_memory() - zero AD components", n) != 0) {
return CRYPT_FAIL_TESTVECTOR;
}
/* Testcase 0x1004 - siv_memory() - zero AD components - decrypt */
buflen = sizeof(buf);
if ((err = siv_memory(cipher, LTC_DECRYPT, Key_A1, sizeof(Key_A1), output_A1_zeroad, sizeof(output_A1_zeroad), buf, &buflen, 0, NULL)) != CRYPT_OK) {
return err;
}
if (ltc_compare_testvector(buf, buflen, Plaintext_A1, sizeof(Plaintext_A1), "siv_memory() - zero AD components", n + 0x1000) != 0) {
return CRYPT_FAIL_TESTVECTOR;
}

n++;

/* Testcase 0x5 - siv_memory() - one empty AD component - encrypt */
buflen = sizeof(buf);
if ((err = siv_memory(cipher, LTC_ENCRYPT, Key_A1, sizeof(Key_A1), Plaintext_A1, sizeof(Plaintext_A1), buf, &buflen, 1, NULL, 0UL, NULL)) != CRYPT_OK) {
return err;
}
if (ltc_compare_testvector(buf, buflen, output_A1_emptyad, sizeof(output_A1_emptyad), "siv_memory() - one empty AD component", n) != 0) {
return CRYPT_FAIL_TESTVECTOR;
}
/* Testcase 0x1005 - siv_memory() - one empty AD component - decrypt */
buflen = sizeof(buf);
if ((err = siv_memory(cipher, LTC_DECRYPT, Key_A1, sizeof(Key_A1), output_A1_emptyad, sizeof(output_A1_emptyad), buf, &buflen, 1, NULL, 0UL, NULL)) != CRYPT_OK) {
return err;
}
if (ltc_compare_testvector(buf, buflen, Plaintext_A1, sizeof(Plaintext_A1), "siv_memory() - one empty AD component", n + 0x1000) != 0) {
return CRYPT_FAIL_TESTVECTOR;
}

for (n = 0; n < LTC_ARRAY_SIZE(tmp); ++n) {
tmp[n] = XCALLOC(1, tmpmax);
if (tmp[n] == NULL) {
Expand Down
Loading