Skip to content

Commit

Permalink
Expose StoredKey encryption parameters (#1766)
Browse files Browse the repository at this point in the history
* Expose StoredKey encryption parameters
* Add Swift test
* Test adjustments, review comments
* Add test with default encryption parameters
* Break up EncryptionParameters to EncryptedPayload and EncryptionParameters
* Weak and Standard levels, provide level in constructor, tests with 2 levels
* Enum to TW include
* Expose encryption level in TWStoredKeyCreate
* Remove StoreKey ctor parameter default value
* Update TW enum, to fix swift/kotlin compilation
* Proper breakup of json parse/create
  • Loading branch information
optout21 authored Nov 23, 2021
1 parent 9dc1a2a commit e110188
Show file tree
Hide file tree
Showing 12 changed files with 311 additions and 116 deletions.
11 changes: 10 additions & 1 deletion include/TrustWalletCore/TWStoredKey.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "TWHDWallet.h"
#include "TWPrivateKey.h"
#include "TWString.h"
#include "TWStoredKeyEncryptionLevel.h"

TW_EXTERN_C_BEGIN

Expand All @@ -35,7 +36,11 @@ struct TWStoredKey* _Nullable TWStoredKeyImportHDWallet(TWString* _Nonnull mnemo
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nullable TWStoredKeyImportJSON(TWData* _Nonnull json);

/// Creates a new key. Returned object needs to be deleted.
/// Creates a new key, with given encrpytion strength level. Returned object needs to be deleted.
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nonnull TWStoredKeyCreateLevel(TWString* _Nonnull name, TWData* _Nonnull password, enum TWStoredKeyEncryptionLevel encryptionLevel);

/// DEPRECATED, use TWStoredKeyCreateLevel. Creates a new key. Returned object needs to be deleted.
TW_EXPORT_STATIC_METHOD
struct TWStoredKey* _Nonnull TWStoredKeyCreate(TWString* _Nonnull name, TWData* _Nonnull password);

Expand Down Expand Up @@ -105,4 +110,8 @@ TWData* _Nullable TWStoredKeyExportJSON(struct TWStoredKey* _Nonnull key);
TW_EXPORT_METHOD
bool TWStoredKeyFixAddresses(struct TWStoredKey* _Nonnull key, TWData* _Nonnull password);

/// Retrieve stored key encoding parameters, as JSON string.
TW_EXPORT_PROPERTY
TWString* _Nullable TWStoredKeyEncryptionParameters(struct TWStoredKey* _Nonnull key);

TW_EXTERN_C_END
24 changes: 24 additions & 0 deletions include/TrustWalletCore/TWStoredKeyEncryptionLevel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright © 2017-2021 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 parameter with different security strength, for key store
TW_EXPORT_ENUM(uint32_t)
enum TWStoredKeyEncryptionLevel {
/// Default, which is one of the below values, determined by the implementation.
TWStoredKeyEncryptionLevelDefault = 0,
/// Minimal sufficient level of encryption strength (scrypt 4096)
TWStoredKeyEncryptionLevelWeak = 1,
/// Standard level of encryption strength (scrypt 262k)
TWStoredKeyEncryptionLevelStandard = 2,
};

TW_EXTERN_C_END
112 changes: 61 additions & 51 deletions src/Keystore/EncryptionParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,52 @@ static Data computeMAC(Iter begin, Iter end, const Data& key) {
return Hash::keccak256(data);
}

EncryptionParameters::EncryptionParameters(const Data& password, const Data& data) : mac() {
auto scryptParams = boost::get<ScryptParameters>(kdfParams);
// -----------------
// Encoding/Decoding
// -----------------

namespace CodingKeys {
static const auto encrypted = "ciphertext";
static const auto cipher = "cipher";
static const auto cipherParams = "cipherparams";
static const auto kdf = "kdf";
static const auto kdfParams = "kdfparams";
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 kdf = json[CodingKeys::kdf].get<std::string>();
if (kdf == "scrypt") {
kdfParams = ScryptParameters(json[CodingKeys::kdfParams]);
} else if (kdf == "pbkdf2") {
kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]);
}
}

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

if (kdfParams.which() == 0) {
auto scryptParams = boost::get<ScryptParameters>(kdfParams);
j[CodingKeys::kdf] = "scrypt";
j[CodingKeys::kdfParams] = scryptParams.json();
} else if (kdfParams.which() == 1) {
auto pbkdf2Params = boost::get<PBKDF2Parameters>(kdfParams);
j[CodingKeys::kdf] = "pbkdf2";
j[CodingKeys::kdfParams] = pbkdf2Params.json();
}

return j;
}

EncryptedPayload::EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params) :
params(std::move(params)), mac() {
auto scryptParams = boost::get<ScryptParameters>(params.kdfParams);
auto derivedKey = Data(scryptParams.desiredKeyLength);
scrypt(reinterpret_cast<const byte*>(password.data()), password.size(), scryptParams.salt.data(),
scryptParams.salt.size(), scryptParams.n, scryptParams.r, scryptParams.p, derivedKey.data(),
Expand All @@ -39,31 +83,32 @@ EncryptionParameters::EncryptionParameters(const Data& password, const Data& dat
auto result = aes_encrypt_key128(derivedKey.data(), &ctx);
assert(result == EXIT_SUCCESS);
if (result == EXIT_SUCCESS) {
Data iv = cipherParams.iv;
Data iv = 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);
}
}

EncryptionParameters::~EncryptionParameters() {
EncryptedPayload::~EncryptedPayload() {
std::fill(encrypted.begin(), encrypted.end(), 0);
std::fill(mac.begin(), mac.end(), 0);
}

Data EncryptionParameters::decrypt(const Data& password) const {
Data EncryptedPayload::decrypt(const Data& password) const {
auto derivedKey = Data();
auto mac = Data();

if (kdfParams.which() == 0) {
auto scryptParams = boost::get<ScryptParameters>(kdfParams);
if (params.kdfParams.which() == 0) {
auto scryptParams = boost::get<ScryptParameters>(params.kdfParams);
derivedKey.resize(scryptParams.defaultDesiredKeyLength);
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);
} else if (kdfParams.which() == 1) {
auto pbkdf2Params = boost::get<PBKDF2Parameters>(kdfParams);
} else if (params.kdfParams.which() == 1) {
auto pbkdf2Params = boost::get<PBKDF2Parameters>(params.kdfParams);
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(),
Expand All @@ -78,15 +123,15 @@ Data EncryptionParameters::decrypt(const Data& password) const {
}

Data decrypted(encrypted.size());
Data iv = cipherParams.iv;
if (cipher == "aes-128-ctr") {
Data iv = params.cipherParams.iv;
if (params.cipher == "aes-128-ctr") {
aes_encrypt_ctx ctx;
auto __attribute__((unused)) result = aes_encrypt_key(derivedKey.data(), 16, &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 (cipher == "aes-128-cbc") {
} else if (params.cipher == "aes-128-cbc") {
aes_decrypt_ctx ctx;
auto __attribute__((unused)) result = aes_decrypt_key(derivedKey.data(), 16, &ctx);
assert(result != EXIT_FAILURE);
Expand All @@ -101,50 +146,15 @@ Data EncryptionParameters::decrypt(const Data& password) const {
return decrypted;
}

// -----------------
// Encoding/Decoding
// -----------------

namespace CodingKeys {
static const auto encrypted = "ciphertext";
static const auto cipher = "cipher";
static const auto cipherParams = "cipherparams";
static const auto kdf = "kdf";
static const auto kdfParams = "kdfparams";
static const auto mac = "mac";
} // namespace CodingKeys

EncryptionParameters::EncryptionParameters(const nlohmann::json& json) {
EncryptedPayload::EncryptedPayload(const nlohmann::json& json) {
params = EncryptionParameters(json);
encrypted = parse_hex(json[CodingKeys::encrypted].get<std::string>());
cipher = json[CodingKeys::cipher].get<std::string>();
cipherParams = AESParameters(json[CodingKeys::cipherParams]);
mac = parse_hex(json[CodingKeys::mac].get<std::string>());

auto kdf = json[CodingKeys::kdf].get<std::string>();
if (kdf == "scrypt") {
kdfParams = ScryptParameters(json[CodingKeys::kdfParams]);
} else if (kdf == "pbkdf2") {
kdfParams = PBKDF2Parameters(json[CodingKeys::kdfParams]);
}
}

nlohmann::json EncryptionParameters::json() const {
nlohmann::json j;
nlohmann::json EncryptedPayload::json() const {
nlohmann::json j = params.json();
j[CodingKeys::encrypted] = hex(encrypted);
j[CodingKeys::cipher] = cipher;
j[CodingKeys::cipherParams] = cipherParams.json();
j[CodingKeys::mac] = hex(mac);

if (kdfParams.which() == 0) {
auto scryptParams = boost::get<ScryptParameters>(kdfParams);
j[CodingKeys::kdf] = "scrypt";
j[CodingKeys::kdfParams] = scryptParams.json();
} else if (kdfParams.which() == 1) {
auto pbkdf2Params = boost::get<PBKDF2Parameters>(kdfParams);
j[CodingKeys::kdf] = "pbkdf2";
j[CodingKeys::kdfParams] = pbkdf2Params.json();

}

return j;
}
88 changes: 63 additions & 25 deletions src/Keystore/EncryptionParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,57 @@
#include "PBKDF2Parameters.h"
#include "ScryptParameters.h"
#include "../Data.h"
#include <TrustWalletCore/TWStoredKeyEncryptionLevel.h>

#include <boost/variant.hpp>
#include <nlohmann/json.hpp>
#include <string>

namespace TW::Keystore {

/// Set of parameters used when encoding
struct EncryptionParameters {
static EncryptionParameters getPreset(enum TWStoredKeyEncryptionLevel preset) {
switch (preset) {
case TWStoredKeyEncryptionLevelWeak:
case TWStoredKeyEncryptionLevelDefault:
default:
return EncryptionParameters(AESParameters(), ScryptParameters::Light);
case TWStoredKeyEncryptionLevelStandard:
return EncryptionParameters(AESParameters(), ScryptParameters::Standard);
}
}

/// Cipher algorithm.
std::string cipher = "aes-128-ctr";

/// Cipher parameters.
AESParameters cipherParams = AESParameters();

/// Key derivation function parameters.
boost::variant<ScryptParameters, PBKDF2Parameters> kdfParams = ScryptParameters();

EncryptionParameters() = default;

/// Initializes with standard values.
EncryptionParameters(AESParameters cipherParams, boost::variant<ScryptParameters, PBKDF2Parameters> kdfParams)
: cipherParams(std::move(cipherParams))
, kdfParams(std::move(kdfParams)) {}

/// Initializes with a JSON object.
EncryptionParameters(const nlohmann::json& json);

/// Saves `this` as a JSON object.
nlohmann::json json() const;

EncryptionParameters(const EncryptionParameters& other) = default;
EncryptionParameters(EncryptionParameters&& other) = default;
EncryptionParameters& operator=(const EncryptionParameters& other) = default;
EncryptionParameters& operator=(EncryptionParameters&& other) = default;

virtual ~EncryptionParameters() = default;
};

/// Errors thrown when decrypting a key.
enum class DecryptionError {
unsupportedKDF,
Expand All @@ -27,50 +71,44 @@ enum class DecryptionError {
invalidPassword,
};

struct EncryptionParameters {
/// An encrypted payload data
struct EncryptedPayload {
public:
EncryptionParameters params;

/// Encrypted data.
Data encrypted;

/// Cipher algorithm.
std::string cipher = "aes-128-ctr";

/// Cipher parameters.
AESParameters cipherParams = AESParameters();

/// Key derivation function parameters.
boost::variant<ScryptParameters, PBKDF2Parameters> kdfParams = ScryptParameters();

/// Message authentication code.
Data mac;

EncryptionParameters() = default;
EncryptedPayload() = default;

/// Initializes `EncryptionParameters` with standard values.
EncryptionParameters(const Data& encrypted, AESParameters cipherParams, boost::variant<ScryptParameters, PBKDF2Parameters> kdfParams, const Data& mac)
: encrypted(std::move(encrypted))
, cipherParams(std::move(cipherParams))
, kdfParams(std::move(kdfParams))
/// Initializes with standard values.
EncryptedPayload(const EncryptionParameters& params, const Data& encrypted, const Data& mac)
: params(std::move(params))
, encrypted(std::move(encrypted))
, mac(std::move(mac)) {}

/// Initializes `EncryptionParameters` by encrypting data with a password
/// Initializes by encrypting data with a password
/// using standard values.
EncryptionParameters(const Data& password, const Data& data);
EncryptedPayload(const Data& password, const Data& data, const EncryptionParameters& params);

/// Initializes `EncryptionParameters` with a JSON object.
EncryptionParameters(const nlohmann::json& json);
/// Initializes with a JSON object.
EncryptedPayload(const nlohmann::json& json);

/// Decrypts the payload with the given password.
Data decrypt(const Data& password) const;

/// Saves `this` as a JSON object.
nlohmann::json json() const;

EncryptionParameters(const EncryptionParameters& other) = default;
EncryptionParameters(EncryptionParameters&& other) = default;
EncryptionParameters& operator=(const EncryptionParameters& other) = default;
EncryptionParameters& operator=(EncryptionParameters&& other) = default;
EncryptedPayload(const EncryptedPayload& other) = default;
EncryptedPayload(EncryptedPayload&& other) = default;
EncryptedPayload& operator=(const EncryptedPayload& other) = default;
EncryptedPayload& operator=(EncryptedPayload&& other) = default;

virtual ~EncryptionParameters();
virtual ~EncryptedPayload();
};

} // namespace TW::Keystore
4 changes: 4 additions & 0 deletions src/Keystore/ScryptParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
using namespace TW;
using namespace TW::Keystore;

ScryptParameters ScryptParameters::Light = ScryptParameters(Data(), lightN, defaultR, lightP, defaultDesiredKeyLength);

ScryptParameters ScryptParameters::Standard = ScryptParameters(Data(), standardN, defaultR, standardP, defaultDesiredKeyLength);

ScryptParameters::ScryptParameters() : salt(32) {
random_buffer(salt.data(), salt.size());
}
Expand Down
4 changes: 4 additions & 0 deletions src/Keystore/ScryptParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ enum class ScryptValidationError {

/// Scrypt function parameters.
struct ScryptParameters {
static ScryptParameters Light;

static ScryptParameters Standard;

/// The N parameter of Scrypt encryption algorithm, using 256MB memory and
/// taking approximately 1s CPU time on a modern processor.
static const uint32_t standardN = 1 << 18;
Expand Down
Loading

0 comments on commit e110188

Please sign in to comment.