Skip to content

Commit c1aab78

Browse files
decentraliserrg911
andauthored
upgrade crypto-js to v4.0.0, add AESEncryptionService and apply it to SimpleWallet, fixes #524 (#530)
* upgrade crypto-js to v4.0.0, add AESEncryptionService and apply it to SimpleWallet, fixes #524 * remove passwordToPrivateKey, rename toMobileKey by encryptPBKDF2 * accept string as password in AESEncryptionService * remove unused functions in Crypto, merge AES Encryption service into Crypto Co-authored-by: Steven Liu <xian.f.liu@gmail.com>
1 parent a03e611 commit c1aab78

18 files changed

+156
-754
lines changed

e2e/infrastructure/TransactionHttp.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2018 NEM
2+
* Copyright 2020 NEM
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,7 +14,6 @@
1414
* limitations under the License.
1515
*/
1616
import { expect } from 'chai';
17-
import * as CryptoJS from 'crypto-js';
1817
import { ChronoUnit } from 'js-joda';
1918
import { sha3_256 } from 'js-sha3';
2019
import { Crypto } from '../../src/core/crypto';
@@ -68,6 +67,9 @@ import { UInt64 } from '../../src/model/UInt64';
6867
import { IntegrationTestHelper } from './IntegrationTestHelper';
6968
import { LockHashUtils } from '../../src/core/utils/LockHashUtils';
7069

70+
// eslint-disable-next-line @typescript-eslint/no-var-requires
71+
const CryptoJS = require('crypto-js');
72+
7173
describe('TransactionHttp', () => {
7274
let transactionHash;
7375
let transactionId;

package-lock.json

Lines changed: 8 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"typings": "dist/index.d.ts",
4343
"devDependencies": {
4444
"@types/chai": "^4.0.4",
45-
"@types/crypto-js": "^3.1.43",
4645
"@types/lodash": "^4.14.85",
4746
"@types/long": "^4.0.0",
4847
"@types/mocha": "^2.2.44",
@@ -74,7 +73,7 @@
7473
"dependencies": {
7574
"bluebird": "^3.7.2",
7675
"catbuffer-typescript": "0.0.11",
77-
"crypto-js": "^3.1.9-1",
76+
"crypto-js": "^4.0.0",
7877
"diff": "^4.0.2",
7978
"futoin-hkdf": "^1.3.1",
8079
"js-joda": "^1.6.2",

src/core/crypto/Crypto.ts

Lines changed: 44 additions & 196 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 NEM
2+
* Copyright 2020 NEM
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,220 +14,68 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { WalletAlgorithm } from '../../model/wallet/WalletAlgorithm';
1817
import { Convert as convert } from '../format/Convert';
1918
import { KeyPair } from './KeyPair';
2019
import * as utility from './Utilities';
20+
2121
// eslint-disable-next-line @typescript-eslint/no-var-requires
2222
const CryptoJS = require('crypto-js');
23+
2324
export class Crypto {
2425
/**
25-
* Encrypt a private key for mobile apps (AES_PBKF2)
26-
*
27-
* @param {string} password - A wallet password
28-
* @param {string} privateKey - An account private key
29-
*
30-
* @return {object} - The encrypted data
26+
* Encrypt data
27+
* @param {string} data
28+
* @param {string} salt
29+
* @param {string} password
3130
*/
32-
public static toMobileKey = (password: string, privateKey: string): any => {
33-
// Errors
34-
if (!password || !privateKey) {
35-
throw new Error('Missing argument !');
36-
}
37-
// Processing
38-
const salt = CryptoJS.lib.WordArray.random(256 / 8);
31+
public static encrypt(data: string, password: string): string {
32+
const salt = CryptoJS.lib.WordArray.random(16);
33+
34+
// generate password based key
3935
const key = CryptoJS.PBKDF2(password, salt, {
40-
keySize: 256 / 32,
41-
iterations: 2000,
36+
keySize: 8,
37+
iterations: 1024,
4238
});
43-
const iv = Crypto.randomBytes(16);
44-
const encIv = {
45-
iv: utility.ua2words(iv, 16),
46-
};
47-
const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Hex.parse(privateKey), key, encIv);
48-
// Result
49-
return {
50-
encrypted: convert.uint8ToHex(iv) + encrypted.ciphertext,
51-
salt: salt.toString(),
52-
};
53-
};
5439

55-
/**
56-
* Derive a private key from a password using count iterations of SHA3-256
57-
*
58-
* @param {string} password - A wallet password
59-
* @param {number} count - A number of iterations above 0
60-
*
61-
* @return {object} - The derived private key
62-
*/
63-
public static derivePassSha = (password: string, count: number): any => {
64-
// Errors
65-
if (!password) {
66-
throw new Error('Missing argument !');
67-
}
68-
if (!count || count <= 0) {
69-
throw new Error('Please provide a count number above 0');
70-
}
71-
// Processing
72-
let data = password;
73-
for (let i = 0; i < count; ++i) {
74-
data = CryptoJS.SHA3(data, {
75-
outputLength: 256,
76-
});
77-
}
78-
// Result
79-
return {
80-
priv: CryptoJS.enc.Hex.stringify(data),
81-
};
82-
};
40+
// encrypt using random IV
41+
const iv = CryptoJS.lib.WordArray.random(16);
42+
const encrypted = CryptoJS.AES.encrypt(data, key, {
43+
iv: iv,
44+
padding: CryptoJS.pad.Pkcs7,
45+
mode: CryptoJS.mode.CBC,
46+
});
8347

84-
/**
85-
* Encrypt hex data using a key
86-
*
87-
* @param {string} data - An hex string
88-
* @param {Uint8Array} key - An Uint8Array key
89-
*
90-
* @return {object} - The encrypted data
91-
*/
92-
public static encrypt = (data: string, key: Uint8Array): any => {
93-
// Errors
94-
if (!data || !key) {
95-
throw new Error('Missing argument !');
96-
}
97-
// Processing
98-
const iv = Crypto.randomBytes(16);
99-
const encKey = utility.ua2words(key, 32);
100-
const encIv = {
101-
iv: utility.ua2words(iv, 16),
102-
};
103-
const encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Hex.parse(data), encKey, encIv);
104-
// Result
105-
return {
106-
ciphertext: encrypted.ciphertext,
107-
iv,
108-
key,
109-
};
110-
};
48+
// salt (16 bytes) + iv (16 bytes)
49+
// prepend them to the ciphertext for use in decryption
50+
return salt.toString() + iv.toString() + encrypted.toString();
51+
}
11152

11253
/**
11354
* Decrypt data
114-
*
115-
* @param {object} data - An encrypted data object
116-
*
117-
* @return {string} - The decrypted hex string
55+
* @param {string} data
56+
* @param {string} salt
57+
* @param {string} password
11858
*/
119-
public static decrypt = (data: any): string => {
120-
// Errors
121-
if (!data) {
122-
throw new Error('Missing argument !');
123-
}
124-
// Processing
125-
const encKey = utility.ua2words(data.key, 32);
126-
const encIv = {
127-
iv: utility.ua2words(data.iv, 16),
128-
};
129-
// Result
130-
return CryptoJS.enc.Hex.stringify(CryptoJS.AES.decrypt(data, encKey, encIv));
131-
};
59+
public static decrypt(data: string, password: string): string {
60+
const salt = CryptoJS.enc.Hex.parse(data.substr(0, 32));
61+
const iv = CryptoJS.enc.Hex.parse(data.substr(32, 32));
62+
const encrypted = data.substring(64);
13263

133-
/**
134-
* Reveal the private key of an account or derive it from the wallet password
135-
*
136-
* @param {object} common- An object containing password and privateKey field
137-
* @param {object} walletAccount - A wallet account object
138-
* @param {WalletAlgorithm} algo - A wallet algorithm
139-
*
140-
* @return {object|boolean} - The account private key in and object or false
141-
*/
142-
public static passwordToPrivateKey = (common: any, walletAccount: any, algo: WalletAlgorithm): any => {
143-
// Errors
144-
if (!common || !common.password || !walletAccount || !algo) {
145-
throw new Error('Missing argument !');
146-
}
147-
// Processing
148-
let r;
149-
if (algo === WalletAlgorithm.Pass_6k) {
150-
// Brain wallets
151-
if (!walletAccount.encrypted && !walletAccount.iv) {
152-
// Account private key is generated simply using a passphrase so it has no encrypted and iv
153-
r = Crypto.derivePassSha(common.password, 6000);
154-
} else if (!walletAccount.encrypted || !walletAccount.iv) {
155-
// Else if one is missing there is a problem
156-
return false;
157-
} else {
158-
// Else child accounts have encrypted and iv so we decrypt
159-
const pass = Crypto.derivePassSha(common.password, 20);
160-
const obj = {
161-
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
162-
iv: convert.hexToUint8(walletAccount.iv),
163-
key: convert.hexToUint8(pass.priv),
164-
};
165-
const d = Crypto.decrypt(obj);
166-
r = { priv: d };
167-
}
168-
} else if (algo === WalletAlgorithm.Pass_bip32) {
169-
// Wallets from PRNG
170-
const pass = Crypto.derivePassSha(common.password, 20);
171-
const obj = {
172-
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
173-
iv: convert.hexToUint8(walletAccount.iv),
174-
key: convert.hexToUint8(pass.priv),
175-
};
176-
const d = Crypto.decrypt(obj);
177-
r = { priv: d };
178-
} else if (algo === WalletAlgorithm.Pass_enc) {
179-
// Private Key wallets
180-
const pass = Crypto.derivePassSha(common.password, 20);
181-
const obj = {
182-
ciphertext: CryptoJS.enc.Hex.parse(walletAccount.encrypted),
183-
iv: convert.hexToUint8(walletAccount.iv),
184-
key: convert.hexToUint8(pass.priv),
185-
};
186-
const d = Crypto.decrypt(obj);
187-
r = { priv: d };
188-
} else if (algo === WalletAlgorithm.Trezor) {
189-
// HW wallet
190-
r = { priv: '' };
191-
common.isHW = true;
192-
} else {
193-
return false;
194-
}
195-
// Result
196-
common.privateKey = r.priv;
197-
return true;
198-
};
64+
// generate password based key
65+
const key = CryptoJS.PBKDF2(password, salt, {
66+
keySize: 8,
67+
iterations: 1024,
68+
});
19969

200-
/**
201-
* Generate a random key
202-
*
203-
* @return {Uint8Array} - A random key
204-
*/
205-
public static randomKey = (): Uint8Array => {
206-
return Crypto.randomBytes(32);
207-
};
70+
// decrypt using custom IV
71+
const decrypted = CryptoJS.AES.decrypt(encrypted, key, {
72+
iv: iv,
73+
padding: CryptoJS.pad.Pkcs7,
74+
mode: CryptoJS.mode.CBC,
75+
});
20876

209-
/**
210-
* Encode a private key using a password
211-
*
212-
* @param {string} privateKey - An hex private key
213-
* @param {string} password - A password
214-
*
215-
* @return {object} - The encoded data
216-
*/
217-
public static encodePrivateKey = (privateKey: string, password: string): any => {
218-
// Errors
219-
if (!privateKey || !password) {
220-
throw new Error('Missing argument !');
221-
}
222-
// Processing
223-
const pass = Crypto.derivePassSha(password, 20);
224-
const r = Crypto.encrypt(privateKey, convert.hexToUint8(pass.priv));
225-
// Result
226-
return {
227-
ciphertext: CryptoJS.enc.Hex.stringify(r.ciphertext),
228-
iv: convert.uint8ToHex(r.iv),
229-
};
230-
};
77+
return decrypted.toString(CryptoJS.enc.Utf8);
78+
}
23179

23280
/***
23381
* Encode a message, separated from encode() to help testing

src/core/crypto/KeyPair.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 NEM
2+
* Copyright 2020 NEM
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,16 +23,18 @@ export class KeyPair {
2323
* @param {string} privateKeyString A hex encoded private key string.
2424
* @returns {module:crypto/keyPair~KeyPair} The key pair.
2525
*/
26-
public static createKeyPairFromPrivateKeyString(privateKeyString: string): any {
26+
public static createKeyPairFromPrivateKeyString(
27+
privateKeyString: string,
28+
): {
29+
privateKey: Uint8Array;
30+
publicKey: Uint8Array;
31+
} {
2732
const privateKey = convert.hexToUint8(privateKeyString);
2833
if (Utility.Key_Size !== privateKey.length) {
2934
throw Error(`private key has unexpected size: ${privateKey.length}`);
3035
}
31-
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
32-
return {
33-
privateKey,
34-
publicKey: keyPair.publicKey,
35-
};
36+
const { publicKey } = nacl.sign.keyPair.fromSeed(privateKey);
37+
return { privateKey, publicKey };
3638
}
3739

3840
/**

src/core/crypto/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/*
2-
import { SHA3Hasher } from './SHA3Hasher';
3-
* Copyright 2019 NEM
2+
* Copyright 2020 NEM
43
*
54
* Licensed under the Apache License, Version 2.0 (the "License");
65
* you may not use this file except in compliance with the License.

0 commit comments

Comments
 (0)