Skip to content

Commit

Permalink
Merge #1486: Replace OpenSSL AES with ctaes-based version
Browse files Browse the repository at this point in the history
a49d5d4 Remove unused OpenSSL includes to make it more clear where OpenSSL is used (furszy)
8adbaab Build: Add ctaes to CMakeLists (Fuzzbawls)
5975fc2 build: Enumerate ctaes rather than globbing (furszy)
096d1b7 crypter: constify encrypt/decrypt (furszy)
ad984e4 crypter: add tests for crypter (Cory Fields)
be94566 crypter: add a BytesToKey clone to replace the use of openssl (furszy)
2aeb09e crypter: hook up the new aes cbc classes (furszy)
b2175f3 crypter: fix the stored initialization vector size (furszy)
86053f5 crypto: add aes cbc tests (Cory Fields)
1708a62 crypto: add AES 128/256 CBC classes (Cory Fields)
2395fa7 Add ctaes-based constant time AES implementation (Pieter Wuille)
bfb12ba Squashed 'src/crypto/ctaes/' content from commit cd3c3ac (Pieter Wuille)

Pull request description:

  Coming from upstream [7689](bitcoin#7689)

  ```
  slow and simple AES implementation.

  Performance on modern systems should be around 2-10 Mbyte/s (for short to larger messages), which is plenty for the needs of our wallet.
  ```

ACKs for top commit:
  Fuzzbawls:
    ACK a49d5d4
  random-zebra:
    utACK cleanup commit a49d5d4

Tree-SHA512: f825aaf200d214adb804f1f1a1849d8f6a052ebe766f015215e958d135acefb7ce2580205bdea309a8be3c3aaf8007cd9aa2399e071c416eb90f1c55e0342ae2
  • Loading branch information
Fuzzbawls committed May 16, 2020
2 parents 1f9e7e4 + a49d5d4 commit e146131
Show file tree
Hide file tree
Showing 15 changed files with 1,769 additions and 118 deletions.
3 changes: 3 additions & 0 deletions CMakeLists.txt
Expand Up @@ -271,6 +271,7 @@ file(GLOB SCRIPT_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/script/*.h)
file(GLOB RPC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/rpc/*.h)
file(GLOB COMPAT_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/compat/*.h)
file(GLOB CONSENSUS_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/consensus/*.h)
file(GLOB CTAES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/ctaes/*.h)

source_group("BitcoinHeaders" FILES
${HEADERS}
Expand All @@ -281,6 +282,7 @@ source_group("BitcoinHeaders" FILES
${RPC_HEADERS}
${COMPAT_HEADERS}
${CONSENSUS_HEADERS}
${CTAES_HEADERS}
./src/support/cleanse.h
)

Expand Down Expand Up @@ -384,6 +386,7 @@ target_include_directories(WALLET_A PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src
)

set(BITCOIN_CRYPTO_SOURCES
./src/crypto/aes.cpp
./src/crypto/sha1.cpp
./src/crypto/sha256.cpp
./src/crypto/sha512.cpp
Expand Down
10 changes: 9 additions & 1 deletion src/Makefile.am
Expand Up @@ -308,6 +308,8 @@ libbitcoin_wallet_a_SOURCES = \
crypto_libbitcoin_crypto_a_CPPFLAGS = $(AM_CPPFLAGS) $(PIC_FLAGS)
crypto_libbitcoin_crypto_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIC_FLAGS)
crypto_libbitcoin_crypto_a_SOURCES = \
crypto/aes.cpp \
crypto/aes.h \
crypto/sha1.cpp \
crypto/sha256.cpp \
crypto/sha512.cpp \
Expand Down Expand Up @@ -566,7 +568,13 @@ CLEANFILES += zmq/*.gcda zmq/*.gcno
CLEANFILES += zpiv/*.gcda zpiv/*.gcno
CLEANFILES += obj/build.h

EXTRA_DIST =
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

EXTRA_DIST = $(CTAES_DIST)


config/pivx-config.h: config/stamp-h1
Expand Down
3 changes: 2 additions & 1 deletion src/Makefile.test.include
Expand Up @@ -84,7 +84,8 @@ BITCOIN_TESTS =\
if ENABLE_WALLET
BITCOIN_TESTS += \
test/accounting_tests.cpp \
wallet/test/wallet_tests.cpp
wallet/test/wallet_tests.cpp \
wallet/test/crypto_tests.cpp
endif

test_test_pivx_SOURCES = $(BITCOIN_TEST_SUITE) $(BITCOIN_TESTS) $(JSON_TEST_FILES) $(RAW_TEST_FILES)
Expand Down
160 changes: 50 additions & 110 deletions src/crypter.cpp
Expand Up @@ -5,25 +5,51 @@

#include "crypter.h"

#include "crypto/aes.h"
#include "crypto/sha512.h"
#include "script/script.h"
#include "script/standard.h"
#include "util.h"
#include "init.h"
#include "uint256.h"

#include <openssl/aes.h>
#include <openssl/evp.h>
#include "wallet/wallet.h"

int CCrypter::BytesToKeySHA512AES(const std::vector<unsigned char>& 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<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod)
{
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);
i = BytesToKeySHA512AES(chSalt, strKeyData, nRounds, chKey, chIV);

if (i != (int)WALLET_CRYPTO_KEY_SIZE) {
memory_cleanse(chKey, sizeof(chKey));
Expand All @@ -37,7 +63,7 @@ bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::v

bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& 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);
Expand All @@ -47,145 +73,59 @@ bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigne
return true;
}

bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char>& vchCiphertext)
bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char>& vchCiphertext) const
{
if (!fKeySet)
return false;

// 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<unsigned char>(nCLen);

bool fOk = true;

EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
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_free(ctx);
// n + AES_BLOCKSIZE bytes
vchCiphertext.resize(vchPlaintext.size() + AES_BLOCKSIZE);

if (!fOk) return false;

vchCiphertext.resize(nCLen + nFLen);
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);
return true;
}

bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext)
bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const
{
if (!fKeySet)
return false;

// plaintext will always be equal to or lesser than length of ciphertext
int nLen = vchCiphertext.size();
int nPLen = nLen, nFLen = 0;

vchPlaintext = CKeyingMaterial(nPLen);

bool fOk = true;

EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
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_free(ctx);

if (!fOk) return false;

vchPlaintext.resize(nPLen + nFLen);
vchPlaintext.resize(nLen);
AES256CBCDecrypt dec(chKey, chIV, true);
nLen = dec.Decrypt(&vchCiphertext[0], vchCiphertext.size(), &vchPlaintext[0]);
if(nLen == 0)
return false;
vchPlaintext.resize(nLen);
return true;
}


bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial& vchPlaintext, const uint256& nIV, std::vector<unsigned char>& vchCiphertext)
{
CCrypter cKeyCrypter;
std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
std::vector<unsigned char> 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);
}


// General secure AES 256 CBC encryption routine
bool EncryptAES256(const SecureString& sKey, const SecureString& sPlaintext, const std::string& sIV, std::string& sCiphertext)
{
// max ciphertext len for a n bytes of plaintext is
// n + AES_BLOCK_SIZE - 1 bytes
int nLen = sPlaintext.size();
int nCLen = nLen + AES_BLOCK_SIZE;
int nFLen = 0;

// Verify key sizes
if (sKey.size() != 32 || sIV.size() != AES_BLOCK_SIZE) {
LogPrintf("crypter EncryptAES256 - Invalid key or block size: Key: %d sIV:%d\n", sKey.size(), sIV.size());
return false;
}

// Prepare output buffer
sCiphertext.resize(nCLen);

// Perform the encryption
EVP_CIPHER_CTX* ctx;

bool fOk = true;

ctx = EVP_CIPHER_CTX_new();
if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*)&sKey[0], (const unsigned char*)&sIV[0]);
if (fOk) fOk = EVP_EncryptUpdate(ctx, (unsigned char*)&sCiphertext[0], &nCLen, (const unsigned char*)&sPlaintext[0], nLen);
if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (unsigned char*)(&sCiphertext[0]) + nCLen, &nFLen);
EVP_CIPHER_CTX_free(ctx);

if (!fOk) return false;

sCiphertext.resize(nCLen + nFLen);
return true;
}


bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
{
CCrypter cKeyCrypter;
std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
std::vector<unsigned char> 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));
}

bool DecryptAES256(const SecureString& sKey, const std::string& sCiphertext, const std::string& sIV, SecureString& sPlaintext)
{
// plaintext will always be equal to or lesser than length of ciphertext
int nLen = sCiphertext.size();
int nPLen = nLen, nFLen = 0;

// Verify key sizes
if (sKey.size() != 32 || sIV.size() != AES_BLOCK_SIZE) {
LogPrintf("crypter DecryptAES256 - Invalid key or block size\n");
return false;
}

sPlaintext.resize(nPLen);

EVP_CIPHER_CTX* ctx;

bool fOk = true;

ctx = EVP_CIPHER_CTX_new();
if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, (const unsigned char*)&sKey[0], (const unsigned char*)&sIV[0]);
if (fOk) fOk = EVP_DecryptUpdate(ctx, (unsigned char*)&sPlaintext[0], &nPLen, (const unsigned char*)&sCiphertext[0], nLen);
if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (unsigned char*)(&sPlaintext[0]) + nPLen, &nFLen);
EVP_CIPHER_CTX_free(ctx);

if (!fOk) return false;

sPlaintext.resize(nPLen + nFLen);
return true;
}


bool CCryptoKeyStore::SetCrypted()
{
LOCK(cs_KeyStore);
Expand Down
18 changes: 12 additions & 6 deletions src/crypter.h
Expand Up @@ -14,6 +14,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,
Expand Down Expand Up @@ -68,18 +69,26 @@ class CMasterKey

typedef std::vector<unsigned char, secure_allocator<unsigned char> > 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_KEY_SIZE];
unsigned char chIV[WALLET_CRYPTO_IV_SIZE];
bool fKeySet;

int BytesToKeySHA512AES(const std::vector<unsigned char>& chSalt, const SecureString& strKeyData, int count, unsigned char *key,unsigned char *iv) const;

public:
bool SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod);
bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char>& vchCiphertext);
bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext);
bool Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char>& vchCiphertext) const;
bool Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext) const;
bool SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV);

void CleanKey()
Expand Down Expand Up @@ -112,9 +121,6 @@ class CCrypter
bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial& vchPlaintext, const uint256& nIV, std::vector<unsigned char>& vchCiphertext);
bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext);

bool EncryptAES256(const SecureString& sKey, const SecureString& sPlaintext, const std::string& sIV, std::string& sCiphertext);
bool DecryptAES256(const SecureString& sKey, const std::string& sCiphertext, const std::string& sIV, SecureString& sPlaintext);


/** Keystore which keeps the private keys encrypted.
* It derives from the basic key store, which is used if no encryption is active.
Expand Down

0 comments on commit e146131

Please sign in to comment.