From 46bfcfe74dcf4943cf5c2f8344344a9f0fe0bcfc Mon Sep 17 00:00:00 2001 From: BrownBear <-> Date: Thu, 27 Feb 2014 12:37:23 +0100 Subject: [PATCH] added missing files: romix.[hc]pp --- include/fc/crypto/romix.hpp | 80 ++++++++++++++++++++++++++++++ src/crypto/romix.cpp | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 include/fc/crypto/romix.hpp create mode 100644 src/crypto/romix.cpp diff --git a/include/fc/crypto/romix.hpp b/include/fc/crypto/romix.hpp new file mode 100644 index 0000000..e46398d --- /dev/null +++ b/include/fc/crypto/romix.hpp @@ -0,0 +1,80 @@ +// Most of this file has been ported from EncryptionUtils.cpp from BitcoinArmory: +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright(C) 2011-2013, Armory Technologies, Inc. // +// Distributed under the GNU Affero General Public License (AGPL v3) // +// See LICENSE or http://www.gnu.org/licenses/agpl.html // +// // +//////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////// +// +// For the KDF: +// +// This technique is described in Colin Percival's paper on memory-hard +// key-derivation functions, used to create "scrypt": +// +// http://www.tarsnap.com/scrypt/scrypt.pdf +// +// The goal is to create a key-derivation function that can force a memory +// requirement on the thread applying the KDF. By picking a sequence-length +// of 1,000,000, each thread will require 32 MB of memory to compute the keys, +// which completely disarms GPUs of their massive parallelization capabilities +// (for maximum parallelization, the kernel must use less than 1-2 MB/thread) +// +// Even with less than 1,000,000 hashes, as long as it requires more than 64 +// kB of memory, a GPU will have to store the computed lookup tables in global +// memory, which is extremely slow for random lookup. As a result, GPUs are +// no better (and possibly much worse) than a CPU for brute-forcing the passwd +// +// This KDF is actually the ROMIX algorithm described on page 6 of Colin's +// paper. This was chosen because it is the simplest technique that provably +// achieves the goal of being secure, and memory-hard. +// +// The computeKdfParams method well test the speed of the system it is running +// on, and try to pick the largest memory-size the system can compute in less +// than 0.25s (or specified target). +// +// +// NOTE: If you are getting an error about invalid argument types, from python, +// it is usually because you passed in a BinaryData/Python-string instead +// of a SecureBinaryData object +// +//////////////////////////////////////////////////////////////////////////////// + +#pragma once +#include + +namespace fc { +//////////////////////////////////////////////////////////////////////////////// +// A memory-bound key-derivation function -- uses a variation of Colin +// Percival's ROMix algorithm: http://www.tarsnap.com/scrypt/scrypt.pdf +// +// The computeKdfParams method takes in a target time, T, for computation +// on the computer executing the test. The final KDF should take somewhere +// between T/2 and T seconds. + class romix + { + public: + romix(u_int32_t memReqts, u_int32_t numIter, std::string salt); + + std::string deriveKey_OneIter(std::string const & password); + std::string deriveKey(std::string const & password); + + private: + u_int32_t hashOutputBytes_; + u_int32_t kdfOutputBytes_; // size of final key data + + u_int32_t memoryReqtBytes_; + u_int32_t sequenceCount_; + std::string salt_; // prob not necessary amidst numIter, memReqts + // but I guess it can't hurt + + u_int32_t numIterations_; // We set the ROMIX params for a given memory + // req't. Then run it numIter times to meet + // the computation-time req't + }; + +} + + diff --git a/src/crypto/romix.cpp b/src/crypto/romix.cpp new file mode 100644 index 0000000..81c3477 --- /dev/null +++ b/src/crypto/romix.cpp @@ -0,0 +1,99 @@ +// Most of this file has been ported from EncryptionUtils.cpp from BitcoinArmory: +//////////////////////////////////////////////////////////////////////////////// +// // +// Copyright(C) 2011-2013, Armory Technologies, Inc. // +// Distributed under the GNU Affero General Public License (AGPL v3) // +// See LICENSE or http://www.gnu.org/licenses/agpl.html // +// // +//////////////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include + +namespace fc +{ + romix::romix( u_int32_t memReqts, u_int32_t numIter, std::string salt ) : + hashOutputBytes_( 64 ), + kdfOutputBytes_( 32 ) + { + memoryReqtBytes_ = memReqts; + sequenceCount_ = memoryReqtBytes_ / hashOutputBytes_; + numIterations_ = numIter; + salt_ = salt; + } + + std::string romix::deriveKey_OneIter( std::string const & password ) + { + static fc::sha512 sha512; + + // Concatenate the salt/IV to the password + std::string saltedPassword = password + salt_; + + // Prepare the lookup table + char *lookupTable_ = new char[memoryReqtBytes_]; + u_int32_t const HSZ = hashOutputBytes_; + + // First hash to seed the lookup table, input is variable length anyway + fc::sha512 hash = sha512.hash(saltedPassword); + memcpy(lookupTable_, &hash, HSZ); + + // Compute consecutive hashes of the passphrase + // Every iteration is stored in the next 64-bytes in the Lookup table + for( u_int32_t nByte = 0; nByte < memoryReqtBytes_ - HSZ; nByte += HSZ ) + { + // Compute hash of slot i, put result in slot i+1 + fc::sha512 hash = sha512.hash(lookupTable_ + nByte, HSZ); + memcpy(lookupTable_ + nByte + HSZ, &hash, HSZ); + } + + // LookupTable should be complete, now start lookup sequence. + // Start with the last hash from the previous step + std::string X(lookupTable_ + memoryReqtBytes_ - HSZ, HSZ); + std::string Y(HSZ, '0'); + + // We "integerize" a hash value by taking the last 4 bytes of + // as a u_int32_t, and take modulo sequenceCount + u_int64_t* X64ptr = (u_int64_t*)(X.data()); + u_int64_t* Y64ptr = (u_int64_t*)(Y.data()); + u_int64_t* V64ptr = NULL; + u_int32_t newIndex; + u_int32_t const nXorOps = HSZ / sizeof(u_int64_t); + + // Pure ROMix would use sequenceCount_ for the number of lookups. + // We divide by 2 to reduce computation time RELATIVE to the memory usage + // This still provides suffient LUT operations, but allows us to use more + // memory in the same amount of time (and this is the justification for + // the scrypt algorithm -- it is basically ROMix, modified for more + // flexibility in controlling compute-time vs memory-usage). + u_int32_t const nLookups = sequenceCount_ / 2; + for(u_int32_t nSeq=0; nSeq + V64ptr = (u_int64_t*)(lookupTable_ + HSZ * newIndex); + + // xor X with V, and store the result in X + for(u_int32_t i = 0; i < nXorOps; i++) + *(Y64ptr + i) = *(X64ptr + i) ^ *(V64ptr + i); + + // Hash the xor'd data to get the next index for lookup + fc::sha512 hash = sha512.hash(Y.data(), HSZ); + X.assign(hash.data(), HSZ); + } + // Truncate the final result to get the final key + delete lookupTable_; + return X.substr(0, kdfOutputBytes_); + } + + std::string romix::deriveKey( std::string const & password ) + { + std::string masterKey(password); + for(u_int32_t i=0; i