From 6bec172eb95e195847bb6dd6d4e62ada79c98c6d Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Wed, 30 Mar 2016 15:37:41 +0200 Subject: Add ctaes-based constant time AES implementation --- src/Makefile.am | 4 ++- src/crypto/aes.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++++++ src/crypto/aes.h | 66 ++++++++++++++++++++++++++++++++++++++++++ src/test/crypto_tests.cpp | 56 ++++++++++++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/crypto/aes.cpp create mode 100644 src/crypto/aes.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 3c056386fa..0ab0d66ac2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -225,6 +225,8 @@ libbitcoin_wallet_a_SOURCES = \ crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_CONFIG_INCLUDES) crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) crypto_libbitcoin_crypto_a_SOURCES = \ + crypto/aes.cpp \ + crypto/aes.h \ crypto/common.h \ crypto/hmac_sha256.cpp \ crypto/hmac_sha256.h \ @@ -434,7 +436,7 @@ CLEANFILES += zmq/*.gcda zmq/*.gcno DISTCLEANFILES = obj/build.h -EXTRA_DIST = leveldb +EXTRA_DIST = leveldb crypto/ctaes clean-local: -$(MAKE) -C leveldb clean diff --git a/src/crypto/aes.cpp b/src/crypto/aes.cpp new file mode 100644 index 0000000000..035abd75bd --- /dev/null +++ b/src/crypto/aes.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "aes.h" +#include "crypto/common.h" + +#include +#include + +extern "C" { +#include "crypto/ctaes/ctaes.c" +} + +AES128Encrypt::AES128Encrypt(const unsigned char key[16]) +{ + AES128_init(&ctx, key); +} + +AES128Encrypt::~AES128Encrypt() +{ + memset(&ctx, 0, sizeof(ctx)); +} + +void AES128Encrypt::Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const +{ + AES128_encrypt(&ctx, 1, ciphertext, plaintext); +} + +AES128Decrypt::AES128Decrypt(const unsigned char key[16]) +{ + AES128_init(&ctx, key); +} + +AES128Decrypt::~AES128Decrypt() +{ + memset(&ctx, 0, sizeof(ctx)); +} + +void AES128Decrypt::Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const +{ + AES128_decrypt(&ctx, 1, plaintext, ciphertext); +} + +AES256Encrypt::AES256Encrypt(const unsigned char key[32]) +{ + AES256_init(&ctx, key); +} + +AES256Encrypt::~AES256Encrypt() +{ + memset(&ctx, 0, sizeof(ctx)); +} + +void AES256Encrypt::Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const +{ + AES256_encrypt(&ctx, 1, ciphertext, plaintext); +} + +AES256Decrypt::AES256Decrypt(const unsigned char key[32]) +{ + AES256_init(&ctx, key); +} + +AES256Decrypt::~AES256Decrypt() +{ + memset(&ctx, 0, sizeof(ctx)); +} + +void AES256Decrypt::Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const +{ + AES256_decrypt(&ctx, 1, plaintext, ciphertext); +} diff --git a/src/crypto/aes.h b/src/crypto/aes.h new file mode 100644 index 0000000000..4bf17a33ea --- /dev/null +++ b/src/crypto/aes.h @@ -0,0 +1,66 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +// C++ wrapper around ctaes, a constant-time AES implementation + +#ifndef BITCOIN_CRYPTO_AES_H +#define BITCOIN_CRYPTO_AES_H + +extern "C" { +#include "crypto/ctaes/ctaes.h" +} + +static const int AES_BLOCKSIZE = 16; +static const int AES128_KEYSIZE = 16; +static const int AES256_KEYSIZE = 32; + +/** An encryption class for AES-128. */ +class AES128Encrypt +{ +private: + AES128_ctx ctx; + +public: + AES128Encrypt(const unsigned char key[16]); + ~AES128Encrypt(); + void Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const; +}; + +/** A decryption class for AES-128. */ +class AES128Decrypt +{ +private: + AES128_ctx ctx; + +public: + AES128Decrypt(const unsigned char key[16]); + ~AES128Decrypt(); + void Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const; +}; + +/** An encryption class for AES-256. */ +class AES256Encrypt +{ +private: + AES256_ctx ctx; + +public: + AES256Encrypt(const unsigned char key[32]); + ~AES256Encrypt(); + void Encrypt(unsigned char ciphertext[16], const unsigned char plaintext[16]) const; +}; + +/** A decryption class for AES-256. */ +class AES256Decrypt +{ +private: + AES256_ctx ctx; + +public: + AES256Decrypt(const unsigned char key[32]); + ~AES256Decrypt(); + void Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const; +}; + +#endif // BITCOIN_CRYPTO_AES_H diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 0b46d718d1..3fc9855d6a 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "crypto/aes.h" #include "crypto/ripemd160.h" #include "crypto/sha1.h" #include "crypto/sha256.h" @@ -63,6 +64,45 @@ void TestHMACSHA512(const std::string &hexkey, const std::string &hexin, const s TestVector(CHMAC_SHA512(&key[0], key.size()), ParseHex(hexin), ParseHex(hexout)); } +void TestAES128(const std::string &hexkey, const std::string &hexin, const std::string &hexout) +{ + std::vector key = ParseHex(hexkey); + std::vector in = ParseHex(hexin); + std::vector correctout = ParseHex(hexout); + std::vector buf, buf2; + + assert(key.size() == 16); + assert(in.size() == 16); + assert(correctout.size() == 16); + AES128Encrypt enc(&key[0]); + buf.resize(correctout.size()); + buf2.resize(correctout.size()); + enc.Encrypt(&buf[0], &in[0]); + BOOST_CHECK_EQUAL(HexStr(buf), HexStr(correctout)); + AES128Decrypt dec(&key[0]); + dec.Decrypt(&buf2[0], &buf[0]); + BOOST_CHECK_EQUAL(HexStr(buf2), HexStr(in)); +} + +void TestAES256(const std::string &hexkey, const std::string &hexin, const std::string &hexout) +{ + std::vector key = ParseHex(hexkey); + std::vector in = ParseHex(hexin); + std::vector correctout = ParseHex(hexout); + std::vector buf; + + assert(key.size() == 32); + assert(in.size() == 16); + assert(correctout.size() == 16); + AES256Encrypt enc(&key[0]); + buf.resize(correctout.size()); + enc.Encrypt(&buf[0], &in[0]); + BOOST_CHECK(buf == correctout); + AES256Decrypt dec(&key[0]); + dec.Decrypt(&buf[0], &buf[0]); + BOOST_CHECK(buf == in); +} + std::string LongTestString(void) { std::string ret; for (int i=0; i<200000; i++) { @@ -248,4 +288,20 @@ BOOST_AUTO_TEST_CASE(hmac_sha512_testvectors) { "b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"); } +BOOST_AUTO_TEST_CASE(aes_testvectors) { + // AES test vectors from FIPS 197. + TestAES128("000102030405060708090a0b0c0d0e0f", "00112233445566778899aabbccddeeff", "69c4e0d86a7b0430d8cdb78070b4c55a"); + TestAES256("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "00112233445566778899aabbccddeeff", "8ea2b7ca516745bfeafc49904b496089"); + + // AES-ECB test vectors from NIST sp800-38a. + TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "6bc1bee22e409f96e93d7e117393172a", "3ad77bb40d7a3660a89ecaf32466ef97"); + TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "ae2d8a571e03ac9c9eb76fac45af8e51", "f5d3d58503b9699de785895a96fdbaaf"); + TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "30c81c46a35ce411e5fbc1191a0a52ef", "43b1cd7f598ece23881b00e3ed030688"); + TestAES128("2b7e151628aed2a6abf7158809cf4f3c", "f69f2445df4f9b17ad2b417be66c3710", "7b0c785e27e8ad3f8223207104725dd4"); + TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "6bc1bee22e409f96e93d7e117393172a", "f3eed1bdb5d2a03c064b5a7e3db181f8"); + TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "ae2d8a571e03ac9c9eb76fac45af8e51", "591ccb10d410ed26dc5ba74a31362870"); + TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "30c81c46a35ce411e5fbc1191a0a52ef", "b6ed21b99ca6f4f9f153e7b1beafed1d"); + TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "f69f2445df4f9b17ad2b417be66c3710", "23304b7a39f9f3ff067d8d8f9e24ecc7"); +} + BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 27a212dcb4fe842ead77d01b98f2c1a58ecca609 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 00:49:13 -0400 Subject: crypto: add AES 128/256 CBC classes The output should always match openssl's, even for failed operations. Even for a decrypt with broken padding, the output is always deterministic (and attemtps to be constant-time). --- src/crypto/aes.cpp | 144 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/crypto/aes.h | 52 +++++++++++++++++++ 2 files changed, 196 insertions(+) (limited to 'src') diff --git a/src/crypto/aes.cpp b/src/crypto/aes.cpp index 035abd75bd..1d469d0fb4 100644 --- a/src/crypto/aes.cpp +++ b/src/crypto/aes.cpp @@ -71,3 +71,147 @@ void AES256Decrypt::Decrypt(unsigned char plaintext[16], const unsigned char cip { AES256_decrypt(&ctx, 1, plaintext, ciphertext); } + + +template +static int CBCEncrypt(const T& enc, const unsigned char iv[AES_BLOCKSIZE], const unsigned char* data, int size, bool pad, unsigned char* out) +{ + int written = 0; + int padsize = size % AES_BLOCKSIZE; + unsigned char mixed[AES_BLOCKSIZE]; + + if (!data || !size || !out) + return 0; + + if (!pad && padsize != 0) + return 0; + + memcpy(mixed, iv, AES_BLOCKSIZE); + + // Write all but the last block + while (written + AES_BLOCKSIZE <= size) { + for (int i = 0; i != AES_BLOCKSIZE; i++) + mixed[i] ^= *data++; + enc.Encrypt(out + written, mixed); + memcpy(mixed, out + written, AES_BLOCKSIZE); + written += AES_BLOCKSIZE; + } + if (pad) { + // For all that remains, pad each byte with the value of the remaining + // space. If there is none, pad by a full block. + for (int i = 0; i != padsize; i++) + mixed[i] ^= *data++; + for (int i = padsize; i != AES_BLOCKSIZE; i++) + mixed[i] ^= AES_BLOCKSIZE - padsize; + enc.Encrypt(out + written, mixed); + written += AES_BLOCKSIZE; + } + return written; +} + +template +static int CBCDecrypt(const T& dec, const unsigned char iv[AES_BLOCKSIZE], const unsigned char* data, int size, bool pad, unsigned char* out) +{ + unsigned char padsize = 0; + int written = 0; + bool fail = false; + const unsigned char* prev = iv; + + if (!data || !size || !out) + return 0; + + if (size % AES_BLOCKSIZE != 0) + return 0; + + // Decrypt all data. Padding will be checked in the output. + while (written != size) { + dec.Decrypt(out, data + written); + for (int i = 0; i != AES_BLOCKSIZE; i++) + *out++ ^= prev[i]; + prev = data + written; + written += AES_BLOCKSIZE; + } + + // When decrypting padding, attempt to run in constant-time + if (pad) { + // If used, padding size is the value of the last decrypted byte. For + // it to be valid, It must be between 1 and AES_BLOCKSIZE. + padsize = *--out; + fail = !padsize | (padsize > AES_BLOCKSIZE); + + // If not well-formed, treat it as though there's no padding. + padsize *= !fail; + + // All padding must equal the last byte otherwise it's not well-formed + for (int i = AES_BLOCKSIZE; i != 0; i--) + fail |= ((i > AES_BLOCKSIZE - padsize) & (*out-- != padsize)); + + written -= padsize; + } + return written * !fail; +} + +AES256CBCEncrypt::AES256CBCEncrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn) + : enc(key), pad(padIn) +{ + memcpy(iv, ivIn, AES_BLOCKSIZE); +} + +int AES256CBCEncrypt::Encrypt(const unsigned char* data, int size, unsigned char* out) const +{ + return CBCEncrypt(enc, iv, data, size, pad, out); +} + +AES256CBCEncrypt::~AES256CBCEncrypt() +{ + memset(iv, 0, sizeof(iv)); +} + +AES256CBCDecrypt::AES256CBCDecrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn) + : dec(key), pad(padIn) +{ + memcpy(iv, ivIn, AES_BLOCKSIZE); +} + + +int AES256CBCDecrypt::Decrypt(const unsigned char* data, int size, unsigned char* out) const +{ + return CBCDecrypt(dec, iv, data, size, pad, out); +} + +AES256CBCDecrypt::~AES256CBCDecrypt() +{ + memset(iv, 0, sizeof(iv)); +} + +AES128CBCEncrypt::AES128CBCEncrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn) + : enc(key), pad(padIn) +{ + memcpy(iv, ivIn, AES_BLOCKSIZE); +} + +AES128CBCEncrypt::~AES128CBCEncrypt() +{ + memset(iv, 0, AES_BLOCKSIZE); +} + +int AES128CBCEncrypt::Encrypt(const unsigned char* data, int size, unsigned char* out) const +{ + return CBCEncrypt(enc, iv, data, size, pad, out); +} + +AES128CBCDecrypt::AES128CBCDecrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn) + : dec(key), pad(padIn) +{ + memcpy(iv, ivIn, AES_BLOCKSIZE); +} + +AES128CBCDecrypt::~AES128CBCDecrypt() +{ + memset(iv, 0, AES_BLOCKSIZE); +} + +int AES128CBCDecrypt::Decrypt(const unsigned char* data, int size, unsigned char* out) const +{ + return CBCDecrypt(dec, iv, data, size, pad, out); +} diff --git a/src/crypto/aes.h b/src/crypto/aes.h index 4bf17a33ea..8cae357c12 100644 --- a/src/crypto/aes.h +++ b/src/crypto/aes.h @@ -63,4 +63,56 @@ public: void Decrypt(unsigned char plaintext[16], const unsigned char ciphertext[16]) const; }; +class AES256CBCEncrypt +{ +public: + AES256CBCEncrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn); + ~AES256CBCEncrypt(); + int Encrypt(const unsigned char* data, int size, unsigned char* out) const; + +private: + const AES256Encrypt enc; + const bool pad; + unsigned char iv[AES_BLOCKSIZE]; +}; + +class AES256CBCDecrypt +{ +public: + AES256CBCDecrypt(const unsigned char key[AES256_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn); + ~AES256CBCDecrypt(); + int Decrypt(const unsigned char* data, int size, unsigned char* out) const; + +private: + const AES256Decrypt dec; + const bool pad; + unsigned char iv[AES_BLOCKSIZE]; +}; + +class AES128CBCEncrypt +{ +public: + AES128CBCEncrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn); + ~AES128CBCEncrypt(); + int Encrypt(const unsigned char* data, int size, unsigned char* out) const; + +private: + const AES128Encrypt enc; + const bool pad; + unsigned char iv[AES_BLOCKSIZE]; +}; + +class AES128CBCDecrypt +{ +public: + AES128CBCDecrypt(const unsigned char key[AES128_KEYSIZE], const unsigned char ivIn[AES_BLOCKSIZE], bool padIn); + ~AES128CBCDecrypt(); + int Decrypt(const unsigned char* data, int size, unsigned char* out) const; + +private: + const AES128Decrypt dec; + const bool pad; + unsigned char iv[AES_BLOCKSIZE]; +}; + #endif // BITCOIN_CRYPTO_AES_H -- cgit v1.2.3 From daa384120a63542257d4ca73047d775f16fac654 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 00:52:58 -0400 Subject: crypto: add aes cbc tests --- src/test/crypto_tests.cpp | 135 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) (limited to 'src') diff --git a/src/test/crypto_tests.cpp b/src/test/crypto_tests.cpp index 3fc9855d6a..58a62ee022 100644 --- a/src/test/crypto_tests.cpp +++ b/src/test/crypto_tests.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include BOOST_FIXTURE_TEST_SUITE(crypto_tests, BasicTestingSetup) @@ -103,6 +105,88 @@ void TestAES256(const std::string &hexkey, const std::string &hexin, const std:: BOOST_CHECK(buf == in); } +void TestAES128CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) +{ + std::vector key = ParseHex(hexkey); + std::vector iv = ParseHex(hexiv); + std::vector in = ParseHex(hexin); + std::vector correctout = ParseHex(hexout); + std::vector realout(in.size() + AES_BLOCKSIZE); + + // Encrypt the plaintext and verify that it equals the cipher + AES128CBCEncrypt enc(&key[0], &iv[0], pad); + int size = enc.Encrypt(&in[0], in.size(), &realout[0]); + realout.resize(size); + BOOST_CHECK(realout.size() == correctout.size()); + BOOST_CHECK_MESSAGE(realout == correctout, HexStr(realout) + std::string(" != ") + hexout); + + // Decrypt the cipher and verify that it equals the plaintext + std::vector decrypted(correctout.size()); + AES128CBCDecrypt dec(&key[0], &iv[0], pad); + size = dec.Decrypt(&correctout[0], correctout.size(), &decrypted[0]); + decrypted.resize(size); + BOOST_CHECK(decrypted.size() == in.size()); + BOOST_CHECK_MESSAGE(decrypted == in, HexStr(decrypted) + std::string(" != ") + hexin); + + // Encrypt and re-decrypt substrings of the plaintext and verify that they equal each-other + for(std::vector::iterator i(in.begin()); i != in.end(); ++i) + { + std::vector sub(i, in.end()); + std::vector subout(sub.size() + AES_BLOCKSIZE); + int size = enc.Encrypt(&sub[0], sub.size(), &subout[0]); + if (size != 0) + { + subout.resize(size); + std::vector subdecrypted(subout.size()); + size = dec.Decrypt(&subout[0], subout.size(), &subdecrypted[0]); + subdecrypted.resize(size); + BOOST_CHECK(decrypted.size() == in.size()); + BOOST_CHECK_MESSAGE(subdecrypted == sub, HexStr(subdecrypted) + std::string(" != ") + HexStr(sub)); + } + } +} + +void TestAES256CBC(const std::string &hexkey, const std::string &hexiv, bool pad, const std::string &hexin, const std::string &hexout) +{ + std::vector key = ParseHex(hexkey); + std::vector iv = ParseHex(hexiv); + std::vector in = ParseHex(hexin); + std::vector correctout = ParseHex(hexout); + std::vector realout(in.size() + AES_BLOCKSIZE); + + // Encrypt the plaintext and verify that it equals the cipher + AES256CBCEncrypt enc(&key[0], &iv[0], pad); + int size = enc.Encrypt(&in[0], in.size(), &realout[0]); + realout.resize(size); + BOOST_CHECK(realout.size() == correctout.size()); + BOOST_CHECK_MESSAGE(realout == correctout, HexStr(realout) + std::string(" != ") + hexout); + + // Decrypt the cipher and verify that it equals the plaintext + std::vector decrypted(correctout.size()); + AES256CBCDecrypt dec(&key[0], &iv[0], pad); + size = dec.Decrypt(&correctout[0], correctout.size(), &decrypted[0]); + decrypted.resize(size); + BOOST_CHECK(decrypted.size() == in.size()); + BOOST_CHECK_MESSAGE(decrypted == in, HexStr(decrypted) + std::string(" != ") + hexin); + + // Encrypt and re-decrypt substrings of the plaintext and verify that they equal each-other + for(std::vector::iterator i(in.begin()); i != in.end(); ++i) + { + std::vector sub(i, in.end()); + std::vector subout(sub.size() + AES_BLOCKSIZE); + int size = enc.Encrypt(&sub[0], sub.size(), &subout[0]); + if (size != 0) + { + subout.resize(size); + std::vector subdecrypted(subout.size()); + size = dec.Decrypt(&subout[0], subout.size(), &subdecrypted[0]); + subdecrypted.resize(size); + BOOST_CHECK(decrypted.size() == in.size()); + BOOST_CHECK_MESSAGE(subdecrypted == sub, HexStr(subdecrypted) + std::string(" != ") + HexStr(sub)); + } + } +} + std::string LongTestString(void) { std::string ret; for (int i=0; i<200000; i++) { @@ -304,4 +388,55 @@ BOOST_AUTO_TEST_CASE(aes_testvectors) { TestAES256("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", "f69f2445df4f9b17ad2b417be66c3710", "23304b7a39f9f3ff067d8d8f9e24ecc7"); } +BOOST_AUTO_TEST_CASE(aes_cbc_testvectors) { + + // NIST AES CBC 128-bit encryption test-vectors + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090A0B0C0D0E0F", false, \ + "6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", false, \ + "ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b2"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "5086cb9b507219ee95db113a917678b2", false, \ + "30c81c46a35ce411e5fbc1191a0a52ef", "73bed6b8e3c1743b7116e69e22229516"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "73bed6b8e3c1743b7116e69e22229516", false, \ + "f69f2445df4f9b17ad2b417be66c3710", "3ff1caa1681fac09120eca307586e1a7"); + + // The same vectors with padding enabled + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "000102030405060708090A0B0C0D0E0F", true, \ + "6bc1bee22e409f96e93d7e117393172a", "7649abac8119b246cee98e9b12e9197d8964e0b149c10b7b682e6e39aaeb731c"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "7649ABAC8119B246CEE98E9B12E9197D", true, \ + "ae2d8a571e03ac9c9eb76fac45af8e51", "5086cb9b507219ee95db113a917678b255e21d7100b988ffec32feeafaf23538"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "5086cb9b507219ee95db113a917678b2", true, \ + "30c81c46a35ce411e5fbc1191a0a52ef", "73bed6b8e3c1743b7116e69e22229516f6eccda327bf8e5ec43718b0039adceb"); + TestAES128CBC("2b7e151628aed2a6abf7158809cf4f3c", "73bed6b8e3c1743b7116e69e22229516", true, \ + "f69f2445df4f9b17ad2b417be66c3710", "3ff1caa1681fac09120eca307586e1a78cb82807230e1321d3fae00d18cc2012"); + + // NIST AES CBC 256-bit encryption test-vectors + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "000102030405060708090A0B0C0D0E0F", false, "6bc1bee22e409f96e93d7e117393172a", \ + "f58c4c04d6e5f1ba779eabfb5f7bfbd6"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", false, "ae2d8a571e03ac9c9eb76fac45af8e51", \ + "9cfc4e967edb808d679f777bc6702c7d"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "9CFC4E967EDB808D679F777BC6702C7D", false, "30c81c46a35ce411e5fbc1191a0a52ef", + "39f23369a9d9bacfa530e26304231461"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "39F23369A9D9BACFA530E26304231461", false, "f69f2445df4f9b17ad2b417be66c3710", \ + "b2eb05e2c39be9fcda6c19078c6a9d1b"); + + // The same vectors with padding enabled + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "000102030405060708090A0B0C0D0E0F", true, "6bc1bee22e409f96e93d7e117393172a", \ + "f58c4c04d6e5f1ba779eabfb5f7bfbd6485a5c81519cf378fa36d42b8547edc0"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "F58C4C04D6E5F1BA779EABFB5F7BFBD6", true, "ae2d8a571e03ac9c9eb76fac45af8e51", \ + "9cfc4e967edb808d679f777bc6702c7d3a3aa5e0213db1a9901f9036cf5102d2"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "9CFC4E967EDB808D679F777BC6702C7D", true, "30c81c46a35ce411e5fbc1191a0a52ef", + "39f23369a9d9bacfa530e263042314612f8da707643c90a6f732b3de1d3f5cee"); + TestAES256CBC("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", \ + "39F23369A9D9BACFA530E26304231461", true, "f69f2445df4f9b17ad2b417be66c3710", \ + "b2eb05e2c39be9fcda6c19078c6a9d1b3f461796d6b0d6b2e0c2a72b4d80e644"); +} + BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 1c391a5866e1342617b51041afebee2215e9a30c Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 01:05:47 -0400 Subject: crypter: fix the stored initialization vector size AES IV's are 16bytes, not 32. This was harmless but confusing. Add WALLET_CRYPTO_IV_SIZE to make its usage explicit. --- src/wallet/crypter.cpp | 10 +++++----- src/wallet/crypter.h | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 95aa4c2593..8f555579fa 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -37,7 +37,7 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector& chNewIV) { - if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_KEY_SIZE) + if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_IV_SIZE) return false; memcpy(&chKey[0], &chNewKey[0], sizeof chKey); @@ -105,8 +105,8 @@ bool CCrypter::Decrypt(const std::vector& vchCiphertext, CKeyingM static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector &vchCiphertext) { CCrypter cKeyCrypter; - std::vector chIV(WALLET_CRYPTO_KEY_SIZE); - memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE); + std::vector chIV(WALLET_CRYPTO_IV_SIZE); + memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE); if(!cKeyCrypter.SetKey(vMasterKey, chIV)) return false; return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext); @@ -115,8 +115,8 @@ static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMateri static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext) { CCrypter cKeyCrypter; - std::vector chIV(WALLET_CRYPTO_KEY_SIZE); - memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE); + std::vector chIV(WALLET_CRYPTO_IV_SIZE); + memcpy(&chIV[0], &nIV, WALLET_CRYPTO_IV_SIZE); if(!cKeyCrypter.SetKey(vMasterKey, chIV)) return false; return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext)); diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index eb06a7866a..b4727ac8ac 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -13,6 +13,7 @@ class uint256; const unsigned int WALLET_CRYPTO_KEY_SIZE = 32; const unsigned int WALLET_CRYPTO_SALT_SIZE = 8; +const unsigned int WALLET_CRYPTO_IV_SIZE = 16; /** * Private key encryption is done based on a CMasterKey, @@ -71,7 +72,7 @@ class CCrypter { private: unsigned char chKey[WALLET_CRYPTO_KEY_SIZE]; - unsigned char chIV[WALLET_CRYPTO_KEY_SIZE]; + unsigned char chIV[WALLET_CRYPTO_IV_SIZE]; bool fKeySet; public: -- cgit v1.2.3 From fb96831c1ff767cd86099f66127fa4dc1ec6e277 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Thu, 26 Mar 2015 17:37:29 -0400 Subject: crypter: constify encrypt/decrypt This makes CCrypter easier to pass aroundf for tests --- src/wallet/crypter.cpp | 4 ++-- src/wallet/crypter.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 8f555579fa..e37a9c4c85 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -47,7 +47,7 @@ bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector &vchCiphertext) +bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext) const { if (!fKeySet) return false; @@ -74,7 +74,7 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext) +bool CCrypter::Decrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext) const { if (!fKeySet) return false; diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index b4727ac8ac..3457d40ffd 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -77,8 +77,8 @@ private: public: bool SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); - bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext); - bool Decrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext); + bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext) const; + bool Decrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext) const; bool SetKey(const CKeyingMaterial& chNewKey, const std::vector& chNewIV); void CleanKey() -- cgit v1.2.3 From 9049cde4d962862f507f9ddf1c0dbd49ea04be51 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 01:10:30 -0400 Subject: crypter: hook up the new aes cbc classes --- src/wallet/crypter.cpp | 45 ++++++++++++++------------------------------- 1 file changed, 14 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index e37a9c4c85..0a19139a31 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -4,6 +4,7 @@ #include "crypter.h" +#include "crypto/aes.h" #include "script/script.h" #include "script/standard.h" #include "util.h" @@ -53,24 +54,15 @@ bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector (nCLen); + // n + AES_BLOCKSIZE bytes + vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE); - EVP_CIPHER_CTX ctx; - - bool fOk = true; - - EVP_CIPHER_CTX_init(&ctx); - if (fOk) fOk = EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0; - if (fOk) fOk = EVP_EncryptUpdate(&ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0; - if (fOk) fOk = EVP_EncryptFinal_ex(&ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0; - EVP_CIPHER_CTX_cleanup(&ctx); - - if (!fOk) return false; + AES256CBCEncrypt enc(chKey, chIV, true); + size_t nLen = enc.Encrypt(&vchPlaintext[0], vchPlaintext.size(), &vchCiphertext[0]); + if(nLen < vchPlaintext.size()) + return false; + vchCiphertext.resize(nLen); - vchCiphertext.resize(nCLen + nFLen); return true; } @@ -81,23 +73,14 @@ bool CCrypter::Decrypt(const std::vector& vchCiphertext, CKeyingM // plaintext will always be equal to or lesser than length of ciphertext int nLen = vchCiphertext.size(); - int nPLen = nLen, nFLen = 0; - - vchPlaintext = CKeyingMaterial(nPLen); - EVP_CIPHER_CTX ctx; + vchPlaintext.resize(nLen); - bool fOk = true; - - EVP_CIPHER_CTX_init(&ctx); - if (fOk) fOk = EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0; - if (fOk) fOk = EVP_DecryptUpdate(&ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0; - if (fOk) fOk = EVP_DecryptFinal_ex(&ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0; - EVP_CIPHER_CTX_cleanup(&ctx); - - if (!fOk) return false; - - vchPlaintext.resize(nPLen + nFLen); + AES256CBCDecrypt dec(chKey, chIV, true); + nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]); + if(nLen == 0) + return false; + vchPlaintext.resize(nLen); return true; } -- cgit v1.2.3 From 976f9ec2645242032d34ab68a60d963f2ac586d8 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 01:24:12 -0400 Subject: crypter: add a BytesToKey clone to replace the use of openssl BytesToKeySHA512AES should be functionally identical to EVP_BytesToKey, but drops the dependency on openssl. --- src/wallet/crypter.cpp | 33 +++++++++++++++++++++++++++++---- src/wallet/crypter.h | 2 ++ 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp index 0a19139a31..190f8ecf2a 100644 --- a/src/wallet/crypter.cpp +++ b/src/wallet/crypter.cpp @@ -5,6 +5,7 @@ #include "crypter.h" #include "crypto/aes.h" +#include "crypto/sha512.h" #include "script/script.h" #include "script/standard.h" #include "util.h" @@ -12,8 +13,33 @@ #include #include #include -#include -#include + +int CCrypter::BytesToKeySHA512AES(const std::vector& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const +{ + // This mimics the behavior of openssl's EVP_BytesToKey with an aes256cbc + // cipher and sha512 message digest. Because sha512's output size (64b) is + // greater than the aes256 block size (16b) + aes256 key size (32b), + // there's no need to process more than once (D_0). + + if(!count || !key || !iv) + return 0; + + unsigned char buf[CSHA512::OUTPUT_SIZE]; + CSHA512 di; + + di.Write((const unsigned char*)strKeyData.c_str(), strKeyData.size()); + if(chSalt.size()) + di.Write(&chSalt[0], chSalt.size()); + di.Finalize(buf); + + for(int i = 0; i != count - 1; i++) + di.Reset().Write(buf, sizeof(buf)).Finalize(buf); + + memcpy(key, buf, WALLET_CRYPTO_KEY_SIZE); + memcpy(iv, buf + WALLET_CRYPTO_KEY_SIZE, WALLET_CRYPTO_IV_SIZE); + memory_cleanse(buf, sizeof(buf)); + return WALLET_CRYPTO_KEY_SIZE; +} bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod) { @@ -22,8 +48,7 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v int i = 0; if (nDerivationMethod == 0) - i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0], - (unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV); + i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, chKey, chIV); if (i != (int)WALLET_CRYPTO_KEY_SIZE) { diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 3457d40ffd..16f2ba6220 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -75,6 +75,8 @@ private: unsigned char chIV[WALLET_CRYPTO_IV_SIZE]; bool fKeySet; + int BytesToKeySHA512AES(const std::vector& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const; + public: bool SetKeyFromPassphrase(const SecureString &strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod); bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext) const; -- cgit v1.2.3 From 0a36b9af281e31b080ca0835eec7704097527bda Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Thu, 26 Mar 2015 19:15:28 -0400 Subject: crypter: shuffle Makefile so that crypto can be used by the wallet Wallet must come before crypto, otherwise linking fails on some platforms. Includes a tangentially-related general cleanup rather than making the Makefile sloppier. --- src/Makefile.am | 55 +++++++++++++++++++++++-------------------------------- 1 file changed, 23 insertions(+), 32 deletions(-) (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 0ab0d66ac2..31917f1350 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -15,13 +15,12 @@ LIBUNIVALUE = $(UNIVALUE_LIBS) endif BITCOIN_CONFIG_INCLUDES=-I$(builddir)/config -BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) +BITCOIN_INCLUDES=-I$(builddir) -I$(builddir)/obj $(BDB_CPPFLAGS) $(BOOST_CPPFLAGS) $(LEVELDB_CPPFLAGS) $(CRYPTO_CFLAGS) $(SSL_CFLAGS) BITCOIN_INCLUDES += -I$(srcdir)/secp256k1/include BITCOIN_INCLUDES += $(UNIVALUE_CFLAGS) LIBBITCOIN_SERVER=libbitcoin_server.a -LIBBITCOIN_WALLET=libbitcoin_wallet.a LIBBITCOIN_COMMON=libbitcoin_common.a LIBBITCOIN_CONSENSUS=libbitcoin_consensus.a LIBBITCOIN_CLI=libbitcoin_cli.a @@ -30,32 +29,32 @@ LIBBITCOIN_CRYPTO=crypto/libbitcoin_crypto.a LIBBITCOINQT=qt/libbitcoinqt.a LIBSECP256K1=secp256k1/libsecp256k1.la +if ENABLE_ZMQ +LIBBITCOIN_ZMQ=libbitcoin_zmq.a +endif +if BUILD_BITCOIN_LIBS +LIBBITCOINCONSENSUS=libbitcoinconsensus.la +endif +if ENABLE_WALLET +LIBBITCOIN_WALLET=libbitcoin_wallet.a +endif + $(LIBSECP256K1): $(wildcard secp256k1/src/*) $(wildcard secp256k1/include/*) $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C $(@D) $(@F) # Make is not made aware of per-object dependencies to avoid limiting building parallelization # But to build the less dependent modules first, we manually select their order here: EXTRA_LIBRARIES += \ - crypto/libbitcoin_crypto.a \ - libbitcoin_util.a \ - libbitcoin_common.a \ - libbitcoin_consensus.a \ - libbitcoin_server.a \ - libbitcoin_cli.a -if ENABLE_WALLET -BITCOIN_INCLUDES += $(BDB_CPPFLAGS) -EXTRA_LIBRARIES += libbitcoin_wallet.a -endif -if ENABLE_ZMQ -EXTRA_LIBRARIES += libbitcoin_zmq.a -endif + $(LIBBITCOIN_CRYPTO) \ + $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_COMMON) \ + $(LIBBITCOIN_CONSENSUS) \ + $(LIBBITCOIN_SERVER) \ + $(LIBBITCOIN_CLI) \ + $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_ZMQ) -if BUILD_BITCOIN_LIBS -lib_LTLIBRARIES = libbitcoinconsensus.la -LIBBITCOINCONSENSUS=libbitcoinconsensus.la -else -LIBBITCOINCONSENSUS= -endif +lib_LTLIBRARIES = $(LIBBITCOINCONSENSUS) bin_PROGRAMS = TESTS = @@ -196,8 +195,6 @@ libbitcoin_server_a_SOURCES = \ $(BITCOIN_CORE_H) if ENABLE_ZMQ -LIBBITCOIN_ZMQ=libbitcoin_zmq.a - libbitcoin_zmq_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(ZMQ_CFLAGS) libbitcoin_zmq_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_zmq_a_SOURCES = \ @@ -347,21 +344,15 @@ bitcoind_LDADD = \ $(LIBBITCOIN_COMMON) \ $(LIBUNIVALUE) \ $(LIBBITCOIN_UTIL) \ + $(LIBBITCOIN_WALLET) \ + $(LIBBITCOIN_ZMQ) \ $(LIBBITCOIN_CONSENSUS) \ $(LIBBITCOIN_CRYPTO) \ $(LIBLEVELDB) \ $(LIBMEMENV) \ $(LIBSECP256K1) -if ENABLE_ZMQ -bitcoind_LDADD += $(LIBBITCOIN_ZMQ) $(ZMQ_LIBS) -endif - -if ENABLE_WALLET -bitcoind_LDADD += libbitcoin_wallet.a -endif - -bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) +bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) $(ZMQ_LIBS) # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp -- cgit v1.2.3 From 34ed64a404fb6da691f67f00f47c4dd748e3e428 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 20 Mar 2015 01:27:50 -0400 Subject: crypter: add tests for crypter Verify that results correct (match known values), consistent (encrypt->decrypt matches the original), and compatible with the previous openssl implementation. Also check that failed encrypts/decrypts fail the exact same way as openssl. --- src/Makefile.test.include | 1 + src/wallet/crypter.h | 6 + src/wallet/test/crypto_tests.cpp | 230 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 src/wallet/test/crypto_tests.cpp (limited to 'src') diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 08e2f6af4d..d443b6d34a 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -94,6 +94,7 @@ BITCOIN_TESTS += \ wallet/test/wallet_test_fixture.h \ wallet/test/accounting_tests.cpp \ wallet/test/wallet_tests.cpp \ + wallet/test/crypto_tests.cpp \ wallet/test/rpc_wallet_tests.cpp endif diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 16f2ba6220..5d0a4a3305 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -67,9 +67,15 @@ public: typedef std::vector > CKeyingMaterial; +namespace wallet_crypto +{ + class TestCrypter; +} + /** Encryption/decryption context with key information */ class CCrypter { +friend class wallet_crypto::TestCrypter; // for test access to chKey/chIV private: unsigned char chKey[WALLET_CRYPTO_KEY_SIZE]; unsigned char chIV[WALLET_CRYPTO_IV_SIZE]; diff --git a/src/wallet/test/crypto_tests.cpp b/src/wallet/test/crypto_tests.cpp new file mode 100644 index 0000000000..05387f5f2b --- /dev/null +++ b/src/wallet/test/crypto_tests.cpp @@ -0,0 +1,230 @@ +// Copyright (c) 2014 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "random.h" +#include "utilstrencodings.h" +#include "test/test_bitcoin.h" +#include "wallet/crypter.h" + +#include + +#include +#include +#include + +BOOST_FIXTURE_TEST_SUITE(wallet_crypto, BasicTestingSetup) + +bool OldSetKeyFromPassphrase(const SecureString& strKeyData, const std::vector& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod, unsigned char* chKey, unsigned char* chIV) +{ + if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE) + return false; + + int i = 0; + if (nDerivationMethod == 0) + i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0], + (unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV); + + if (i != (int)WALLET_CRYPTO_KEY_SIZE) + { + memory_cleanse(chKey, sizeof(chKey)); + memory_cleanse(chIV, sizeof(chIV)); + return false; + } + return true; +} + +bool OldEncrypt(const CKeyingMaterial& vchPlaintext, std::vector &vchCiphertext, const unsigned char chKey[32], const unsigned char chIV[16]) +{ + // max ciphertext len for a n bytes of plaintext is + // n + AES_BLOCK_SIZE - 1 bytes + int nLen = vchPlaintext.size(); + int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0; + vchCiphertext = std::vector (nCLen); + + EVP_CIPHER_CTX ctx; + + bool fOk = true; + + EVP_CIPHER_CTX_init(&ctx); + if (fOk) fOk = EVP_EncryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0; + if (fOk) fOk = EVP_EncryptUpdate(&ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0; + if (fOk) fOk = EVP_EncryptFinal_ex(&ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0; + EVP_CIPHER_CTX_cleanup(&ctx); + + if (!fOk) return false; + + vchCiphertext.resize(nCLen + nFLen); + return true; +} + +bool OldDecrypt(const std::vector& vchCiphertext, CKeyingMaterial& vchPlaintext, const unsigned char chKey[32], const unsigned char chIV[16]) +{ + // plaintext will always be equal to or lesser than length of ciphertext + int nLen = vchCiphertext.size(); + int nPLen = nLen, nFLen = 0; + + vchPlaintext = CKeyingMaterial(nPLen); + + EVP_CIPHER_CTX ctx; + + bool fOk = true; + + EVP_CIPHER_CTX_init(&ctx); + if (fOk) fOk = EVP_DecryptInit_ex(&ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0; + if (fOk) fOk = EVP_DecryptUpdate(&ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0; + if (fOk) fOk = EVP_DecryptFinal_ex(&ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0; + EVP_CIPHER_CTX_cleanup(&ctx); + + if (!fOk) return false; + + vchPlaintext.resize(nPLen + nFLen); + return true; +} + +class TestCrypter +{ +public: +static void TestPassphraseSingle(const std::vector& vchSalt, const SecureString& passphrase, uint32_t rounds, + const std::vector& correctKey = std::vector(), + const std::vector& correctIV=std::vector()) +{ + unsigned char chKey[WALLET_CRYPTO_KEY_SIZE]; + unsigned char chIV[WALLET_CRYPTO_IV_SIZE]; + + CCrypter crypt; + crypt.SetKeyFromPassphrase(passphrase, vchSalt, rounds, 0); + + OldSetKeyFromPassphrase(passphrase, vchSalt, rounds, 0, chKey, chIV); + + BOOST_CHECK_MESSAGE(memcmp(chKey, crypt.chKey, sizeof(chKey)) == 0, \ + HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(crypt.chKey, crypt.chKey + (sizeof crypt.chKey))); + BOOST_CHECK_MESSAGE(memcmp(chIV, crypt.chIV, sizeof(chIV)) == 0, \ + HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(crypt.chIV, crypt.chIV + (sizeof crypt.chIV))); + + if(!correctKey.empty()) + BOOST_CHECK_MESSAGE(memcmp(chKey, &correctKey[0], sizeof(chKey)) == 0, \ + HexStr(chKey, chKey+sizeof(chKey)) + std::string(" != ") + HexStr(correctKey.begin(), correctKey.end())); + if(!correctIV.empty()) + BOOST_CHECK_MESSAGE(memcmp(chIV, &correctIV[0], sizeof(chIV)) == 0, + HexStr(chIV, chIV+sizeof(chIV)) + std::string(" != ") + HexStr(correctIV.begin(), correctIV.end())); +} + +static void TestPassphrase(const std::vector& vchSalt, const SecureString& passphrase, uint32_t rounds, + const std::vector& correctKey = std::vector(), + const std::vector& correctIV=std::vector()) +{ + TestPassphraseSingle(vchSalt, passphrase, rounds, correctKey, correctIV); + for(SecureString::const_iterator i(passphrase.begin()); i != passphrase.end(); ++i) + TestPassphraseSingle(vchSalt, SecureString(i, passphrase.end()), rounds); +} + + +static void TestDecrypt(const CCrypter& crypt, const std::vector& vchCiphertext, \ + const std::vector& vchPlaintext = std::vector()) +{ + CKeyingMaterial vchDecrypted1; + CKeyingMaterial vchDecrypted2; + int result1, result2; + result1 = crypt.Decrypt(vchCiphertext, vchDecrypted1); + result2 = OldDecrypt(vchCiphertext, vchDecrypted2, crypt.chKey, crypt.chIV); + BOOST_CHECK(result1 == result2); + + // These two should be equal. However, OpenSSL 1.0.1j introduced a change + // that would zero all padding except for the last byte for failed decrypts. + // This behavior was reverted for 1.0.1k. + if (vchDecrypted1 != vchDecrypted2 && vchDecrypted1.size() >= AES_BLOCK_SIZE && SSLeay() == 0x100010afL) + { + for(CKeyingMaterial::iterator it = vchDecrypted1.end() - AES_BLOCK_SIZE; it != vchDecrypted1.end() - 1; it++) + *it = 0; + } + + BOOST_CHECK_MESSAGE(vchDecrypted1 == vchDecrypted2, HexStr(vchDecrypted1.begin(), vchDecrypted1.end()) + " != " + HexStr(vchDecrypted2.begin(), vchDecrypted2.end())); + + if (vchPlaintext.size()) + BOOST_CHECK(CKeyingMaterial(vchPlaintext.begin(), vchPlaintext.end()) == vchDecrypted2); +} + +static void TestEncryptSingle(const CCrypter& crypt, const CKeyingMaterial& vchPlaintext, + const std::vector& vchCiphertextCorrect = std::vector()) +{ + std::vector vchCiphertext1; + std::vector vchCiphertext2; + int result1 = crypt.Encrypt(vchPlaintext, vchCiphertext1); + + int result2 = OldEncrypt(vchPlaintext, vchCiphertext2, crypt.chKey, crypt.chIV); + BOOST_CHECK(result1 == result2); + BOOST_CHECK(vchCiphertext1 == vchCiphertext2); + + if (!vchCiphertextCorrect.empty()) + BOOST_CHECK(vchCiphertext2 == vchCiphertextCorrect); + + const std::vector vchPlaintext2(vchPlaintext.begin(), vchPlaintext.end()); + + if(vchCiphertext1 == vchCiphertext2) + TestDecrypt(crypt, vchCiphertext1, vchPlaintext2); +} + +static void TestEncrypt(const CCrypter& crypt, const std::vector& vchPlaintextIn, \ + const std::vector& vchCiphertextCorrect = std::vector()) +{ + TestEncryptSingle(crypt, CKeyingMaterial(vchPlaintextIn.begin(), vchPlaintextIn.end()), vchCiphertextCorrect); + for(std::vector::const_iterator i(vchPlaintextIn.begin()); i != vchPlaintextIn.end(); ++i) + TestEncryptSingle(crypt, CKeyingMaterial(i, vchPlaintextIn.end())); +} + +}; + +BOOST_AUTO_TEST_CASE(passphrase) { + // These are expensive. + + TestCrypter::TestPassphrase(ParseHex("0000deadbeef0000"), "test", 25000, \ + ParseHex("fc7aba077ad5f4c3a0988d8daa4810d0d4a0e3bcb53af662998898f33df0556a"), \ + ParseHex("cf2f2691526dd1aa220896fb8bf7c369")); + + std::string hash(GetRandHash().ToString()); + std::vector vchSalt(8); + GetRandBytes(&vchSalt[0], vchSalt.size()); + uint32_t rounds = insecure_rand(); + if (rounds > 30000) + rounds = 30000; + TestCrypter::TestPassphrase(vchSalt, SecureString(hash.begin(), hash.end()), rounds); +} + +BOOST_AUTO_TEST_CASE(encrypt) { + std::vector vchSalt = ParseHex("0000deadbeef0000"); + BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE); + CCrypter crypt; + crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0); + TestCrypter::TestEncrypt(crypt, ParseHex("22bcade09ac03ff6386914359cfe885cfeb5f77ff0d670f102f619687453b29d")); + + for (int i = 0; i != 100; i++) + { + uint256 hash(GetRandHash()); + TestCrypter::TestEncrypt(crypt, std::vector(hash.begin(), hash.end())); + } + +} + +BOOST_AUTO_TEST_CASE(decrypt) { + std::vector vchSalt = ParseHex("0000deadbeef0000"); + BOOST_CHECK(vchSalt.size() == WALLET_CRYPTO_SALT_SIZE); + CCrypter crypt; + crypt.SetKeyFromPassphrase("passphrase", vchSalt, 25000, 0); + + // Some corner cases the came up while testing + TestCrypter::TestDecrypt(crypt,ParseHex("795643ce39d736088367822cdc50535ec6f103715e3e48f4f3b1a60a08ef59ca")); + TestCrypter::TestDecrypt(crypt,ParseHex("de096f4a8f9bd97db012aa9d90d74de8cdea779c3ee8bc7633d8b5d6da703486")); + TestCrypter::TestDecrypt(crypt,ParseHex("32d0a8974e3afd9c6c3ebf4d66aa4e6419f8c173de25947f98cf8b7ace49449c")); + TestCrypter::TestDecrypt(crypt,ParseHex("e7c055cca2faa78cb9ac22c9357a90b4778ded9b2cc220a14cea49f931e596ea")); + TestCrypter::TestDecrypt(crypt,ParseHex("b88efddd668a6801d19516d6830da4ae9811988ccbaf40df8fbb72f3f4d335fd")); + TestCrypter::TestDecrypt(crypt,ParseHex("8cae76aa6a43694e961ebcb28c8ca8f8540b84153d72865e8561ddd93fa7bfa9")); + + for (int i = 0; i != 100; i++) + { + uint256 hash(GetRandHash()); + TestCrypter::TestDecrypt(crypt, std::vector(hash.begin(), hash.end())); + } +} + +BOOST_AUTO_TEST_SUITE_END() -- cgit v1.2.3 From 723779c6504453cfb5ccdacf864e7e2f09bb6c32 Mon Sep 17 00:00:00 2001 From: Cory Fields Date: Fri, 27 May 2016 14:14:44 -0400 Subject: build: Enumerate ctaes rather than globbing --- src/Makefile.am | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 31917f1350..cb8a99fe9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -410,6 +410,12 @@ libbitcoinconsensus_la_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) endif # +CTAES_DIST = crypto/ctaes/bench.c +CTAES_DIST += crypto/ctaes/ctaes.c +CTAES_DIST += crypto/ctaes/ctaes.h +CTAES_DIST += crypto/ctaes/README.md +CTAES_DIST += crypto/ctaes/test.c + CLEANFILES = leveldb/libleveldb.a leveldb/libmemenv.a CLEANFILES += $(EXTRA_LIBRARIES) CLEANFILES += *.gcda *.gcno @@ -427,7 +433,7 @@ CLEANFILES += zmq/*.gcda zmq/*.gcno DISTCLEANFILES = obj/build.h -EXTRA_DIST = leveldb crypto/ctaes +EXTRA_DIST = leveldb $(CTAES_DIST) clean-local: -$(MAKE) -C leveldb clean -- cgit v1.2.3