Scrypt

From Crypto++ Wiki
Jump to navigation Jump to search
Scrypt
Documentation
#include <cryptopp/scrypt.h>

Scrypt is a key derivation function and password hashing algorithm created by Colin Percival. Scrypt was the first of the modern "memory hard" algorithms. The algorithm is standardized in RFC 7914, The scrypt Password-Based Key Derivation Function. Also see Stronger Key Derivation via Sequential Memory-Hard Functions and The scrypt key derivation function.

The Scrypt class derives from the KeyDerivationFuction class, which is the base class of all derivation functions. KeyDerivationFuction provides several member functions but the one of interest is DeriveKey. Each derived class will override DeriveKey and provide a specialized implementation for a consistent interface.

The Scrypt algorithm provides three tunable security parameters called cost, blockSize and parallelization. There are some restrictions on how the parameters are combined, and you should examine ValidateParameters for some of the details. The overarching concern is allocation success, so byte* buffer = new byte[cost*blockSize*128]; must not overflow when calculating the size of the buffer.

The algorithm was added at Crypto++ 6.2. The Crypto++ implementation provides OpenMP support for parallel hashing. OpenMP must be enabled when building the Crypto++ library for threading to be available. Also see Issue 613, Add scrypt key derivation function.

The Scrypt implementation took one bug report after it was cut-in. The class over-committed resources when the parallelization parameter was less than the number of OMP threads. The over-commit was fixed at Commit edc7689a7fa4. Also see GitHub Issue 641.

Constructors

Scrypt uses trivial constructors generated by the compiler.

DeriveKey

DeriveKey is the member function of interest for KeyDerivationFuction derived classes. The base class DeriveKey signature is shown below.

size_t DeriveKey(byte *derived, size_t derivedLen,
                 const byte *secret, size_t secretLen,
                 const NameValuePairs& params)

derived is a pointer to the derived key, and derivedLen is the size of the buffer.

secret is a pointer to the key or seed material, and keyLen is the size of the buffer.

params are NameValuePairs used to provide additional configuration information.

Using NameValuePairs to configure an object is an acquired taste, so each KeyDerivationFuction derived object provides a DeriveKey which unrolls the parameters as shown below. The default parameter arguments were taken from RFC 7914.

size_t DeriveKey(byte *derived, size_t derivedLen,
                 const byte *secret, size_t secretLen,
                 const byte *salt, size_t saltLen,
                 word64 cost=2,
                 word64 blockSize=8,
                 word64 parallelization=1)

derived is a pointer to the derived key, and derivedLen is the size of the buffer.

secret is a pointer to the key or seed material, and keyLen is the size of the buffer.

salt is a pointer to additional seed material, and saltLen is the size of the buffer.

cost is the CPU/memory tradeoff.

blockSize is the number of 256-byte blocks.

parallelization is the number of discrete processing parts.

Sample Programs

The following are some sample programs for Scrypt.

Test Vectors

RFC 7914 provides 4 test vectors. Three of them are enabled when running cryptest.exe v. The fourth is disabled because it takes so long to run it appears the test program hangs. The program below id the second test vector from the RFC. The parameters are secret="password", salt="NaCl", cost=1024, blockSize=8, paralellization=16.

$ cat test.cxx
#include "cryptlib.h"
#include "secblock.h"
#include "scrypt.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>

int main()
{
    using namespace CryptoPP;

    SecByteBlock derived(64);
    const byte pass[] = "password";
    const byte salt[] = "NaCl";

    Scrypt scrypt;
    scrypt.DeriveKey(derived, derived.size(), pass, 8, salt, 4, 1024, 8, 16);

    std::cout << "Derived: ";
    StringSource(derived, 16, true, new HexEncoder(new FileSink(std::cout)));
    std::cout << "..." << std::endl;

    return 0;
}

Running the program results in the following output. The known answer from the test vector is fdbabe1c9d347200 7856e7190d01e9fe 7c6ad7cbc8237830 e77376634b373162 2eaf30d92e22a388 6ff109279d9830da c727afb94a83ee6d 8360cbdfa2cc0640.

$ time ./test.exe
Derived: FDBABE1C9D3472007856E7190D01E9FE...

real    0m0.017s
user    0m0.040s
sys     0m0.009s

If you want to call the base class DeriveKey using NameValuePairs then you can use the following code.

std::string pass("password"), salt("NaCl");
word64 cost=1024, blockSize=8, parallelization=16;

AlgorithmParameters params = MakeParameters("Cost", cost)
    ("BlockSize", blockSize)("Parallelization", parallelization)
    ("Salt", ConstByteArrayParameter((const byte*)&salt[0], salt.size()));

SecByteBlock derived(64);
pbkdf.DeriveKey(derived, derived.size(), (const byte*)&pass[0], pass.size(), params);

OpenMP

The following program uses OpenMP to speed up hashing on a Core i5 with four cores. You have to build the library with OpenMP support and the steps are detailed on the OpenMP wiki page. Though the RFC recommends cost=2 and parallelization=1, the program below uses cost=(1<<20) and parallelization=4. 1<<20 is approximately 1 million and it highlights the benefit of OpenMP for this algorithm.

$ cat test.cxx
#include "cryptlib.h"
#include "secblock.h"
#include "scrypt.h"
#include "osrng.h"
#include "files.h"
#include "hex.h"

#include <iostream>
#include <omp.h>

int main()
{
    using namespace CryptoPP;

    int threads = 1;
    #pragma omp parallel
    {
        threads = omp_get_num_threads();
    }
    std::cout << "Threads: " << threads << std::endl;

    AutoSeededRandomPool prng;

    SecByteBlock key(64), salt(16*1024);
    prng.GenerateBlock(key, key.size());
    prng.GenerateBlock(salt, salt.size());

    Scrypt scrypt;
    scrypt.DeriveKey(key, key.size(), key, key.size(), salt, salt.size(), 1<<20, 12, 4);

    std::cout << "Key: ";
    StringSource(key, 16, true, new HexEncoder(new FileSink(std::cout)));
    std::cout << "..." << std::endl;

    return 0;
}

The multithreaded OpenMP version of the program is about 3x faster than the single threaded version. The different results are due to using random parameters. Using the same parameters would produce the same result.

$ time OMP_NUM_THREADS=1 ./test.exe
Threads: 1
Key: 4A433D4B73F5EA27400D6EC001CA3C9E...

real    0m13.516s
user    0m13.061s
sys     0m0.438s

$ time OMP_NUM_THREADS=2 ./test.exe
Threads: 2
Key: 9EF3B82A54E57ADA188E0CD83B80C24A...

real    0m7.889s
user    0m14.684s
sys     0m0.966s

$ time OMP_NUM_THREADS=4 ./test.exe
Threads: 4
Key: 512B6C8C44CCCA1A1E67C990178EB2CC...

real    0m4.869s
user    0m16.618s
sys     0m2.136s

$ time OMP_NUM_THREADS=6 ./test.exe
Threads: 6
Key: 0A23D30D90C4E2212A7DF2FCA8A67412...

real    0m4.831s
user    0m16.634s
sys     0m2.022s

$ time OMP_NUM_THREADS=8 ./test.exe
Threads: 8
Key: 033BE7CE45D127234261905E36761BB9...

real    0m4.894s
user    0m16.705s
sys     0m2.206s

$ time OMP_NUM_THREADS=12 ./test.exe
Threads: 12
Key: A8BC687006F55B67D2F283FBA242736A...

real    0m4.848s
user    0m16.652s
sys     0m2.059s

Downloads

No downloads.