Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions e2e/infrastructure/TransactionHttp.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2018 NEM
* Copyright 2020 NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,7 +14,6 @@
* limitations under the License.
*/
import { expect } from 'chai';
import * as CryptoJS from 'crypto-js';
import { ChronoUnit } from 'js-joda';
import { sha3_256 } from 'js-sha3';
import { Crypto } from '../../src/core/crypto';
Expand Down Expand Up @@ -68,6 +67,9 @@ import { UInt64 } from '../../src/model/UInt64';
import { IntegrationTestHelper } from './IntegrationTestHelper';
import { LockHashUtils } from '../../src/core/utils/LockHashUtils';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CryptoJS = require('crypto-js');

describe('TransactionHttp', () => {
let transactionHash;
let transactionId;
Expand Down
17 changes: 8 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"typings": "dist/index.d.ts",
"devDependencies": {
"@types/chai": "^4.0.4",
"@types/crypto-js": "^3.1.43",
"@types/lodash": "^4.14.85",
"@types/long": "^4.0.0",
"@types/mocha": "^2.2.44",
Expand Down Expand Up @@ -74,7 +73,7 @@
"dependencies": {
"bluebird": "^3.7.2",
"catbuffer-typescript": "0.0.11",
"crypto-js": "^3.1.9-1",
"crypto-js": "^4.0.0",
"diff": "^4.0.2",
"futoin-hkdf": "^1.3.1",
"js-joda": "^1.6.2",
Expand Down
240 changes: 44 additions & 196 deletions src/core/crypto/Crypto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 NEM
* Copyright 2020 NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,220 +14,68 @@
* limitations under the License.
*/

import { WalletAlgorithm } from '../../model/wallet/WalletAlgorithm';
import { Convert as convert } from '../format/Convert';
import { KeyPair } from './KeyPair';
import * as utility from './Utilities';

// eslint-disable-next-line @typescript-eslint/no-var-requires
const CryptoJS = require('crypto-js');

export class Crypto {
/**
* Encrypt a private key for mobile apps (AES_PBKF2)
*
* @param {string} password - A wallet password
* @param {string} privateKey - An account private key
*
* @return {object} - The encrypted data
* Encrypt data
* @param {string} data
* @param {string} salt
* @param {string} password
*/
public static toMobileKey = (password: string, privateKey: string): any => {
// Errors
if (!password || !privateKey) {
throw new Error('Missing argument !');
}
// Processing
const salt = CryptoJS.lib.WordArray.random(256 / 8);
public static encrypt(data: string, password: string): string {
const salt = CryptoJS.lib.WordArray.random(16);

// generate password based key
const key = CryptoJS.PBKDF2(password, salt, {
keySize: 256 / 32,
iterations: 2000,
keySize: 8,
iterations: 1024,
});
const iv = Crypto.randomBytes(16);
const encIv = {
iv: utility.ua2words(iv, 16),
};
const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Hex.parse(privateKey), key, encIv);
// Result
return {
encrypted: convert.uint8ToHex(iv) + encrypted.ciphertext,
salt: salt.toString(),
};
};

/**
* Derive a private key from a password using count iterations of SHA3-256
*
* @param {string} password - A wallet password
* @param {number} count - A number of iterations above 0
*
* @return {object} - The derived private key
*/
public static derivePassSha = (password: string, count: number): any => {
// Errors
if (!password) {
throw new Error('Missing argument !');
}
if (!count || count <= 0) {
throw new Error('Please provide a count number above 0');
}
// Processing
let data = password;
for (let i = 0; i < count; ++i) {
data = CryptoJS.SHA3(data, {
outputLength: 256,
});
}
// Result
return {
priv: CryptoJS.enc.Hex.stringify(data),
};
};
// encrypt using random IV
const iv = CryptoJS.lib.WordArray.random(16);
const encrypted = CryptoJS.AES.encrypt(data, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC,
});

/**
* Encrypt hex data using a key
*
* @param {string} data - An hex string
* @param {Uint8Array} key - An Uint8Array key
*
* @return {object} - The encrypted data
*/
public static encrypt = (data: string, key: Uint8Array): any => {
// Errors
if (!data || !key) {
throw new Error('Missing argument !');
}
// Processing
const iv = Crypto.randomBytes(16);
const encKey = utility.ua2words(key, 32);
const encIv = {
iv: utility.ua2words(iv, 16),
};
const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Hex.parse(data), encKey, encIv);
// Result
return {
ciphertext: encrypted.ciphertext,
iv,
key,
};
};
// salt (16 bytes) + iv (16 bytes)
// prepend them to the ciphertext for use in decryption
return salt.toString() + iv.toString() + encrypted.toString();
}

/**
* Decrypt data
*
* @param {object} data - An encrypted data object
*
* @return {string} - The decrypted hex string
* @param {string} data
* @param {string} salt
* @param {string} password
*/
public static decrypt = (data: any): string => {
// Errors
if (!data) {
throw new Error('Missing argument !');
}
// Processing
const encKey = utility.ua2words(data.key, 32);
const encIv = {
iv: utility.ua2words(data.iv, 16),
};
// Result
return CryptoJS.enc.Hex.stringify(CryptoJS.AES.decrypt(data, encKey, encIv));
};
public static decrypt(data: string, password: string): string {
const salt = CryptoJS.enc.Hex.parse(data.substr(0, 32));
const iv = CryptoJS.enc.Hex.parse(data.substr(32, 32));
const encrypted = data.substring(64);

/**
* Reveal the private key of an account or derive it from the wallet password
*
* @param {object} common- An object containing password and privateKey field
* @param {object} walletAccount - A wallet account object
* @param {WalletAlgorithm} algo - A wallet algorithm
*
* @return {object|boolean} - The account private key in and object or false
*/
public static passwordToPrivateKey = (common: any, walletAccount: any, algo: WalletAlgorithm): any => {
// Errors
if (!common || !common.password || !walletAccount || !algo) {
throw new Error('Missing argument !');
}
// Processing
let r;
if (algo === WalletAlgorithm.Pass_6k) {
// Brain wallets
if (!walletAccount.encrypted && !walletAccount.iv) {
// Account private key is generated simply using a passphrase so it has no encrypted and iv
r = Crypto.derivePassSha(common.password, 6000);
} else if (!walletAccount.encrypted || !walletAccount.iv) {
// Else if one is missing there is a problem
return false;
} else {
// Else child accounts have encrypted and iv so we decrypt
const pass = Crypto.derivePassSha(common.password, 20);
const obj = {
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
iv: convert.hexToUint8(walletAccount.iv),
key: convert.hexToUint8(pass.priv),
};
const d = Crypto.decrypt(obj);
r = { priv: d };
}
} else if (algo === WalletAlgorithm.Pass_bip32) {
// Wallets from PRNG
const pass = Crypto.derivePassSha(common.password, 20);
const obj = {
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
iv: convert.hexToUint8(walletAccount.iv),
key: convert.hexToUint8(pass.priv),
};
const d = Crypto.decrypt(obj);
r = { priv: d };
} else if (algo === WalletAlgorithm.Pass_enc) {
// Private Key wallets
const pass = Crypto.derivePassSha(common.password, 20);
const obj = {
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
iv: convert.hexToUint8(walletAccount.iv),
key: convert.hexToUint8(pass.priv),
};
const d = Crypto.decrypt(obj);
r = { priv: d };
} else if (algo === WalletAlgorithm.Trezor) {
// HW wallet
r = { priv: '' };
common.isHW = true;
} else {
return false;
}
// Result
common.privateKey = r.priv;
return true;
};
// generate password based key
const key = CryptoJS.PBKDF2(password, salt, {
keySize: 8,
iterations: 1024,
});

/**
* Generate a random key
*
* @return {Uint8Array} - A random key
*/
public static randomKey = (): Uint8Array => {
return Crypto.randomBytes(32);
};
// decrypt using custom IV
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC,
});

/**
* Encode a private key using a password
*
* @param {string} privateKey - An hex private key
* @param {string} password - A password
*
* @return {object} - The encoded data
*/
public static encodePrivateKey = (privateKey: string, password: string): any => {
// Errors
if (!privateKey || !password) {
throw new Error('Missing argument !');
}
// Processing
const pass = Crypto.derivePassSha(password, 20);
const r = Crypto.encrypt(privateKey, convert.hexToUint8(pass.priv));
// Result
return {
ciphertext: CryptoJS.enc.Hex.stringify(r.ciphertext),
iv: convert.uint8ToHex(r.iv),
};
};
return decrypted.toString(CryptoJS.enc.Utf8);
}

/***
* Encode a message, separated from encode() to help testing
Expand Down
16 changes: 9 additions & 7 deletions src/core/crypto/KeyPair.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019 NEM
* Copyright 2020 NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,16 +23,18 @@ export class KeyPair {
* @param {string} privateKeyString A hex encoded private key string.
* @returns {module:crypto/keyPair~KeyPair} The key pair.
*/
public static createKeyPairFromPrivateKeyString(privateKeyString: string): any {
public static createKeyPairFromPrivateKeyString(
privateKeyString: string,
): {
privateKey: Uint8Array;
publicKey: Uint8Array;
} {
const privateKey = convert.hexToUint8(privateKeyString);
if (Utility.Key_Size !== privateKey.length) {
throw Error(`private key has unexpected size: ${privateKey.length}`);
}
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
return {
privateKey,
publicKey: keyPair.publicKey,
};
const { publicKey } = nacl.sign.keyPair.fromSeed(privateKey);
return { privateKey, publicKey };
}

/**
Expand Down
3 changes: 1 addition & 2 deletions src/core/crypto/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/*
import { SHA3Hasher } from './SHA3Hasher';
* Copyright 2019 NEM
* Copyright 2020 NEM
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Loading