diff --git a/doc/crypt.tex b/doc/crypt.tex
index 73fdeaae4..4e68dce55 100644
--- a/doc/crypt.tex
+++ b/doc/crypt.tex
@@ -7629,6 +7629,33 @@ \subsection{Argon2}
The function returns \texttt{CRYPT\_OK} on success, \texttt{CRYPT\_MEM} if memory allocation fails, or \texttt{CRYPT\_INVALID\_ARG} if any parameter is out of range.
+\subsection{scrypt}
+\index{scrypt}
+\label{scrypt}
+
+scrypt is a memory-hard password-based key derivation function defined in \href{https://datatracker.ietf.org/doc/html/rfc7914}{\texttt{RFC 7914}}.
+It is designed so that a large amount of memory is required for evaluation, making hardware brute-force attacks more costly.
+Internally it uses PBKDF2-HMAC-SHA-256 and a reduced-round Salsa20 core.
+
+To enable scrypt, define \texttt{LTC\_SCRYPT} in \textit{tomcrypt\_custom.h} (it also requires \texttt{LTC\_PKCS\_5} and \texttt{LTC\_SHA256}).
+
+\index{scrypt\_pbkdf()}
+\begin{alltt}
+int scrypt_pbkdf(const unsigned char *password, unsigned long password_len,
+ const unsigned char *salt, unsigned long salt_len,
+ unsigned long N, unsigned long r, unsigned long p,
+ unsigned char *out, unsigned long outlen);
+\end{alltt}
+
+The \textit{password} parameter is the password of length \textit{password\_len}.
+The \textit{salt} parameter is a random salt of length \textit{salt\_len}.
+The \textit{N} parameter is the CPU/memory cost; it must be greater than 1 and a power of 2.
+The \textit{r} parameter is the block size (minimum 1; a typical value is 8).
+The \textit{p} parameter is the parallelisation factor (minimum 1); this implementation is single-threaded, so increasing \textit{p} raises the computational cost without improving performance.
+The derived key of length \textit{outlen} is written to \textit{out}.
+The function returns \texttt{CRYPT\_OK} on success or an error code on failure.
+
+
\mysection{PKCS \#8}
\index{PKCS \#8}
\label{pkcs8}
diff --git a/libtomcrypt_VS2008.vcproj b/libtomcrypt_VS2008.vcproj
index a2c27ca14..69a5e2d69 100644
--- a/libtomcrypt_VS2008.vcproj
+++ b/libtomcrypt_VS2008.vcproj
@@ -1675,6 +1675,14 @@
>
+
+
+
+
diff --git a/makefile.mingw b/makefile.mingw
index 7d2b23065..5d388f55e 100644
--- a/makefile.mingw
+++ b/makefile.mingw
@@ -116,13 +116,14 @@ src/misc/padding/padding_pad.o src/misc/password_free.o src/misc/pbes/pbes.o src
src/misc/pbes/pbes2.o src/misc/pem/pem.o src/misc/pem/pem_pkcs.o src/misc/pem/pem_read.o \
src/misc/pem/pem_ssh.o src/misc/pkcs12/pkcs12_kdf.o src/misc/pkcs12/pkcs12_utf8_to_utf16.o \
src/misc/pkcs5/pkcs_5_1.o src/misc/pkcs5/pkcs_5_2.o src/misc/pkcs5/pkcs_5_test.o \
-src/misc/ssh/ssh_decode_sequence_multi.o src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o \
-src/modes/cbc/cbc_decrypt.o src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o \
-src/modes/cbc/cbc_getiv.o src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o \
-src/modes/cfb/cfb_decrypt.o src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o \
-src/modes/cfb/cfb_getiv.o src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o \
-src/modes/ctr/ctr_decrypt.o src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o \
-src/modes/ctr/ctr_getiv.o src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
+src/misc/scrypt/scrypt.o src/misc/ssh/ssh_decode_sequence_multi.o \
+src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o src/modes/cbc/cbc_decrypt.o \
+src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o src/modes/cbc/cbc_getiv.o \
+src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o src/modes/cfb/cfb_decrypt.o \
+src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o src/modes/cfb/cfb_getiv.o \
+src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o src/modes/ctr/ctr_decrypt.o \
+src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o src/modes/ctr/ctr_getiv.o \
+src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
src/modes/ecb/ecb_decrypt.o src/modes/ecb/ecb_done.o src/modes/ecb/ecb_encrypt.o \
src/modes/ecb/ecb_start.o src/modes/f8/f8_decrypt.o src/modes/f8/f8_done.o src/modes/f8/f8_encrypt.o \
src/modes/f8/f8_getiv.o src/modes/f8/f8_setiv.o src/modes/f8/f8_start.o src/modes/f8/f8_test_mode.o \
@@ -242,7 +243,7 @@ tests/misc_test.o tests/modes_test.o tests/mpi_test.o tests/multi_test.o \
tests/no_null_termination_check_test.o tests/no_prng.o tests/padding_test.o tests/pem_test.o \
tests/pk_oid_test.o tests/pkcs_1_eme_test.o tests/pkcs_1_emsa_test.o tests/pkcs_1_oaep_test.o \
tests/pkcs_1_pss_test.o tests/pkcs_1_test.o tests/prng_test.o tests/rotate_test.o tests/rsa_test.o \
-tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
+tests/scrypt_test.o tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
#The following headers will be installed by "make install"
HEADERS_PUB=src/headers/tomcrypt.h src/headers/tomcrypt_argchk.h src/headers/tomcrypt_cfg.h \
diff --git a/makefile.msvc b/makefile.msvc
index 25bd5707b..9f530931c 100644
--- a/makefile.msvc
+++ b/makefile.msvc
@@ -109,13 +109,14 @@ src/misc/padding/padding_pad.obj src/misc/password_free.obj src/misc/pbes/pbes.o
src/misc/pbes/pbes2.obj src/misc/pem/pem.obj src/misc/pem/pem_pkcs.obj src/misc/pem/pem_read.obj \
src/misc/pem/pem_ssh.obj src/misc/pkcs12/pkcs12_kdf.obj src/misc/pkcs12/pkcs12_utf8_to_utf16.obj \
src/misc/pkcs5/pkcs_5_1.obj src/misc/pkcs5/pkcs_5_2.obj src/misc/pkcs5/pkcs_5_test.obj \
-src/misc/ssh/ssh_decode_sequence_multi.obj src/misc/ssh/ssh_encode_sequence_multi.obj src/misc/zeromem.obj \
-src/modes/cbc/cbc_decrypt.obj src/modes/cbc/cbc_done.obj src/modes/cbc/cbc_encrypt.obj \
-src/modes/cbc/cbc_getiv.obj src/modes/cbc/cbc_setiv.obj src/modes/cbc/cbc_start.obj \
-src/modes/cfb/cfb_decrypt.obj src/modes/cfb/cfb_done.obj src/modes/cfb/cfb_encrypt.obj \
-src/modes/cfb/cfb_getiv.obj src/modes/cfb/cfb_setiv.obj src/modes/cfb/cfb_start.obj \
-src/modes/ctr/ctr_decrypt.obj src/modes/ctr/ctr_done.obj src/modes/ctr/ctr_encrypt.obj \
-src/modes/ctr/ctr_getiv.obj src/modes/ctr/ctr_setiv.obj src/modes/ctr/ctr_start.obj src/modes/ctr/ctr_test.obj \
+src/misc/scrypt/scrypt.obj src/misc/ssh/ssh_decode_sequence_multi.obj \
+src/misc/ssh/ssh_encode_sequence_multi.obj src/misc/zeromem.obj src/modes/cbc/cbc_decrypt.obj \
+src/modes/cbc/cbc_done.obj src/modes/cbc/cbc_encrypt.obj src/modes/cbc/cbc_getiv.obj \
+src/modes/cbc/cbc_setiv.obj src/modes/cbc/cbc_start.obj src/modes/cfb/cfb_decrypt.obj \
+src/modes/cfb/cfb_done.obj src/modes/cfb/cfb_encrypt.obj src/modes/cfb/cfb_getiv.obj \
+src/modes/cfb/cfb_setiv.obj src/modes/cfb/cfb_start.obj src/modes/ctr/ctr_decrypt.obj \
+src/modes/ctr/ctr_done.obj src/modes/ctr/ctr_encrypt.obj src/modes/ctr/ctr_getiv.obj \
+src/modes/ctr/ctr_setiv.obj src/modes/ctr/ctr_start.obj src/modes/ctr/ctr_test.obj \
src/modes/ecb/ecb_decrypt.obj src/modes/ecb/ecb_done.obj src/modes/ecb/ecb_encrypt.obj \
src/modes/ecb/ecb_start.obj src/modes/f8/f8_decrypt.obj src/modes/f8/f8_done.obj src/modes/f8/f8_encrypt.obj \
src/modes/f8/f8_getiv.obj src/modes/f8/f8_setiv.obj src/modes/f8/f8_start.obj src/modes/f8/f8_test_mode.obj \
@@ -235,7 +236,7 @@ tests/misc_test.obj tests/modes_test.obj tests/mpi_test.obj tests/multi_test.obj
tests/no_null_termination_check_test.obj tests/no_prng.obj tests/padding_test.obj tests/pem_test.obj \
tests/pk_oid_test.obj tests/pkcs_1_eme_test.obj tests/pkcs_1_emsa_test.obj tests/pkcs_1_oaep_test.obj \
tests/pkcs_1_pss_test.obj tests/pkcs_1_test.obj tests/prng_test.obj tests/rotate_test.obj tests/rsa_test.obj \
-tests/ssh_test.obj tests/store_test.obj tests/test.obj tests/x25519_test.obj
+tests/scrypt_test.obj tests/ssh_test.obj tests/store_test.obj tests/test.obj tests/x25519_test.obj
#The following headers will be installed by "make install"
HEADERS_PUB=src/headers/tomcrypt.h src/headers/tomcrypt_argchk.h src/headers/tomcrypt_cfg.h \
diff --git a/makefile.unix b/makefile.unix
index 36202fc9d..df169e288 100644
--- a/makefile.unix
+++ b/makefile.unix
@@ -130,13 +130,14 @@ src/misc/padding/padding_pad.o src/misc/password_free.o src/misc/pbes/pbes.o src
src/misc/pbes/pbes2.o src/misc/pem/pem.o src/misc/pem/pem_pkcs.o src/misc/pem/pem_read.o \
src/misc/pem/pem_ssh.o src/misc/pkcs12/pkcs12_kdf.o src/misc/pkcs12/pkcs12_utf8_to_utf16.o \
src/misc/pkcs5/pkcs_5_1.o src/misc/pkcs5/pkcs_5_2.o src/misc/pkcs5/pkcs_5_test.o \
-src/misc/ssh/ssh_decode_sequence_multi.o src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o \
-src/modes/cbc/cbc_decrypt.o src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o \
-src/modes/cbc/cbc_getiv.o src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o \
-src/modes/cfb/cfb_decrypt.o src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o \
-src/modes/cfb/cfb_getiv.o src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o \
-src/modes/ctr/ctr_decrypt.o src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o \
-src/modes/ctr/ctr_getiv.o src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
+src/misc/scrypt/scrypt.o src/misc/ssh/ssh_decode_sequence_multi.o \
+src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o src/modes/cbc/cbc_decrypt.o \
+src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o src/modes/cbc/cbc_getiv.o \
+src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o src/modes/cfb/cfb_decrypt.o \
+src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o src/modes/cfb/cfb_getiv.o \
+src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o src/modes/ctr/ctr_decrypt.o \
+src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o src/modes/ctr/ctr_getiv.o \
+src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
src/modes/ecb/ecb_decrypt.o src/modes/ecb/ecb_done.o src/modes/ecb/ecb_encrypt.o \
src/modes/ecb/ecb_start.o src/modes/f8/f8_decrypt.o src/modes/f8/f8_done.o src/modes/f8/f8_encrypt.o \
src/modes/f8/f8_getiv.o src/modes/f8/f8_setiv.o src/modes/f8/f8_start.o src/modes/f8/f8_test_mode.o \
@@ -256,7 +257,7 @@ tests/misc_test.o tests/modes_test.o tests/mpi_test.o tests/multi_test.o \
tests/no_null_termination_check_test.o tests/no_prng.o tests/padding_test.o tests/pem_test.o \
tests/pk_oid_test.o tests/pkcs_1_eme_test.o tests/pkcs_1_emsa_test.o tests/pkcs_1_oaep_test.o \
tests/pkcs_1_pss_test.o tests/pkcs_1_test.o tests/prng_test.o tests/rotate_test.o tests/rsa_test.o \
-tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
+tests/scrypt_test.o tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
#The following headers will be installed by "make install"
HEADERS_PUB=src/headers/tomcrypt.h src/headers/tomcrypt_argchk.h src/headers/tomcrypt_cfg.h \
diff --git a/makefile_include.mk b/makefile_include.mk
index 3c37167fb..52013d7f3 100644
--- a/makefile_include.mk
+++ b/makefile_include.mk
@@ -301,13 +301,14 @@ src/misc/padding/padding_pad.o src/misc/password_free.o src/misc/pbes/pbes.o src
src/misc/pbes/pbes2.o src/misc/pem/pem.o src/misc/pem/pem_pkcs.o src/misc/pem/pem_read.o \
src/misc/pem/pem_ssh.o src/misc/pkcs12/pkcs12_kdf.o src/misc/pkcs12/pkcs12_utf8_to_utf16.o \
src/misc/pkcs5/pkcs_5_1.o src/misc/pkcs5/pkcs_5_2.o src/misc/pkcs5/pkcs_5_test.o \
-src/misc/ssh/ssh_decode_sequence_multi.o src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o \
-src/modes/cbc/cbc_decrypt.o src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o \
-src/modes/cbc/cbc_getiv.o src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o \
-src/modes/cfb/cfb_decrypt.o src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o \
-src/modes/cfb/cfb_getiv.o src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o \
-src/modes/ctr/ctr_decrypt.o src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o \
-src/modes/ctr/ctr_getiv.o src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
+src/misc/scrypt/scrypt.o src/misc/ssh/ssh_decode_sequence_multi.o \
+src/misc/ssh/ssh_encode_sequence_multi.o src/misc/zeromem.o src/modes/cbc/cbc_decrypt.o \
+src/modes/cbc/cbc_done.o src/modes/cbc/cbc_encrypt.o src/modes/cbc/cbc_getiv.o \
+src/modes/cbc/cbc_setiv.o src/modes/cbc/cbc_start.o src/modes/cfb/cfb_decrypt.o \
+src/modes/cfb/cfb_done.o src/modes/cfb/cfb_encrypt.o src/modes/cfb/cfb_getiv.o \
+src/modes/cfb/cfb_setiv.o src/modes/cfb/cfb_start.o src/modes/ctr/ctr_decrypt.o \
+src/modes/ctr/ctr_done.o src/modes/ctr/ctr_encrypt.o src/modes/ctr/ctr_getiv.o \
+src/modes/ctr/ctr_setiv.o src/modes/ctr/ctr_start.o src/modes/ctr/ctr_test.o \
src/modes/ecb/ecb_decrypt.o src/modes/ecb/ecb_done.o src/modes/ecb/ecb_encrypt.o \
src/modes/ecb/ecb_start.o src/modes/f8/f8_decrypt.o src/modes/f8/f8_done.o src/modes/f8/f8_encrypt.o \
src/modes/f8/f8_getiv.o src/modes/f8/f8_setiv.o src/modes/f8/f8_start.o src/modes/f8/f8_test_mode.o \
@@ -432,7 +433,7 @@ tests/misc_test.o tests/modes_test.o tests/mpi_test.o tests/multi_test.o \
tests/no_null_termination_check_test.o tests/no_prng.o tests/padding_test.o tests/pem_test.o \
tests/pk_oid_test.o tests/pkcs_1_eme_test.o tests/pkcs_1_emsa_test.o tests/pkcs_1_oaep_test.o \
tests/pkcs_1_pss_test.o tests/pkcs_1_test.o tests/prng_test.o tests/rotate_test.o tests/rsa_test.o \
-tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
+tests/scrypt_test.o tests/ssh_test.o tests/store_test.o tests/test.o tests/x25519_test.o
# The following headers will be installed by "make install"
HEADERS_PUB=src/headers/tomcrypt.h src/headers/tomcrypt_argchk.h src/headers/tomcrypt_cfg.h \
diff --git a/sources.cmake b/sources.cmake
index 9da7f144b..a192ed391 100644
--- a/sources.cmake
+++ b/sources.cmake
@@ -253,6 +253,7 @@ src/misc/pkcs12/pkcs12_utf8_to_utf16.c
src/misc/pkcs5/pkcs_5_1.c
src/misc/pkcs5/pkcs_5_2.c
src/misc/pkcs5/pkcs_5_test.c
+src/misc/scrypt/scrypt.c
src/misc/ssh/ssh_decode_sequence_multi.c
src/misc/ssh/ssh_encode_sequence_multi.c
src/misc/zeromem.c
diff --git a/src/headers/tomcrypt_custom.h b/src/headers/tomcrypt_custom.h
index 31b383b8d..970e9c67b 100644
--- a/src/headers/tomcrypt_custom.h
+++ b/src/headers/tomcrypt_custom.h
@@ -516,6 +516,8 @@
#define LTC_ARGON2
+#define LTC_SCRYPT
+
/* Keep LTC_NO_HKDF for compatibility reasons
* superseeded by LTC_NO_MISC*/
#ifndef LTC_NO_HKDF
@@ -687,6 +689,10 @@
#error LTC_ARGON2 requires LTC_BLAKE2B
#endif
+#if defined(LTC_SCRYPT) && (!defined(LTC_PKCS_5) || !defined(LTC_SHA256))
+ #error LTC_SCRYPT requires LTC_PKCS_5 and LTC_SHA256
+#endif
+
#if defined(LTC_CHACHA20POLY1305_MODE) && (!defined(LTC_CHACHA) || !defined(LTC_POLY1305))
#error LTC_CHACHA20POLY1305_MODE requires LTC_CHACHA + LTC_POLY1305
#endif
diff --git a/src/headers/tomcrypt_misc.h b/src/headers/tomcrypt_misc.h
index 18a89e7a7..be63f5507 100644
--- a/src/headers/tomcrypt_misc.h
+++ b/src/headers/tomcrypt_misc.h
@@ -71,6 +71,14 @@ int argon2_hash(const unsigned char *pwd, unsigned long pwdlen,
unsigned char *out, unsigned long outlen);
#endif /* LTC_ARGON2 */
+/* ---- scrypt password-based KDF (RFC 7914) ---- */
+#ifdef LTC_SCRYPT
+int scrypt_pbkdf(const unsigned char *password, unsigned long password_len,
+ const unsigned char *salt, unsigned long salt_len,
+ unsigned long N, unsigned long r, unsigned long p,
+ unsigned char *out, unsigned long outlen);
+#endif /* LTC_SCRYPT */
+
#ifdef LTC_BCRYPT
int bcrypt_pbkdf_openbsd(const void *secret, unsigned long secret_len,
const unsigned char *salt, unsigned long salt_len,
diff --git a/src/misc/crypt/crypt.c b/src/misc/crypt/crypt.c
index cc95fe8eb..a07a191ba 100644
--- a/src/misc/crypt/crypt.c
+++ b/src/misc/crypt/crypt.c
@@ -458,6 +458,9 @@ const char *crypt_build_settings =
" BCRYPT "
" " NAME_VALUE(LTC_BCRYPT_DEFAULT_ROUNDS) " "
#endif
+#if defined(LTC_SCRYPT)
+ " SCRYPT "
+#endif
#if defined(LTC_CRC32)
" CRC32 "
#endif
diff --git a/src/misc/scrypt/scrypt.c b/src/misc/scrypt/scrypt.c
new file mode 100644
index 000000000..4d2a1613e
--- /dev/null
+++ b/src/misc/scrypt/scrypt.c
@@ -0,0 +1,190 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+#include "tomcrypt_private.h"
+
+/**
+ @file scrypt.c
+ scrypt password-based key derivation function (RFC 7914)
+*/
+#ifdef LTC_SCRYPT
+
+/* Salsa20/8 Core (RFC 7914 Section 3) */
+#define SCRYPT_QR(a,b,c,d) \
+ x[b] ^= ROL(x[a] + x[d], 7); \
+ x[c] ^= ROL(x[b] + x[a], 9); \
+ x[d] ^= ROL(x[c] + x[b], 13); \
+ x[a] ^= ROL(x[d] + x[c], 18);
+
+static void s_salsa20_8(unsigned char B[64])
+{
+ ulong32 x[16], b32[16];
+ int i;
+
+ for (i = 0; i < 16; ++i) {
+ LOAD32L(b32[i], B + i * 4);
+ }
+ XMEMCPY(x, b32, sizeof(x));
+ for (i = 8; i > 0; i -= 2) {
+ SCRYPT_QR( 0, 4, 8,12)
+ SCRYPT_QR( 5, 9,13, 1)
+ SCRYPT_QR(10,14, 2, 6)
+ SCRYPT_QR(15, 3, 7,11)
+ SCRYPT_QR( 0, 1, 2, 3)
+ SCRYPT_QR( 5, 6, 7, 4)
+ SCRYPT_QR(10,11, 8, 9)
+ SCRYPT_QR(15,12,13,14)
+ }
+ for (i = 0; i < 16; ++i) {
+ STORE32L(x[i] + b32[i], B + i * 4);
+ }
+}
+
+/* scryptBlockMix (RFC 7914 Section 4) */
+static void s_blockmix(unsigned char *B, unsigned char *Y, unsigned long r)
+{
+ unsigned char X[64];
+ unsigned long i;
+ unsigned long blen = 128 * r;
+
+ /* 1: X = B[2r - 1] */
+ XMEMCPY(X, B + blen - 64, 64);
+ /* 2: for i = 0 to 2r-1 */
+ for (i = 0; i < 2 * r; ++i) {
+ unsigned long j;
+ for (j = 0; j < 64; ++j) X[j] ^= B[i * 64 + j];
+ s_salsa20_8(X);
+ XMEMCPY(Y + i * 64, X, 64);
+ }
+ /* 3: B' = (Y[0], Y[2], ..., Y[2r-2], Y[1], Y[3], ..., Y[2r-1]) */
+ for (i = 0; i < r; ++i) {
+ XMEMCPY(B + i * 64, Y + (2 * i) * 64, 64);
+ }
+ for (i = 0; i < r; ++i) {
+ XMEMCPY(B + (i + r) * 64, Y + (2 * i + 1) * 64, 64);
+ }
+}
+
+/* Integerify: interpret last 64-byte block as little-endian and return low 64 bits */
+static LTC_INLINE ulong64 s_integerify(const unsigned char *B, unsigned long r)
+{
+ const unsigned char *X = B + (2 * r - 1) * 64;
+ ulong64 v;
+
+ LOAD64L(v, X);
+ return v;
+}
+
+/* scryptROMix (RFC 7914 Section 5) */
+static void s_romix(unsigned char *B, unsigned long r, ulong64 N, unsigned char *V, unsigned char *XY)
+{
+ unsigned char *X = XY;
+ unsigned char *Y = XY + 128 * r;
+ unsigned long blen = 128 * r;
+ ulong64 i, j;
+
+ /* 1: X = B */
+ XMEMCPY(X, B, blen);
+ /* 2: for i = 0 to N-1: V[i] = X; X = BlockMix(X) */
+ for (i = 0; i < N; ++i) {
+ XMEMCPY(V + i * blen, X, blen);
+ s_blockmix(X, Y, r);
+ }
+ /* 3: for i = 0 to N-1 */
+ for (i = 0; i < N; ++i) {
+ j = s_integerify(X, r) & (N - 1);
+ {
+ unsigned long k;
+ unsigned char *Vj = V + j * blen;
+ for (k = 0; k < blen; ++k) X[k] ^= Vj[k];
+ }
+ s_blockmix(X, Y, r);
+ }
+ /* 4: B' = X */
+ XMEMCPY(B, X, blen);
+}
+
+/**
+ Derive a key using scrypt (RFC 7914)
+
+ @param password Password
+ @param password_len Length of password
+ @param salt Salt
+ @param salt_len Length of salt
+ @param N CPU/memory cost parameter (must be > 1 and a power of 2)
+ @param r Block size parameter (minimum 1)
+ @param p Parallelisation parameter (minimum 1)
+ @param out [out] Derived key
+ @param outlen Desired output length
+ @return CRYPT_OK on success
+*/
+int scrypt_pbkdf(const unsigned char *password, unsigned long password_len,
+ const unsigned char *salt, unsigned long salt_len,
+ unsigned long N, unsigned long r, unsigned long p,
+ unsigned char *out, unsigned long outlen)
+{
+ unsigned char *B = NULL, *V = NULL, *XY = NULL;
+ const unsigned char *pwd;
+ unsigned long pwd_len, Blen, Vlen, XYlen, i;
+ unsigned char zero_byte = 0;
+ int err, hash_idx;
+
+ LTC_ARGCHK(out != NULL);
+
+ LTC_ARGCHK(password != NULL || password_len == 0);
+ LTC_ARGCHK(salt != NULL || salt_len == 0);
+ LTC_ARGCHK(N >= 2 && (N & (N - 1)) == 0); /* must be > 1 and power of 2 */
+ LTC_ARGCHK(r >= 1);
+ LTC_ARGCHK(p >= 1);
+ LTC_ARGCHK(outlen >= 1);
+ LTC_ARGCHK(r <= ULONG_MAX / 128 / p);
+ LTC_ARGCHK(r <= ULONG_MAX / 256);
+ LTC_ARGCHK(N <= ULONG_MAX / 128 / r);
+
+ hash_idx = find_hash("sha256");
+ if (hash_idx == -1) return CRYPT_INVALID_HASH;
+
+ /* WORKAROUND: HMAC rejects zero-length keys; a single zero byte
+ * produces the same zero-padded key block as an empty key. */
+ pwd = password;
+ pwd_len = password_len;
+ if (pwd_len == 0) {
+ pwd = &zero_byte;
+ pwd_len = 1;
+ }
+
+ Blen = 128 * r * p;
+ Vlen = 128 * r * N;
+ XYlen = 256 * r;
+
+ B = (unsigned char *)XMALLOC(Blen);
+ V = (unsigned char *)XMALLOC(Vlen);
+ XY = (unsigned char *)XMALLOC(XYlen);
+ if (B == NULL || V == NULL || XY == NULL) {
+ err = CRYPT_MEM;
+ goto cleanup;
+ }
+
+ /* 1: B = PBKDF2-HMAC-SHA256(password, salt, 1, p * 128 * r) */
+ {
+ unsigned long blen_out = Blen;
+ err = pkcs_5_alg2(pwd, pwd_len, salt, salt_len, 1, hash_idx, B, &blen_out);
+ if (err != CRYPT_OK) goto cleanup;
+ }
+ /* 2: for i = 0 to p-1: B[i] = ROMix(r, B[i], N) */
+ for (i = 0; i < p; ++i) {
+ s_romix(B + i * 128 * r, r, (ulong64)N, V, XY);
+ }
+ /* 3: DK = PBKDF2-HMAC-SHA256(password, B, 1, dkLen) */
+ {
+ unsigned long outlen_out = outlen;
+ err = pkcs_5_alg2(pwd, pwd_len, B, Blen, 1, hash_idx, out, &outlen_out);
+ }
+
+cleanup:
+ if (XY != NULL) { zeromem(XY, XYlen); XFREE(XY); }
+ if (V != NULL) { zeromem(V, Vlen); XFREE(V); }
+ if (B != NULL) { zeromem(B, Blen); XFREE(B); }
+ return err;
+}
+
+#endif /* LTC_SCRYPT */
diff --git a/tests/misc_test.c b/tests/misc_test.c
index 4fff9c5d3..d3d2945dc 100644
--- a/tests/misc_test.c
+++ b/tests/misc_test.c
@@ -10,6 +10,9 @@ int misc_test(void)
#ifdef LTC_BCRYPT
DO(bcrypt_test());
#endif
+#ifdef LTC_SCRYPT
+ DO(scrypt_test());
+#endif
#ifdef LTC_HKDF
DO(hkdf_test());
#endif
diff --git a/tests/scrypt_test.c b/tests/scrypt_test.c
new file mode 100644
index 000000000..b3f579208
--- /dev/null
+++ b/tests/scrypt_test.c
@@ -0,0 +1,106 @@
+/* LibTomCrypt, modular cryptographic library -- Tom St Denis */
+/* SPDX-License-Identifier: Unlicense */
+
+#include
+
+#ifdef LTC_SCRYPT
+
+/* RFC 7914 test vectors */
+
+typedef struct {
+ const char *password;
+ unsigned long password_len;
+ const char *salt;
+ unsigned long salt_len;
+ unsigned long N, r, p;
+ const unsigned char expected[64];
+ const char *name;
+} scrypt_testcase;
+
+static const scrypt_testcase cases[] = {
+ {
+ "", 0, "", 0, 16, 1, 1,
+ {
+ 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20,
+ 0x3b, 0x19, 0xca, 0x42, 0xc1, 0x8a, 0x04, 0x97,
+ 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8,
+ 0xdf, 0xdf, 0xfa, 0x3f, 0xed, 0xe2, 0x14, 0x42,
+ 0xfc, 0xd0, 0x06, 0x9d, 0xed, 0x09, 0x48, 0xf8,
+ 0x32, 0x6a, 0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17,
+ 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28,
+ 0xcf, 0x35, 0xe2, 0x0c, 0x38, 0xd1, 0x89, 0x06
+ },
+ "scrypt(\"\", \"\", 16, 1, 1)"
+ },
+ {
+ "password", 8, "NaCl", 4, 1024, 8, 16,
+ {
+ 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00,
+ 0x78, 0x56, 0xe7, 0x19, 0x0d, 0x01, 0xe9, 0xfe,
+ 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30,
+ 0xe7, 0x73, 0x76, 0x63, 0x4b, 0x37, 0x31, 0x62,
+ 0x2e, 0xaf, 0x30, 0xd9, 0x2e, 0x22, 0xa3, 0x88,
+ 0x6f, 0xf1, 0x09, 0x27, 0x9d, 0x98, 0x30, 0xda,
+ 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d,
+ 0x83, 0x60, 0xcb, 0xdf, 0xa2, 0xcc, 0x06, 0x40
+ },
+ "scrypt(\"password\", \"NaCl\", 1024, 8, 16)"
+ },
+ {
+ "pleaseletmein", 13, "SodiumChloride", 14, 16384, 8, 1,
+ {
+ 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48,
+ 0x46, 0x1c, 0x06, 0xcd, 0x81, 0xfd, 0x38, 0xeb,
+ 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e,
+ 0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d, 0xa1, 0xf2,
+ 0xd5, 0x43, 0x29, 0x55, 0x61, 0x3f, 0x0f, 0xcf,
+ 0x62, 0xd4, 0x97, 0x05, 0x24, 0x2a, 0x9a, 0xf9,
+ 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40,
+ 0xdf, 0xcf, 0x01, 0x7b, 0x45, 0x57, 0x58, 0x87
+ },
+ "scrypt(\"pleaseletmein\", \"SodiumChloride\", 16384, 8, 1)"
+ },
+#ifdef LTC_SCRYPT_TEST_1GB
+ /* Define LTC_SCRYPT_TEST_1GB via CFLAGS to enable this test vector
+ * which allocates ~1 GiB of memory and takes a long time to run. */
+ {
+ "pleaseletmein", 13, "SodiumChloride", 14, 1048576, 8, 1,
+ {
+ 0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae,
+ 0xad, 0xdb, 0xbe, 0x09, 0xcf, 0x70, 0xf8, 0x81,
+ 0xec, 0x56, 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d,
+ 0xab, 0xe5, 0xee, 0x98, 0x20, 0xad, 0xaa, 0x47,
+ 0x8e, 0x56, 0xfd, 0x8f, 0x4b, 0xa5, 0xd0, 0x9f,
+ 0xfa, 0x1c, 0x6d, 0x92, 0x7c, 0x40, 0xf4, 0xc3,
+ 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, 0x52, 0xfb,
+ 0xcb, 0xf4, 0x5c, 0x6f, 0xa7, 0x7a, 0x41, 0xa4
+ },
+ "scrypt(\"pleaseletmein\", \"SodiumChloride\", 1048576, 8, 1)"
+ },
+#endif
+};
+
+int scrypt_test(void)
+{
+ unsigned char dk[64];
+ unsigned int n;
+
+ for (n = 0; n < LTC_ARRAY_SIZE(cases); ++n) {
+ DO(scrypt_pbkdf((const unsigned char *)cases[n].password, cases[n].password_len,
+ (const unsigned char *)cases[n].salt, cases[n].salt_len,
+ cases[n].N, cases[n].r, cases[n].p,
+ dk, sizeof(dk)));
+ COMPARE_TESTVECTOR(dk, sizeof(dk), cases[n].expected, sizeof(cases[n].expected), cases[n].name, n);
+ }
+
+ return CRYPT_OK;
+}
+
+#else
+
+int scrypt_test(void)
+{
+ return CRYPT_NOP;
+}
+
+#endif
diff --git a/tests/sources.cmake b/tests/sources.cmake
index d0e5b465d..ae348a644 100644
--- a/tests/sources.cmake
+++ b/tests/sources.cmake
@@ -31,6 +31,7 @@ pkcs_1_test.c
prng_test.c
rotate_test.c
rsa_test.c
+scrypt_test.c
ssh_test.c
store_test.c
test.c
diff --git a/tests/tomcrypt_test.h b/tests/tomcrypt_test.h
index 70514f67f..747ede567 100644
--- a/tests/tomcrypt_test.h
+++ b/tests/tomcrypt_test.h
@@ -44,6 +44,7 @@ int ed25519_test(void);
int ssh_test(void);
int argon2_test(void);
int bcrypt_test(void);
+int scrypt_test(void);
int no_null_termination_check_test(void);
int pk_oid_test(void);
int deprecated_test(void);