Skip to content

Commit

Permalink
[Encryption]: add support for aes-256-ctr (trustwallet#2726)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milerius authored and DeanDonkov committed Apr 3, 2023
1 parent c25dd16 commit a7168de
Show file tree
Hide file tree
Showing 20 changed files with 531 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.junit.Assert.*
import org.junit.Test
import wallet.core.jni.StoredKey
import wallet.core.jni.CoinType
import wallet.core.jni.StoredKeyEncryption

class TestKeyStore {

Expand All @@ -21,6 +22,16 @@ class TestKeyStore {
assertNotNull(result2)
}

@Test
fun testDecryptMnemonicAes256() {
val keyStore = StoredKey("Test Wallet", "password".toByteArray(), StoredKeyEncryption.AES256CTR)
val result = keyStore.decryptMnemonic("wrong".toByteArray())
val result2 = keyStore.decryptMnemonic("password".toByteArray())

assertNull(result)
assertNotNull(result2)
}

@Test
fun testRemoveCoins() {
val password = "password".toByteArray()
Expand Down
7 changes: 7 additions & 0 deletions include/TrustWalletCore/TWBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@
#define TW_ASSUME_NONNULL_END
#endif

#if defined(__cplusplus) && (__cplusplus >= 201402L)
# define TW_DEPRECATED(since) [[deprecated("Since " #since)]]
# define TW_DEPRECATED_FOR(since, replacement) [[deprecated("Since " #since "; use " #replacement)]]
#else
# define TW_DEPRECATED(since)
# define TW_DEPRECATED_FOR(since, replacement)
#endif

#if !__has_feature(nullability)
#ifndef _Nullable
Expand Down
49 changes: 48 additions & 1 deletion include/TrustWalletCore/TWStoredKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "TWHDWallet.h"
#include "TWPrivateKey.h"
#include "TWStoredKeyEncryptionLevel.h"
#include "TWStoredKeyEncryption.h"
#include "TWString.h"

TW_EXTERN_C_BEGIN
Expand Down Expand Up @@ -40,6 +41,18 @@ struct TWStoredKey* _Nullable TWStoredKeyLoad(TWString* _Nonnull path);
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);

/// Imports a private key.
///
/// \param privateKey Non-null Block of data private key
/// \param name The name of the stored key to import as a non-null string
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKeyWithEncryption(TWData* _Nonnull privateKey, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports an HD wallet.
///
/// \param mnemonic Non-null bip39 mnemonic
Expand All @@ -51,6 +64,18 @@ struct TWStoredKey* _Nullable TWStoredKeyImportPrivateKey(TWData* _Nonnull priva
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin);

/// Imports an HD wallet.
///
/// \param mnemonic Non-null bip39 mnemonic
/// \param name The name of the stored key to import as a non-null string
/// \param password Non-null block of data, password of the stored key
/// \param coin the coin type
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return Nullptr if the key can't be imported, the stored key otherwise
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportHDWalletWithEncryption(TWString* _Nonnull mnemonic, TWString* _Nonnull name, TWData* _Nonnull password, enum TWCoinType coin, enum TWStoredKeyEncryption encryption);

/// Imports a key from JSON.
///
/// \param json Json stored key import format as a non-null block of data
Expand All @@ -59,16 +84,28 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json);

/// Creates a new key, with given encryption strength level. Returned object needs to be deleted.
/// Creates a new key, with given encryption strength level. Returned object needs to be deleted.
///
/// \param name The name of the key to be stored
/// \param password Non-null block of data, password of the stored key
/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return The stored key as a non-null pointer
TW_DEPRECATED_FOR("3.1.1", "TWStoredKeyCreateLevelAndEncryption")
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel);

/// Creates a new key, with given encryption strength level. Returned object needs to be deleted.
///
/// \param name The name of the key to be stored
/// \param password Non-null block of data, password of the stored key
/// \param encryptionLevel The level of encryption, see \TWStoredKeyEncryptionLevel
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return The stored key as a non-null pointer
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nonnull TWStoredKeyCreateLevelAndEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel, enum TWStoredKeyEncryption encryption);

/// Creates a new key.
///
/// \deprecated use TWStoredKeyCreateLevel.
Expand All @@ -78,6 +115,16 @@ struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWD
/// \return The stored key as a non-null pointer
TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password);

/// Creates a new key.
///
/// \deprecated use TWStoredKeyCreateLevel.
/// \param name The name of the key to be stored
/// \param password Non-null block of data, password of the stored key
/// \param encryption cipher encryption mode
/// \note Returned object needs to be deleted with \TWStoredKeyDelete
/// \return The stored key as a non-null pointer
TW_EXPORT_STATIC_METHOD struct TWStoredKey* _Nonnull TWStoredKeyCreateEncryption(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryption encryption);

/// Delete a stored key
///
/// \param key The key to be deleted
Expand Down
22 changes: 22 additions & 0 deletions include/TrustWalletCore/TWStoredKeyEncryption.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright © 2017-2022 Trust Wallet.
//
// This file is part of Trust. The full Trust copyright notice, including
// terms governing use, modification, and redistribution, is contained in the
// file LICENSE at the root of the source code distribution tree.

#pragma once

#include "TWBase.h"

TW_EXTERN_C_BEGIN

/// Preset encryption kind
TW_EXPORT_ENUM(uint32_t)
enum TWStoredKeyEncryption {
TWStoredKeyEncryptionAes128Ctr = 0,
TWStoredKeyEncryptionAes128Cbc = 1,
TWStoredKeyEncryptionAes192Ctr = 2,
TWStoredKeyEncryptionAes256Ctr = 3,
};

TW_EXTERN_C_END
41 changes: 36 additions & 5 deletions src/Keystore/AESParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,44 @@

using namespace TW;

namespace TW::Keystore {
namespace {

AESParameters::AESParameters() {
iv = Data(blockSize, 0);
Data generateIv(std::size_t blockSize = TW::Keystore::gBlockSize) {
auto iv = Data(blockSize, 0);
random_buffer(iv.data(), blockSize);
return iv;
}

static TWStoredKeyEncryption getCipher(const std::string& cipher) {
if (cipher == Keystore::gAes128Ctr) {
return TWStoredKeyEncryption::TWStoredKeyEncryptionAes128Ctr;
} else if (cipher == Keystore::gAes192Ctr) {
return TWStoredKeyEncryption::TWStoredKeyEncryptionAes192Ctr;
} else if (cipher == Keystore::gAes256Ctr) {
return TWStoredKeyEncryption::TWStoredKeyEncryptionAes256Ctr;
}
return TWStoredKeyEncryptionAes128Ctr;
}

const std::unordered_map<TWStoredKeyEncryption, Keystore::AESParameters> gEncryptionRegistry{
{TWStoredKeyEncryptionAes128Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes128Ctr}},
{TWStoredKeyEncryptionAes128Cbc, Keystore::AESParameters{.mKeyLength = Keystore::A128, .mCipher = Keystore::gAes128Cbc, .mCipherEncryption = TWStoredKeyEncryptionAes128Cbc}},
{TWStoredKeyEncryptionAes192Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A192, .mCipher = Keystore::gAes192Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes192Ctr}},
{TWStoredKeyEncryptionAes256Ctr, Keystore::AESParameters{.mKeyLength = Keystore::A256, .mCipher = Keystore::gAes256Ctr, .mCipherEncryption = TWStoredKeyEncryptionAes256Ctr}}
};
} // namespace

namespace TW::Keystore {

namespace CodingKeys {
static const auto iv = "iv";
} // namespace CodingKeys

/// Initializes `AESParameters` with a JSON object.
AESParameters::AESParameters(const nlohmann::json& json) {
iv = parse_hex(json[CodingKeys::iv].get<std::string>());
AESParameters AESParameters::AESParametersFromJson(const nlohmann::json& json, const std::string& cipher) {
auto parameters = AESParameters::AESParametersFromEncryption(getCipher(cipher));
parameters.iv = parse_hex(json[CodingKeys::iv].get<std::string>());
return parameters;
}

/// Saves `this` as a JSON object.
Expand All @@ -35,4 +59,11 @@ nlohmann::json AESParameters::json() const {
return j;
}

AESParameters AESParameters::AESParametersFromEncryption(TWStoredKeyEncryption encryption) {
auto parameters = gEncryptionRegistry.at(encryption);
// be sure to regenerate an iv.
parameters.iv = generateIv();
return parameters;
}

} // namespace TW::Keystore
29 changes: 23 additions & 6 deletions src/Keystore/AESParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,37 @@
#include "Data.h"

#include <nlohmann/json.hpp>
#include <TrustWalletCore/TWStoredKeyEncryption.h>

namespace TW::Keystore {

// AES128 parameters.
struct AESParameters {
static const std::size_t blockSize = 128 / 8;
enum AESKeySize : std::int32_t {
Uninitialized = 0,
A128 = 16,
A192 = 24,
A256 = 32,
};

inline constexpr std::size_t gBlockSize{16};
inline constexpr const char* gAes128Ctr{"aes-128-ctr"};
inline constexpr const char* gAes128Cbc{"aes-128-cbc"};
inline constexpr const char* gAes192Ctr{"aes-192-ctr"};
inline constexpr const char* gAes256Ctr{"aes-256-ctr"};

// AES128/192/256 parameters.
struct AESParameters {
// For AES, your block length is always going to be 128 bits/16 bytes
std::int32_t mBlockSize{gBlockSize};
std::int32_t mKeyLength{A128};
std::string mCipher{gAes128Ctr};
TWStoredKeyEncryption mCipherEncryption{TWStoredKeyEncryptionAes128Ctr};
Data iv;

/// Initializes `AESParameters` with a random `iv` for AES 128.
AESParameters();
/// Initializes `AESParameters` with a encryption cipher.
static AESParameters AESParametersFromEncryption(TWStoredKeyEncryption encryption);;

/// Initializes `AESParameters` with a JSON object.
AESParameters(const nlohmann::json& json);
static AESParameters AESParametersFromJson(const nlohmann::json& json, const std::string& cipher);

/// Saves `this` as a JSON object.
nlohmann::json json() const;
Expand Down
40 changes: 26 additions & 14 deletions src/Keystore/EncryptionParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ static const auto mac = "mac";
} // namespace CodingKeys

EncryptionParameters::EncryptionParameters(const nlohmann::json& json) {
cipher = json[CodingKeys::cipher].get<std::string>();
cipherParams = AESParameters(json[CodingKeys::cipherParams]);
auto cipher = json[CodingKeys::cipher].get<std::string>();
cipherParams = AESParameters::AESParametersFromJson(json[CodingKeys::cipherParams], cipher);

auto kdf = json[CodingKeys::kdf].get<std::string>();
if (kdf == "scrypt") {
Expand All @@ -53,7 +53,7 @@ EncryptionParameters::EncryptionParameters(const nlohmann::json& json) {

nlohmann::json EncryptionParameters::json() const {
nlohmann::json j;
j[CodingKeys::cipher] = cipher;
j[CodingKeys::cipher] = cipher();
j[CodingKeys::cipherParams] = cipherParams.json();

if (auto* scryptParams = std::get_if<ScryptParameters>(&kdfParams); scryptParams) {
Expand All @@ -76,14 +76,25 @@ EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const
scryptParams.desiredKeyLength);

aes_encrypt_ctx ctx;
auto result = aes_encrypt_key128(derivedKey.data(), &ctx);
auto result = 0;
switch(this->params.cipherParams.mCipherEncryption) {
case TWStoredKeyEncryptionAes128Ctr:
case TWStoredKeyEncryptionAes128Cbc:
result = aes_encrypt_key128(derivedKey.data(), &ctx);
break;
case TWStoredKeyEncryptionAes192Ctr:
result = aes_encrypt_key192(derivedKey.data(), &ctx);
break;
case TWStoredKeyEncryptionAes256Ctr:
result = aes_encrypt_key256(derivedKey.data(), &ctx);
break;
}
assert(result == EXIT_SUCCESS);
if (result == EXIT_SUCCESS) {
Data iv = this->params.cipherParams.iv;
encrypted = Data(data.size());
aes_ctr_encrypt(data.data(), encrypted.data(), static_cast<int>(data.size()), iv.data(), aes_ctr_cbuf_inc, &ctx);

_mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted);
_mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted);
}
}

Expand All @@ -101,13 +112,13 @@ Data EncryptedPayload::decrypt(const Data& password) const {
scrypt(password.data(), password.size(), scryptParams->salt.data(),
scryptParams->salt.size(), scryptParams->n, scryptParams->r, scryptParams->p, derivedKey.data(),
scryptParams->defaultDesiredKeyLength);
mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted);
mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted);
} else if (auto* pbkdf2Params = std::get_if<PBKDF2Parameters>(&params.kdfParams); pbkdf2Params) {
derivedKey.resize(pbkdf2Params->defaultDesiredKeyLength);
pbkdf2_hmac_sha256(password.data(), static_cast<int>(password.size()), pbkdf2Params->salt.data(),
static_cast<int>(pbkdf2Params->salt.size()), pbkdf2Params->iterations, derivedKey.data(),
pbkdf2Params->defaultDesiredKeyLength);
mac = computeMAC(derivedKey.end() - 16, derivedKey.end(), encrypted);
mac = computeMAC(derivedKey.end() - params.getKeyBytesSize(), derivedKey.end(), encrypted);
} else {
throw DecryptionError::unsupportedKDF;
}
Expand All @@ -118,20 +129,21 @@ Data EncryptedPayload::decrypt(const Data& password) const {

Data decrypted(encrypted.size());
Data iv = params.cipherParams.iv;
if (params.cipher == "aes-128-ctr") {
const auto encryption = params.cipherParams.mCipherEncryption;
if (encryption == TWStoredKeyEncryptionAes128Ctr || encryption == TWStoredKeyEncryptionAes256Ctr) {
aes_encrypt_ctx ctx;
auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &ctx);
[[maybe_unused]] auto result = aes_encrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx);
assert(result != EXIT_FAILURE);

aes_ctr_decrypt(encrypted.data(), decrypted.data(), static_cast<int>(encrypted.size()), iv.data(),
aes_ctr_cbuf_inc, &ctx);
} else if (params.cipher == "aes-128-cbc") {
} else if (encryption == TWStoredKeyEncryptionAes128Cbc) {
aes_decrypt_ctx ctx;
auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx);
[[maybe_unused]] auto result = aes_decrypt_key(derivedKey.data(), params.getKeyBytesSize(), &ctx);
assert(result != EXIT_FAILURE);

for (auto i = 0ul; i < encrypted.size(); i += 16) {
aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, 16, iv.data(), &ctx);
for (auto i = 0ul; i < encrypted.size(); i += params.getKeyBytesSize()) {
aes_cbc_decrypt(encrypted.data() + i, decrypted.data() + i, params.getKeyBytesSize(), iv.data(), &ctx);
}
} else {
throw DecryptionError::unsupportedCipher;
Expand Down
Loading

0 comments on commit a7168de

Please sign in to comment.