summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--cipher/Makefile.am7
-rw-r--r--cipher/kdf-internal.h40
-rw-r--r--cipher/kdf.c48
-rw-r--r--cipher/memxor.c326
-rw-r--r--cipher/memxor.h21
-rw-r--r--cipher/scrypt.c276
-rw-r--r--cipher/scrypt.h66
-rw-r--r--configure.ac43
-rw-r--r--doc/gcrypt.texi2
-rw-r--r--src/gcrypt.h.in2
-rw-r--r--tests/t-kdf.c103
12 files changed, 393 insertions, 544 deletions
diff --git a/NEWS b/NEWS
index 0d75680c..926e531e 100644
--- a/NEWS
+++ b/NEWS
@@ -14,6 +14,8 @@ Noteworthy changes in version 1.6.0 (unreleased)
* Added a random number generator to directly use the system's RNG.
Also added an interface to prefer the use of a specified RNG.
+ * Added support for the SCRYPT algorithm.
+
* Interface changes relative to the 1.5.0 release:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gcry_ac_* REMOVED.
@@ -58,6 +60,7 @@ Noteworthy changes in version 1.6.0 (unreleased)
GCRYMPI_FLAG_IMMUTABLE NEW.
GCRYMPI_FLAG_CONST NEW.
GCRYPT_VERSION_NUMBER NEW.
+ GCRY_KDF_SCRYPT NEW.
Noteworthy changes in version 1.5.0 (2011-06-29)
diff --git a/cipher/Makefile.am b/cipher/Makefile.am
index 5b016f05..51897949 100644
--- a/cipher/Makefile.am
+++ b/cipher/Makefile.am
@@ -31,7 +31,8 @@ AM_CCASFLAGS = $(NOEXECSTACK_FLAGS)
noinst_LTLIBRARIES = libcipher.la
-GCRYPT_MODULES = @GCRYPT_CIPHERS@ @GCRYPT_PUBKEY_CIPHERS@ @GCRYPT_DIGESTS@
+GCRYPT_MODULES = @GCRYPT_CIPHERS@ @GCRYPT_PUBKEY_CIPHERS@ \
+ @GCRYPT_DIGESTS@ @GCRYPT_KDFS@
libcipher_la_DEPENDENCIES = $(GCRYPT_MODULES)
libcipher_la_LIBADD = $(GCRYPT_MODULES)
@@ -39,7 +40,8 @@ libcipher_la_LIBADD = $(GCRYPT_MODULES)
libcipher_la_SOURCES = \
cipher.c cipher-internal.h \
cipher-cbc.c cipher-cfb.c cipher-ofb.c cipher-ctr.c cipher-aeswrap.c \
-pubkey.c md.c kdf.c scrypt.c memxor.c \
+pubkey.c md.c \
+kdf.c kdf-internal.h \
hmac-tests.c \
bithelp.h \
bufhelp.h \
@@ -62,6 +64,7 @@ md5.c \
rijndael.c rijndael-tables.h \
rmd160.c \
rsa.c \
+scrypt.c \
seed.c \
serpent.c \
sha1.c \
diff --git a/cipher/kdf-internal.h b/cipher/kdf-internal.h
new file mode 100644
index 00000000..7079860e
--- /dev/null
+++ b/cipher/kdf-internal.h
@@ -0,0 +1,40 @@
+/* kdf-internal.h - Internal defs for kdf.c
+ * Copyright (C) 2013 g10 Code GmbH
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser general Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GCRY_KDF_INTERNAL_H
+#define GCRY_KDF_INTERNAL_H
+
+/*-- kdf.c --*/
+gpg_err_code_t
+_gcry_kdf_pkdf2 (const void *passphrase, size_t passphraselen,
+ int hashalgo,
+ const void *salt, size_t saltlen,
+ unsigned long iterations,
+ size_t keysize, void *keybuffer);
+
+/*-- scrypt.c --*/
+gcry_err_code_t
+_gcry_kdf_scrypt (const unsigned char *passwd, size_t passwdlen,
+ int algo, int subalgo,
+ const unsigned char *salt, size_t saltlen,
+ unsigned long iterations,
+ size_t dklen, unsigned char *dk);
+
+
+#endif /*GCRY_KDF_INTERNAL_H*/
diff --git a/cipher/kdf.c b/cipher/kdf.c
index 4ea0fb29..da63574f 100644
--- a/cipher/kdf.c
+++ b/cipher/kdf.c
@@ -1,5 +1,6 @@
/* kdf.c - Key Derivation Functions
* Copyright (C) 1998, 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2013 g10 Code GmbH
*
* This file is part of Libgcrypt.
*
@@ -26,7 +27,7 @@
#include "g10lib.h"
#include "cipher.h"
#include "ath.h"
-#include "scrypt.h"
+#include "kdf-internal.h"
/* Transform a passphrase into a suitable key of length KEYSIZE and
@@ -34,7 +35,7 @@
must provide an HASHALGO, a valid ALGO and depending on that algo a
SALT of 8 bytes and the number of ITERATIONS. Code taken from
gnupg/agent/protect.c:hash_passphrase. */
-gpg_err_code_t
+static gpg_err_code_t
openpgp_s2k (const void *passphrase, size_t passphraselen,
int algo, int hashalgo,
const void *salt, size_t saltlen,
@@ -117,11 +118,11 @@ openpgp_s2k (const void *passphrase, size_t passphraselen,
used in HMAC mode. SALT is a salt of length SALTLEN and ITERATIONS
gives the number of iterations. */
gpg_err_code_t
-pkdf2 (const void *passphrase, size_t passphraselen,
- int hashalgo,
- const void *salt, size_t saltlen,
- unsigned long iterations,
- size_t keysize, void *keybuffer)
+_gcry_kdf_pkdf2 (const void *passphrase, size_t passphraselen,
+ int hashalgo,
+ const void *salt, size_t saltlen,
+ unsigned long iterations,
+ size_t keysize, void *keybuffer)
{
gpg_err_code_t ec;
gcry_md_hd_t md;
@@ -139,7 +140,10 @@ pkdf2 (const void *passphrase, size_t passphraselen,
unsigned long iter; /* Current iteration number. */
unsigned int i;
- if (!salt || !saltlen || !iterations || !dklen)
+ /* NWe allow for a saltlen of 0 here to support scrypt. It is not
+ clear whether rfc2898 allows for this this, thus we do a test on
+ saltlen > 0 only in gcry_kdf_derive. */
+ if (!salt || !iterations || !dklen)
return GPG_ERR_INV_VALUE;
hlen = gcry_md_get_algo_dlen (hashalgo);
@@ -239,11 +243,12 @@ gcry_kdf_derive (const void *passphrase, size_t passphraselen,
{
gpg_err_code_t ec;
- if (!passphrase || (!passphraselen && algo != GCRY_KDF_PBKDF2))
+ if (!passphrase)
{
ec = GPG_ERR_INV_DATA;
goto leave;
}
+
if (!keybuffer || !keysize)
{
ec = GPG_ERR_INV_VALUE;
@@ -256,8 +261,11 @@ gcry_kdf_derive (const void *passphrase, size_t passphraselen,
case GCRY_KDF_SIMPLE_S2K:
case GCRY_KDF_SALTED_S2K:
case GCRY_KDF_ITERSALTED_S2K:
- ec = openpgp_s2k (passphrase, passphraselen, algo, subalgo,
- salt, saltlen, iterations, keysize, keybuffer);
+ if (!passphraselen)
+ ec = GPG_ERR_INV_DATA;
+ else
+ ec = openpgp_s2k (passphrase, passphraselen, algo, subalgo,
+ salt, saltlen, iterations, keysize, keybuffer);
break;
case GCRY_KDF_PBKDF1:
@@ -265,12 +273,22 @@ gcry_kdf_derive (const void *passphrase, size_t passphraselen,
break;
case GCRY_KDF_PBKDF2:
- ec = pkdf2 (passphrase, passphraselen, subalgo,
- salt, saltlen, iterations, keysize, keybuffer);
+ if (!saltlen)
+ ec = GPG_ERR_INV_VALUE;
+ else
+ ec = _gcry_kdf_pkdf2 (passphrase, passphraselen, subalgo,
+ salt, saltlen, iterations, keysize, keybuffer);
break;
+
+ case 41:
case GCRY_KDF_SCRYPT:
- ec = scrypt (passphrase, passphraselen, subalgo,
- salt, saltlen, iterations, keysize, keybuffer);
+#if USE_SCRYPT
+ ec = _gcry_kdf_scrypt (passphrase, passphraselen, algo, subalgo,
+ salt, saltlen, iterations, keysize, keybuffer);
+#else
+ ec = GPG_ERR_UNSUPPORTED_ALGORITHM;
+#endif /*USE_SCRYPT*/
+ break;
default:
ec = GPG_ERR_UNKNOWN_ALGORITHM;
diff --git a/cipher/memxor.c b/cipher/memxor.c
deleted file mode 100644
index 74307f0c..00000000
--- a/cipher/memxor.c
+++ /dev/null
@@ -1,326 +0,0 @@
-/* memxor.c
- *
- *
- * This file is part of Libgcrypt.
- * Adapted from the nettle, low-level cryptographics library for
- * libgcrypt by Christian Grothoff; original license:
- */
-
-/*
- * Copyright (C) 1991, 1993, 1995 Free Software Foundation, Inc.
- * Copyright (C) 2010 Niels Möller
- *
- * The nettle library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2.1 of the License, or (at your
- * option) any later version.
- *
- * The nettle library is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with the nettle library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02111-1301, USA.
- */
-
-/* Implementation inspired by memcmp in glibc, contributed to the FSF
- by Torbjorn Granlund.
- */
-
-#include <config.h>
-#include <limits.h>
-#include <stdint.h>
-#include "memxor.h"
-
-typedef unsigned long int word_t;
-
-// FIXME: need configure test for this hack...
-#define SIZEOF_LONG 8
-
-#if SIZEOF_LONG & (SIZEOF_LONG - 1)
-#error Word size must be a power of two
-#endif
-
-#define ALIGN_OFFSET(p) ((uintptr_t) (p) % sizeof(word_t))
-
-#ifndef WORDS_BIGENDIAN
-#define MERGE(w0, sh_1, w1, sh_2) (((w0) >> (sh_1)) | ((w1) << (sh_2)))
-#else
-#define MERGE(w0, sh_1, w1, sh_2) (((w0) << (sh_1)) | ((w1) >> (sh_2)))
-#endif
-
-#define WORD_T_THRESH 16
-
-/* XOR word-aligned areas. n is the number of words, not bytes. */
-static void
-memxor_common_alignment (word_t *dst, const word_t *src, size_t n)
-{
- /* FIXME: Require n > 0? */
- /* FIXME: Unroll four times, like memcmp? Probably not worth the
- effort. */
-
- if (n & 1)
- {
- *dst++ ^= *src++;
- n--;
- }
- for (; n >= 2; dst += 2, src += 2, n -= 2)
- {
- dst[0] ^= src[0];
- dst[1] ^= src[1];
- }
-}
-
-/* XOR *un-aligned* src-area onto aligned dst area. n is number of
- words, not bytes. Assumes we can read complete words at the start
- and end of the src operand. */
-static void
-memxor_different_alignment (word_t *dst, const uint8_t *src, size_t n)
-{
- size_t i;
- int shl, shr;
- const word_t *src_word;
- unsigned offset = ALIGN_OFFSET (src);
- word_t s0, s1;
-
- shl = CHAR_BIT * offset;
- shr = CHAR_BIT * (sizeof(word_t) - offset);
-
- src_word = (const word_t *) ((uintptr_t) src & -SIZEOF_LONG);
-
- /* FIXME: Unroll four times, like memcmp? */
- i = n & 1;
- s0 = src_word[i];
- if (i)
- {
- s1 = src_word[0];
- dst[0] ^= MERGE (s1, shl, s0, shr);
- }
-
- for (; i < n; i += 2)
- {
- s1 = src_word[i+1];
- dst[i] ^= MERGE(s0, shl, s1, shr);
- s0 = src_word[i+2];
- dst[i+1] ^= MERGE(s1, shl, s0, shr);
- }
-}
-
-/* Performance, Intel SU1400 (x86_64): 0.25 cycles/byte aligned, 0.45
- cycles/byte unaligned. */
-
-/* XOR LEN bytes starting at SRCADDR onto DESTADDR. Result undefined
- if the source overlaps with the destination. Return DESTADDR. */
-uint8_t *
-memxor(uint8_t *dst, const uint8_t *src, size_t n)
-{
- uint8_t *orig_dst = dst;
-
- if (n >= WORD_T_THRESH)
- {
- /* There are at least some bytes to compare. No need to test
- for N == 0 in this alignment loop. */
- while (ALIGN_OFFSET (dst))
- {
- *dst++ ^= *src++;
- n--;
- }
- if (ALIGN_OFFSET (src))
- memxor_different_alignment ((word_t *) dst, src, n / sizeof(word_t));
- else
- memxor_common_alignment ((word_t *) dst, (const word_t *) src, n / sizeof(word_t));
-
- dst += n & -SIZEOF_LONG;
- src += n & -SIZEOF_LONG;
- n = n & (SIZEOF_LONG - 1);
- }
- for (; n > 0; n--)
- *dst++ ^= *src++;
-
- return orig_dst;
-}
-
-
-/* XOR word-aligned areas. n is the number of words, not bytes. */
-static void
-memxor3_common_alignment (word_t *dst,
- const word_t *a, const word_t *b, size_t n)
-{
- /* FIXME: Require n > 0? */
- while (n-- > 0)
- dst[n] = a[n] ^ b[n];
-}
-
-static void
-memxor3_different_alignment_b (word_t *dst,
- const word_t *a, const uint8_t *b, unsigned offset, size_t n)
-{
- int shl, shr;
- const word_t *b_word;
-
- word_t s0, s1;
-
- shl = CHAR_BIT * offset;
- shr = CHAR_BIT * (sizeof(word_t) - offset);
-
- b_word = (const word_t *) ((uintptr_t) b & -SIZEOF_LONG);
-
- if (n & 1)
- {
- n--;
- s1 = b_word[n];
- s0 = b_word[n+1];
- dst[n] = a[n] ^ MERGE (s1, shl, s0, shr);
- }
- else
- s1 = b_word[n];
-
- while (n > 0)
- {
- n -= 2;
- s0 = b_word[n+1];
- dst[n+1] = a[n+1] ^ MERGE(s0, shl, s1, shr);
- s1 = b_word[n];
- dst[n] = a[n] ^ MERGE(s1, shl, s0, shr);
- }
-}
-
-static void
-memxor3_different_alignment_ab (word_t *dst,
- const uint8_t *a, const uint8_t *b,
- unsigned offset, size_t n)
-{
- int shl, shr;
- const word_t *a_word;
- const word_t *b_word;
-
- word_t s0, s1;
-
- shl = CHAR_BIT * offset;
- shr = CHAR_BIT * (sizeof(word_t) - offset);
-
- a_word = (const word_t *) ((uintptr_t) a & -SIZEOF_LONG);
- b_word = (const word_t *) ((uintptr_t) b & -SIZEOF_LONG);
-
- if (n & 1)
- {
- n--;
- s1 = a_word[n] ^ b_word[n];
- s0 = a_word[n+1] ^ b_word[n+1];
- dst[n] = MERGE (s1, shl, s0, shr);
- }
- else
- s1 = a_word[n] ^ b_word[n];
-
- while (n > 0)
- {
- n -= 2;
- s0 = a_word[n+1] ^ b_word[n+1];
- dst[n+1] = MERGE(s0, shl, s1, shr);
- s1 = a_word[n] ^ b_word[n];
- dst[n] = MERGE(s1, shl, s0, shr);
- }
-}
-
-static void
-memxor3_different_alignment_all (word_t *dst,
- const uint8_t *a, const uint8_t *b,
- unsigned a_offset, unsigned b_offset,
- size_t n)
-{
- int al, ar, bl, br;
- const word_t *a_word;
- const word_t *b_word;
-
- word_t a0, a1, b0, b1;
-
- al = CHAR_BIT * a_offset;
- ar = CHAR_BIT * (sizeof(word_t) - a_offset);
- bl = CHAR_BIT * b_offset;
- br = CHAR_BIT * (sizeof(word_t) - b_offset);
-
- a_word = (const word_t *) ((uintptr_t) a & -SIZEOF_LONG);
- b_word = (const word_t *) ((uintptr_t) b & -SIZEOF_LONG);
-
- if (n & 1)
- {
- n--;
- a1 = a_word[n]; a0 = a_word[n+1];
- b1 = b_word[n]; b0 = b_word[n+1];
-
- dst[n] = MERGE (a1, al, a0, ar) ^ MERGE (b1, bl, b0, br);
- }
- else
- {
- a1 = a_word[n];
- b1 = b_word[n];
- }
-
- while (n > 0)
- {
- n -= 2;
- a0 = a_word[n+1]; b0 = b_word[n+1];
- dst[n+1] = MERGE(a0, al, a1, ar) ^ MERGE(b0, bl, b1, br);
- a1 = a_word[n]; b1 = b_word[n];
- dst[n] = MERGE(a1, al, a0, ar) ^ MERGE(b1, bl, b0, br);
- }
-}
-
-/* Current implementation processes data in descending order, to
- support overlapping operation with one of the sources overlapping
- the start of the destination area. This feature is used only
- internally by cbc decrypt, and it is not advertised or documented
- to nettle users. */
-uint8_t *
-memxor3(uint8_t *dst, const uint8_t *a, const uint8_t *b, size_t n)
-{
- if (n >= WORD_T_THRESH)
- {
- unsigned i;
- unsigned a_offset;
- unsigned b_offset;
- size_t nwords;
-
- for (i = ALIGN_OFFSET(dst + n); i > 0; i--)
- {
- n--;
- dst[n] = a[n] ^ b[n];
- }
-
- a_offset = ALIGN_OFFSET(a + n);
- b_offset = ALIGN_OFFSET(b + n);
-
- nwords = n / sizeof (word_t);
- n %= sizeof (word_t);
-
- if (a_offset == b_offset)
- {
- if (!a_offset)
- memxor3_common_alignment((word_t *) (dst + n),
- (const word_t *) (a + n),
- (const word_t *) (b + n), nwords);
- else
- memxor3_different_alignment_ab((word_t *) (dst + n),
- a + n, b + n, a_offset,
- nwords);
- }
- else if (!a_offset)
- memxor3_different_alignment_b((word_t *) (dst + n),
- (const word_t *) (a + n), b + n,
- b_offset, nwords);
- else if (!b_offset)
- memxor3_different_alignment_b((word_t *) (dst + n),
- (const word_t *) (b + n), a + n,
- a_offset, nwords);
- else
- memxor3_different_alignment_all((word_t *) (dst + n), a + n, b + n,
- a_offset, b_offset, nwords);
- }
- while (n-- > 0)
- dst[n] = a[n] ^ b[n];
-
- return dst;
-}
diff --git a/cipher/memxor.h b/cipher/memxor.h
deleted file mode 100644
index f308155f..00000000
--- a/cipher/memxor.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/* memxor.h
- *
- */
-
-#ifndef MEMXOR_H_INCLUDED
-#define MEMXOR_H_INCLUDED
-
-#include <stdlib.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-uint8_t *memxor(uint8_t *dst, const uint8_t *src, size_t n);
-uint8_t *memxor3(uint8_t *dst, const uint8_t *a, const uint8_t *b, size_t n);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* MEMXOR_H_INCLUDED */
diff --git a/cipher/scrypt.c b/cipher/scrypt.c
index 45ab4b31..06196d62 100644
--- a/cipher/scrypt.c
+++ b/cipher/scrypt.c
@@ -1,6 +1,22 @@
/* scrypt.c - Scrypt password-based key derivation function.
+ * Copyright (C) 2012 Simon Josefsson
+ * Copyright (C) 2013 Christian Grothoff
+ * Copyright (C) 2013 g10 Code GmbH
*
* This file is part of Libgcrypt.
+ *
+ * Libgcrypt is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser general Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * Libgcrypt is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* Adapted from the nettle, low-level cryptographics library for
@@ -30,39 +46,40 @@
#include <string.h>
#include "g10lib.h"
-#include "scrypt.h"
-#include "memxor.h"
-
+#include "kdf-internal.h"
+#include "bufhelp.h"
+/* We really need a 64 bit type for this code. */
+#ifdef HAVE_U64_TYPEDEF
-#define _SALSA20_INPUT_LENGTH 16
+#define SALSA20_INPUT_LENGTH 16
#define ROTL32(n,x) (((x)<<(n)) | ((x)>>(32-(n))))
/* Reads a 64-bit integer, in network, big-endian, byte order */
#define READ_UINT64(p) \
-( (((uint64_t) (p)[0]) << 56) \
- | (((uint64_t) (p)[1]) << 48) \
- | (((uint64_t) (p)[2]) << 40) \
- | (((uint64_t) (p)[3]) << 32) \
- | (((uint64_t) (p)[4]) << 24) \
- | (((uint64_t) (p)[5]) << 16) \
- | (((uint64_t) (p)[6]) << 8) \
- | ((uint64_t) (p)[7]))
+( (((u64) (p)[0]) << 56) \
+ | (((u64) (p)[1]) << 48) \
+ | (((u64) (p)[2]) << 40) \
+ | (((u64) (p)[3]) << 32) \
+ | (((u64) (p)[4]) << 24) \
+ | (((u64) (p)[5]) << 16) \
+ | (((u64) (p)[6]) << 8) \
+ | ((u64) (p)[7]))
/* And the other, little-endian, byteorder */
#define LE_READ_UINT64(p) \
-( (((uint64_t) (p)[7]) << 56) \
- | (((uint64_t) (p)[6]) << 48) \
- | (((uint64_t) (p)[5]) << 40) \
- | (((uint64_t) (p)[4]) << 32) \
- | (((uint64_t) (p)[3]) << 24) \
- | (((uint64_t) (p)[2]) << 16) \
- | (((uint64_t) (p)[1]) << 8) \
- | ((uint64_t) (p)[0]))
+( (((u64) (p)[7]) << 56) \
+ | (((u64) (p)[6]) << 48) \
+ | (((u64) (p)[5]) << 40) \
+ | (((u64) (p)[4]) << 32) \
+ | (((u64) (p)[3]) << 24) \
+ | (((u64) (p)[2]) << 16) \
+ | (((u64) (p)[1]) << 8) \
+ | ((u64) (p)[0]))
@@ -83,9 +100,9 @@
static void
-_salsa20_core(uint32_t *dst, const uint32_t *src, unsigned rounds)
+_salsa20_core(u32 *dst, const u32 *src, unsigned rounds)
{
- uint32_t x[_SALSA20_INPUT_LENGTH];
+ u32 x[SALSA20_INPUT_LENGTH];
unsigned i;
assert ( (rounds & 1) == 0);
@@ -104,33 +121,36 @@ _salsa20_core(uint32_t *dst, const uint32_t *src, unsigned rounds)
QROUND(x[15], x[12], x[13], x[14]);
}
- for (i = 0; i < _SALSA20_INPUT_LENGTH; i++)
+ for (i = 0; i < SALSA20_INPUT_LENGTH; i++)
{
- uint32_t t = x[i] + src[i];
+ u32 t = x[i] + src[i];
dst[i] = LE_SWAP32 (t);
}
}
static void
-_scryptBlockMix (uint32_t r, uint8_t *B, uint8_t *tmp2)
+_scryptBlockMix (u32 r, unsigned char *B, unsigned char *tmp2)
{
- uint64_t i;
- uint8_t *X = tmp2;
- uint8_t *Y = tmp2 + 64;
+ u64 i;
+ unsigned char *X = tmp2;
+ unsigned char *Y = tmp2 + 64;
#if 0
- for (i = 0; i < 2 * r; i++)
+ if (r == 1)
{
- size_t j;
- printf ("B[%d]: ", i);
- for (j = 0; j < 64; j++)
- {
- if (j % 4 == 0)
- printf (" ");
- printf ("%02x", B[i * 64 + j]);
- }
- printf ("\n");
+ for (i = 0; i < 2 * r; i++)
+ {
+ size_t j;
+ printf ("B[%d] = ", (int)i);
+ for (j = 0; j < 64; j++)
+ {
+ if (j && !(j % 16))
+ printf ("\n ");
+ printf (" %02x", B[i * 64 + j]);
+ }
+ putchar ('\n');
+ }
}
#endif
@@ -141,10 +161,10 @@ _scryptBlockMix (uint32_t r, uint8_t *B, uint8_t *tmp2)
for (i = 0; i <= 2 * r - 1; i++)
{
/* T = X xor B[i] */
- memxor(X, &B[i * 64], 64);
+ buf_xor(X, X, &B[i * 64], 64);
/* X = Salsa (T) */
- _salsa20_core (X, X, 8);
+ _salsa20_core ((u32*)X, (u32*)X, 8);
/* Y[i] = X */
memcpy (&Y[i * 64], X, 64);
@@ -157,38 +177,43 @@ _scryptBlockMix (uint32_t r, uint8_t *B, uint8_t *tmp2)
}
#if 0
- for (i = 0; i < 2 * r; i++)
+ if (r==1)
{
- size_t j;
- printf ("B'[%d]: ", i);
- for (j = 0; j < 64; j++)
- {
- if (j % 4 == 0)
- printf (" ");
- printf ("%02x", B[i * 64 + j]);
- }
- printf ("\n");
+ for (i = 0; i < 2 * r; i++)
+ {
+ size_t j;
+ printf ("B'[%d] =", (int)i);
+ for (j = 0; j < 64; j++)
+ {
+ if (j && !(j % 16))
+ printf ("\n ");
+ printf (" %02x", B[i * 64 + j]);
+ }
+ putchar ('\n');
+ }
}
#endif
}
static void
-_scryptROMix (uint32_t r, uint8_t *B, uint64_t N,
- uint8_t *tmp1, uint8_t *tmp2)
+_scryptROMix (u32 r, unsigned char *B, u64 N,
+ unsigned char *tmp1, unsigned char *tmp2)
{
- uint8_t *X = B, *T = B;
- uint64_t i;
+ unsigned char *X = B, *T = B;
+ u64 i;
#if 0
- printf ("B: ");
- for (i = 0; i < 128 * r; i++)
+ if (r == 1)
{
- size_t j;
- if (i % 4 == 0)
- printf (" ");
- printf ("%02x", B[i]);
+ printf ("B = ");
+ for (i = 0; i < 128 * r; i++)
+ {
+ if (i && !(i % 16))
+ printf ("\n ");
+ printf (" %02x", B[i]);
+ }
+ putchar ('\n');
}
- printf ("\n");
#endif
/* for i = 0 to N - 1 do */
@@ -204,81 +229,118 @@ _scryptROMix (uint32_t r, uint8_t *B, uint64_t N,
/* for i = 0 to N - 1 do */
for (i = 0; i <= N - 1; i++)
{
- uint64_t j;
+ u64 j;
/* j = Integerify (X) mod N */
j = LE_READ_UINT64 (&X[128 * r - 64]) % N;
/* T = X xor V[j] */
- memxor (T, &tmp1[j * 128 * r], 128 * r);
+ buf_xor (T, T, &tmp1[j * 128 * r], 128 * r);
/* X = scryptBlockMix (T) */
_scryptBlockMix (r, T, tmp2);
}
#if 0
- printf ("B': ");
- for (i = 0; i < 128 * r; i++)
+ if (r == 1)
{
- size_t j;
- if (i % 4 == 0)
- printf (" ");
- printf ("%02x", B[i]);
+ printf ("B' =");
+ for (i = 0; i < 128 * r; i++)
+ {
+ if (i && !(i % 16))
+ printf ("\n ");
+ printf (" %02x", B[i]);
+ }
+ putchar ('\n');
}
- printf ("\n");
#endif
}
/**
*/
gcry_err_code_t
-scrypt (const uint8_t * passwd, size_t passwdlen,
- int subalgo,
- const uint8_t * salt, size_t saltlen,
- unsigned long iterations,
- size_t dkLen, uint8_t * DK)
+_gcry_kdf_scrypt (const unsigned char *passwd, size_t passwdlen,
+ int algo, int subalgo,
+ const unsigned char *salt, size_t saltlen,
+ unsigned long iterations,
+ size_t dkLen, unsigned char *DK)
{
- /* XXX sanity-check parameters */
- uint64_t N = subalgo; /* CPU/memory cost paramter */
- uint32_t r = 8; /* block size, should be sane enough */
- uint32_t p = iterations; /* parallelization parameter */
-
- uint32_t i;
- uint8_t *B;
- uint8_t *tmp1;
- uint8_t *tmp2;
-
+ u64 N = subalgo; /* CPU/memory cost paramter. */
+ u32 r; /* Block size. */
+ u32 p = iterations; /* Parallelization parameter. */
+
+ gpg_err_code_t ec;
+ u32 i;
+ unsigned char *B = NULL;
+ unsigned char *tmp1 = NULL;
+ unsigned char *tmp2 = NULL;
+ size_t r128;
+ size_t nbytes;
+
+ if (subalgo < 1 || !iterations)
+ return GPG_ERR_INV_VALUE;
+
+ if (algo == GCRY_KDF_SCRYPT)
+ r = 8;
+ else if (algo == 41) /* Hack to allow the use of all test vectors. */
+ r = 1;
+ else
+ return GPG_ERR_UNKNOWN_ALGORITHM;
+
+ r128 = r * 128;
+ if (r128 / 128 != r)
+ return GPG_ERR_ENOMEM;
- B = malloc (p * 128 * r);
- if (B == NULL)
+ nbytes = p * r128;
+ if (r128 && nbytes / r128 != p)
return GPG_ERR_ENOMEM;
- tmp1 = malloc (N * 128 * r);
- if (tmp1 == NULL)
- {
- free (B);
+ nbytes = N * r128;
+ if (r128 && nbytes / r128 != N)
return GPG_ERR_ENOMEM;
- }
- tmp2 = malloc (64 + 128 * r);
- if (tmp2 == NULL)
- {
- free (B);
- free (tmp1);
+ nbytes = 64 + r128;
+ if (nbytes < r128)
return GPG_ERR_ENOMEM;
- }
- pkdf2 (passwd, passwdlen, GCRY_MD_SHA256, salt, saltlen, 1 /* iterations */, p * 128 * r, B);
+ B = gcry_malloc (p * r128);
+ if (!B)
+ {
+ ec = gpg_err_code_from_syserror ();
+ goto leave;
+ }
- for (i = 0; i < p; i++)
- _scryptROMix (r, &B[i * 128 * r], N, tmp1, tmp2);
+ tmp1 = gcry_malloc (N * r128);
+ if (!tmp1)
+ {
+ ec = gpg_err_code_from_syserror ();
+ goto leave;
+ }
+
+ tmp2 = gcry_malloc (64 + r128);
+ if (!tmp2)
+ {
+ ec = gpg_err_code_from_syserror ();
+ goto leave;
+ }
- for (i = 0; i < p; i++)
- pkdf2 (passwd, passwdlen, GCRY_MD_SHA256, B, p * 128 * r, 1 /* iterations */, dkLen, DK);
+ ec = _gcry_kdf_pkdf2 (passwd, passwdlen, GCRY_MD_SHA256, salt, saltlen,
+ 1 /* iterations */, p * r128, B);
- free (tmp2);
- free (tmp1);
- free (B);
+ for (i = 0; !ec && i < p; i++)
+ _scryptROMix (r, &B[i * r128], N, tmp1, tmp2);
- return 0;
+ for (i = 0; !ec && i < p; i++)
+ ec = _gcry_kdf_pkdf2 (passwd, passwdlen, GCRY_MD_SHA256, B, p * r128,
+ 1 /* iterations */, dkLen, DK);
+
+ leave:
+ gcry_free (tmp2);
+ gcry_free (tmp1);
+ gcry_free (B);
+
+ return ec;
}
+
+
+#endif /* HAVE_U64_TYPEDEF */
diff --git a/cipher/scrypt.h b/cipher/scrypt.h
deleted file mode 100644
index e0c8df92..00000000
--- a/cipher/scrypt.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* scrypt.h - Scrypt password-based key derivation function.
- *
- * This file is part of Libgcrypt.
- */
-
-/* Adapted from the nettle, low-level cryptographics library for
- * libgcrypt by Christian Grothoff; original license:
- *
- * Copyright (C) 2012 Simon Josefsson
- *
- * The nettle library is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation; either version 2.1 of the License, or (at your
- * option) any later version.
- *
- * The nettle library is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with the nettle library; see the file COPYING.LIB. If not, write to
- * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02111-1301, USA.
- */
-
-#ifndef SCRYPT_H_INCLUDED
-#define SCRYPT_H_INCLUDED
-
-#ifdef __cplusplus
-extern "C"
-{
-#endif
-
-#include <stdint.h>
-#include "g10lib.h"
-#include "cipher.h"
-
-
-/* Transform a passphrase into a suitable key of length KEYSIZE and
- store this key in the caller provided buffer KEYBUFFER. The caller
- must provide PRFALGO which indicates the pseudorandom function to
- use: This shall be the algorithms id of a hash algorithm; it is
- used in HMAC mode. SALT is a salt of length SALTLEN and ITERATIONS
- gives the number of iterations; implemented in 'kdf.c', used by
- scrypt.c */
-gpg_err_code_t
-pkdf2 (const void *passphrase, size_t passphraselen,
- int hashalgo,
- const void *salt, size_t saltlen,
- unsigned long iterations,
- size_t keysize, void *keybuffer);
-
-
-gcry_err_code_t
-scrypt (const uint8_t * passwd, size_t passwdlen,
- int subalgo,
- const uint8_t * salt, size_t saltlen,
- unsigned long iterations,
- size_t dkLen, uint8_t * DK);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* SCRYPT_H_INCLUDED */
diff --git a/configure.ac b/configure.ac
index cebf4b9e..c5973896 100644
--- a/configure.ac
+++ b/configure.ac
@@ -199,6 +199,11 @@ available_digests="crc md4 md5 rmd160 sha1 sha256"
available_digests_64="sha512 tiger whirlpool"
enabled_digests=""
+# Definitions for kdfs (optional ones)
+available_kdfs="s2k pkdf2"
+available_kdfs_64="scrypt"
+enabled_kdfs=""
+
# Definitions for random modules.
available_random_modules="linux egd unix"
auto_random_modules="$available_random_modules"
@@ -351,9 +356,11 @@ if test "$ac_cv_sizeof_unsigned_int" != "8" \
&& test "$ac_cv_sizeof_unsigned_long" != "8" \
&& test "$ac_cv_sizeof_unsigned_long_long" != "8" \
&& test "$ac_cv_sizeof_uint64_t" != "8"; then
- AC_MSG_WARN([No 64-bit types. Disabling TIGER/192, SHA-384, and SHA-512])
+ AC_MSG_WARN([No 64-bit types. Disabling TIGER/192, SCRYPT, SHA-384, \
+ and SHA-512])
else
available_digests="$available_digests $available_digests_64"
+ available_kdfs="$available_kdfs $available_kdfs_64"
fi
# If not specified otherwise, all available algorithms will be
@@ -361,6 +368,7 @@ fi
default_ciphers="$available_ciphers"
default_pubkey_ciphers="$available_pubkey_ciphers"
default_digests="$available_digests"
+default_kdfs="$available_kdfs"
# Substitutions to set generated files in a Emacs buffer to read-only.
AC_SUBST(emacs_local_vars_begin, ['Local Variables:'])
@@ -431,6 +439,26 @@ for digest in $enabled_digests; do
done
AC_MSG_RESULT([$enabled_digests])
+# Implementation of the --enable-kdfs switch.
+AC_ARG_ENABLE(kdfs,
+ AC_HELP_STRING([--enable-kfds=kdfs],
+ [select the KDFs to include]),
+ [enabled_kdfs=`echo $enableval | tr ',:' ' ' | tr '[A-Z]' '[a-z]'`],
+ [enabled_kdfs=""])
+if test "x$enabled_kdfs" = "x" \
+ -o "$enabled_kdfs" = "yes" \
+ -o "$enabled_kdfs" = "no"; then
+ enabled_kdfs=$default_kdfs
+fi
+AC_MSG_CHECKING([which key derivation functions to include])
+for kdf in $enabled_kdfs; do
+ LIST_MEMBER($kdf, $available_kdfs)
+ if test "$found" = "0"; then
+ AC_MSG_ERROR([unsupported key derivation function specified])
+ fi
+done
+AC_MSG_RESULT([$enabled_kdfs])
+
# Implementation of the --enable-random switch.
AC_ARG_ENABLE(random,
AC_HELP_STRING([--enable-random=name],
@@ -1145,7 +1173,7 @@ fi
# Define conditional sources and config.h symbols depending on the
-# selected ciphers, pubkey-ciphers, digests and random modules.
+# selected ciphers, pubkey-ciphers, digests, kdfs, and random modules.
LIST_MEMBER(arcfour, $enabled_ciphers)
if test "$found" = "1"; then
@@ -1291,6 +1319,12 @@ GCRYPT_DIGESTS="$GCRYPT_DIGESTS rmd160.lo sha1.lo"
AC_DEFINE(USE_RMD160, 1, [Defined if this module should be included])
AC_DEFINE(USE_SHA1, 1, [Defined if this module should be included])
+LIST_MEMBER(scrypt, $enabled_kdfs)
+if test "$found" = "1" ; then
+ GCRYPT_KDFS="$GCRYPT_KDFS scrypt.lo"
+ AC_DEFINE(USE_SCRYPT, 1, [Defined if this module should be included])
+fi
+
LIST_MEMBER(linux, $random_modules)
if test "$found" = "1" ; then
GCRYPT_RANDOM="$GCRYPT_RANDOM rndlinux.lo"
@@ -1327,6 +1361,7 @@ fi
AC_SUBST([GCRYPT_CIPHERS])
AC_SUBST([GCRYPT_PUBKEY_CIPHERS])
AC_SUBST([GCRYPT_DIGESTS])
+AC_SUBST([GCRYPT_KDFS])
AC_SUBST([GCRYPT_RANDOM])
AC_SUBST(LIBGCRYPT_CIPHERS, $enabled_ciphers)
@@ -1344,6 +1379,9 @@ AC_DEFINE_UNQUOTED(LIBGCRYPT_PUBKEY_CIPHERS, "$tmp",
tmp=`echo "$enabled_digests" | tr ' ' : `
AC_DEFINE_UNQUOTED(LIBGCRYPT_DIGESTS, "$tmp",
[List of available digest algorithms])
+tmp=`echo "$enabled_kdfs" | tr ' ' : `
+AC_DEFINE_UNQUOTED(LIBGCRYPT_KDFS, "$tmp",
+ [List of available KDF algorithms])
#
@@ -1428,6 +1466,7 @@ GCRY_MSG_SHOW([Platform: ],[$PRINTABLE_OS_NAME ($host)])
GCRY_MSG_SHOW([Hardware detection module:],[$detection_module])
GCRY_MSG_WRAP([Enabled cipher algorithms:],[$enabled_ciphers])
GCRY_MSG_WRAP([Enabled digest algorithms:],[$enabled_digests])
+GCRY_MSG_WRAP([Enabled kdf algorithms: ],[$enabled_kdfs])
GCRY_MSG_WRAP([Enabled pubkey algorithms:],[$enabled_pubkey_ciphers])
GCRY_MSG_SHOW([Random number generator: ],[$random])
GCRY_MSG_SHOW([Using linux capabilities: ],[$use_capabilities])
diff --git a/doc/gcrypt.texi b/doc/gcrypt.texi
index fe040f02..8f277c2a 100644
--- a/doc/gcrypt.texi
+++ b/doc/gcrypt.texi
@@ -3174,7 +3174,7 @@ The PKCS#5 Passphrase Based Key Derivation Function number 2.
@item GCRY_KDF_SCRYPT
The SCRYPT Key Derivation Function. The subalgorithm is used to specify
-the CPU/memory cost paramter N, and the number of iterations
+the CPU/memory cost parameter N, and the number of iterations
is used for the parallelization parameter p. The block size is fixed
at 8 in the current implementation.
diff --git a/src/gcrypt.h.in b/src/gcrypt.h.in
index dff0e0b1..3b37e19c 100644
--- a/src/gcrypt.h.in
+++ b/src/gcrypt.h.in
@@ -1194,7 +1194,7 @@ enum gcry_kdf_algos
GCRY_KDF_ITERSALTED_S2K = 19,
GCRY_KDF_PBKDF1 = 33,
GCRY_KDF_PBKDF2 = 34,
- GCRY_KDF_SCRYPT = 35
+ GCRY_KDF_SCRYPT = 48
};
/* Derive a key from a passphrase. */
diff --git a/tests/t-kdf.c b/tests/t-kdf.c
index 06c00263..50deba08 100644
--- a/tests/t-kdf.c
+++ b/tests/t-kdf.c
@@ -35,6 +35,7 @@
/* Program option flags. */
static int verbose;
+static int debug;
static int error_count;
static void
@@ -925,7 +926,7 @@ check_pbkdf2 (void)
20,
"\x13\x3a\x4c\xe8\x37\xb4\xd2\x52\x1e\xe2"
"\xbf\x03\xe1\x1c\x71\xca\x79\x4e\x07\x97"
- },
+ }
};
int tvidx;
gpg_error_t err;
@@ -957,11 +958,106 @@ check_pbkdf2 (void)
}
+static void
+check_scrypt (void)
+{
+ /* Test vectors are from draft-josefsson-scrypt-kdf-01. */
+ static struct {
+ const char *p; /* Passphrase. */
+ size_t plen; /* Length of P. */
+ const char *salt;
+ size_t saltlen;
+ int parm_n; /* CPU/memory cost. */
+ int parm_r; /* blocksize */
+ unsigned long parm_p; /* parallelization. */
+ int dklen; /* Requested key length. */
+ const char *dk; /* Derived key. */
+ int disabled;
+ } tv[] = {
+ {
+ "", 0,
+ "", 0,
+ 16,
+ 1,
+ 1,
+ 64,
+ "\x77\xd6\x57\x62\x38\x65\x7b\x20\x3b\x19\xca\x42\xc1\x8a\x04\x97"
+ "\xf1\x6b\x48\x44\xe3\x07\x4a\xe8\xdf\xdf\xfa\x3f\xed\xe2\x14\x42"
+ "\xfc\xd0\x06\x9d\xed\x09\x48\xf8\x32\x6a\x75\x3a\x0f\xc8\x1f\x17"
+ "\xe8\xd3\xe0\xfb\x2e\x0d\x36\x28\xcf\x35\xe2\x0c\x38\xd1\x89\x06"
+ },
+ {
+ "password", 8,
+ "NaCl", 4,
+ 1024,
+ 8,
+ 16,
+ 64,
+ "\xfd\xba\xbe\x1c\x9d\x34\x72\x00\x78\x56\xe7\x19\x0d\x01\xe9\xfe"
+ "\x7c\x6a\xd7\xcb\xc8\x23\x78\x30\xe7\x73\x76\x63\x4b\x37\x31\x62"
+ "\x2e\xaf\x30\xd9\x2e\x22\xa3\x88\x6f\xf1\x09\x27\x9d\x98\x30\xda"
+ "\xc7\x27\xaf\xb9\x4a\x83\xee\x6d\x83\x60\xcb\xdf\xa2\xcc\x06\x40"
+ },
+ {
+ "pleaseletmein", 13,
+ "SodiumChloride", 14,
+ 16384,
+ 8,
+ 1,
+ 64,
+ "\x70\x23\xbd\xcb\x3a\xfd\x73\x48\x46\x1c\x06\xcd\x81\xfd\x38\xeb"
+ "\xfd\xa8\xfb\xba\x90\x4f\x8e\x3e\xa9\xb5\x43\xf6\x54\x5d\xa1\xf2"
+ "\xd5\x43\x29\x55\x61\x3f\x0f\xcf\x62\xd4\x97\x05\x24\x2a\x9a\xf9"
+ "\xe6\x1e\x85\xdc\x0d\x65\x1e\x40\xdf\xcf\x01\x7b\x45\x57\x58\x87"
+ },
+ {
+ "pleaseletmein", 13,
+ "SodiumChloride", 14,
+ 1048576,
+ 8,
+ 1,
+ 64,
+ "\x21\x01\xcb\x9b\x6a\x51\x1a\xae\xad\xdb\xbe\x09\xcf\x70\xf8\x81"
+ "\xec\x56\x8d\x57\x4a\x2f\xfd\x4d\xab\xe5\xee\x98\x20\xad\xaa\x47"
+ "\x8e\x56\xfd\x8f\x4b\xa5\xd0\x9f\xfa\x1c\x6d\x92\x7c\x40\xf4\xc3"
+ "\x37\x30\x40\x49\xe8\xa9\x52\xfb\xcb\xf4\x5c\x6f\xa7\x7a\x41\xa4",
+ 2 /* Only in debug mode. */
+ }
+ };
+ int tvidx;
+ gpg_error_t err;
+ unsigned char outbuf[64];
+ int i;
+
+ for (tvidx=0; tvidx < DIM(tv); tvidx++)
+ {
+ if (tv[tvidx].disabled && !(tv[tvidx].disabled == 2 && debug))
+ continue;
+ if (verbose)
+ fprintf (stderr, "checking SCRYPT test vector %d\n", tvidx);
+ assert (tv[tvidx].dklen <= sizeof outbuf);
+ err = gcry_kdf_derive (tv[tvidx].p, tv[tvidx].plen,
+ tv[tvidx].parm_r == 1 ? 41 : GCRY_KDF_SCRYPT,
+ tv[tvidx].parm_n,
+ tv[tvidx].salt, tv[tvidx].saltlen,
+ tv[tvidx].parm_p, tv[tvidx].dklen, outbuf);
+ if (err)
+ fail ("scrypt test %d failed: %s\n", tvidx, gpg_strerror (err));
+ else if (memcmp (outbuf, tv[tvidx].dk, tv[tvidx].dklen))
+ {
+ fail ("scrypt test %d failed: mismatch\n", tvidx);
+ fputs ("got:", stderr);
+ for (i=0; i < tv[tvidx].dklen; i++)
+ fprintf (stderr, " %02x", outbuf[i]);
+ putc ('\n', stderr);
+ }
+ }
+}
+
+
int
main (int argc, char **argv)
{
- int debug = 0;
-
if (argc > 1 && !strcmp (argv[1], "--verbose"))
verbose = 1;
else if (argc > 1 && !strcmp (argv[1], "--debug"))
@@ -977,6 +1073,7 @@ main (int argc, char **argv)
check_openpgp ();
check_pbkdf2 ();
+ check_scrypt ();
return error_count ? 1 : 0;
}