Added support for PBKD2 encrypted wallets (which Parity uses).

This commit is contained in:
ricmoo 2017-05-28 15:46:57 -05:00
parent 70f10cd23e
commit 91f9a47afa
5 changed files with 125 additions and 69 deletions

@ -9,7 +9,7 @@ function prefixAddress(address) {
if (address.substring(0, 2) !== '0x') { if (address.substring(0, 2) !== '0x') {
address = '0x' + address; address = '0x' + address;
} }
return address; return address.toLowerCase();
} }
@ -25,6 +25,15 @@ Output.push({
}); });
*/ */
var privateKeys = {
'0x0b88d4b324ec24c8c078551e6e5075547157e5b6': '0xd4375d2a931db84ea8825b69a3128913597744d9236cacec675cc18e1bda4446',
'0x2e326fa404fc3661de4f4361776ed9bbabdc26e3': '0xcf367fc32bf789b3339c6664af4a12263e9db0e0eb70f247da1d1165e150c487',
'0x00a329c0648769a73afac7f9381e08fb43dbea72': '0x4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7',
'0x4a9cf99357f5789251a8d7fad5b86d0f31eeb938': '0xa016182717223d01f776149ec0b4a217d0e9930cad263f205427c6d3cd5560e7',
'0x88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290': '0xf03e581353c794928373fb0893bc731aefc4c4e234e643f3a46998b03cd4d7c5',
'0x17c5185167401ed00cf5f5b2fc97d9bbfdb7d025': '0x4242424242424242424242424242424242424242424242424242424242424242',
}
var walletPath = path.join(__dirname, 'test-wallets'); var walletPath = path.join(__dirname, 'test-wallets');
fs.readdirSync(walletPath).forEach(function(filename) { fs.readdirSync(walletPath).forEach(function(filename) {
var data = require(path.join(walletPath, filename)); var data = require(path.join(walletPath, filename));
@ -33,7 +42,7 @@ fs.readdirSync(walletPath).forEach(function(filename) {
var password = filename.substring(0, filename.length - 5).split('-'); var password = filename.substring(0, filename.length - 5).split('-');
password = password[password.length - 1]; password = password[password.length - 1];
if (password === 'life') { password = 'foobar42'; } if (password === 'life') { password = 'foo'; }
if (data.ethaddr) { if (data.ethaddr) {
Output.push({ Output.push({
@ -41,7 +50,7 @@ fs.readdirSync(walletPath).forEach(function(filename) {
address: prefixAddress(data.ethaddr), address: prefixAddress(data.ethaddr),
json: JSON.stringify(data), json: JSON.stringify(data),
password: password, password: password,
privateKey: '', privateKey: privateKeys[prefixAddress(data.ethaddr)],
}); });
} else { } else {
@ -50,7 +59,7 @@ fs.readdirSync(walletPath).forEach(function(filename) {
address: prefixAddress(data.address), address: prefixAddress(data.address),
json: JSON.stringify(data), json: JSON.stringify(data),
password: password, password: password,
privateKey: '', privateKey: privateKeys[prefixAddress(data.address)],
}); });
} }
}); });

@ -0,0 +1 @@
{"id":"a9b7570b-fd36-8a32-f4ea-26080a941143","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"1de99c07c6592eaac911ae9cf07db88d"},"ciphertext":"951ca34a268314c0cde0409566bcac4d4907dc87f34764fe8542786415cb5726","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"7d2ebe49878f15e031bcdf9f0be47e7d49542252145850b7862a08a0c209a0e9"},"mac":"dc595787a3d08d98e2d808a981b8e9566d8ed1087c0257481ffc1196e13d8c0a"},"address":"00a329c0648769a73afac7f9381e08fb43dbea72","name":"Blank","meta":"{\"passwordHint\":\"\",\"timestamp\":1495227675346}"}

@ -2,7 +2,7 @@
var fs = require('fs'); var fs = require('fs');
var path = require('path'); var path = require('path');
var utils = require('ethers-utils'); var utils = require('../../utils');
function randomBytes(seed, lower, upper) { function randomBytes(seed, lower, upper) {
if (!upper) { upper = lower; } if (!upper) { upper = lower; }

3
tests/tests/keep.txt Normal file

@ -0,0 +1,3 @@
This file just ensures this directory exists.
Run the files in make-tests to create the individual test cases.

@ -4,6 +4,8 @@ var aes = require('aes-js');
var scrypt = require('scrypt-js'); var scrypt = require('scrypt-js');
var uuid = require('uuid'); var uuid = require('uuid');
var hmac = require('ethers-utils/hmac');
var pbkdf2 = require('ethers-utils/pbkdf2');
var utils = require('ethers-utils'); var utils = require('ethers-utils');
var SigningKey = require('./signing-key.js'); var SigningKey = require('./signing-key.js');
@ -69,6 +71,7 @@ utils.defineProperty(secretStorage, 'isValidWallet', function(json) {
if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) { if (!data.version || parseInt(data.version) !== data.version || parseInt(data.version) !== 3) {
return false; return false;
} }
// @TODO: Put more checks to make sure it has kdf, iv and all that good stuff // @TODO: Put more checks to make sure it has kdf, iv and all that good stuff
return true; return true;
}); });
@ -89,7 +92,7 @@ utils.defineProperty(secretStorage, 'decryptCrowdsale', function(json, password)
throw new Error('invalid encseed'); throw new Error('invalid encseed');
} }
var key = utils.pbkdf2(password, password, 2000, 32, utils.hmac.createSha256Hmac).slice(0, 16); var key = pbkdf2(password, password, 2000, 32, hmac.createSha256Hmac).slice(0, 16);
var iv = encseed.slice(0, 16); var iv = encseed.slice(0, 16);
var encryptedSeed = encseed.slice(16); var encryptedSeed = encseed.slice(16);
@ -140,9 +143,36 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
return utils.keccak256(utils.concat([derivedHalf, ciphertext])); return utils.keccak256(utils.concat([derivedHalf, ciphertext]));
} }
var getSigningKey = function(key) {
var ciphertext = arrayify(searchPath(data, 'crypto/ciphertext'));
var computedMAC = utils.hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2);
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
reject(new Error('invalid password'));
return null;
}
var privateKey = decrypt(key.slice(0, 16), ciphertext);
if (!privateKey) {
reject(new Error('unsupported cipher'));
return null;
}
var signingKey = new SigningKey(privateKey);
if (signingKey.address !== utils.getAddress(data.address)) {
reject(new Error('address mismatch'));
return null;
}
return signingKey;
}
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var kdf = searchPath(data, 'crypto/kdf'); var kdf = searchPath(data, 'crypto/kdf');
if (kdf && typeof(kdf) === 'string' && kdf.toLowerCase() === 'scrypt') { if (kdf && typeof(kdf) === 'string') {
if (kdf.toLowerCase() === 'scrypt') {
var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt'); var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt');
var N = parseInt(searchPath(data, 'crypto/kdfparams/n')); var N = parseInt(searchPath(data, 'crypto/kdfparams/n'));
var r = parseInt(searchPath(data, 'crypto/kdfparams/r')); var r = parseInt(searchPath(data, 'crypto/kdfparams/r'));
@ -172,26 +202,8 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
} else if (key) { } else if (key) {
key = arrayify(key); key = arrayify(key);
var ciphertext = arrayify(searchPath(data, 'crypto/ciphertext')); var signingKey = getSigningKey(key);
if (!signingKey) { return; }
var computedMAC = utils.hexlify(computeMAC(key.slice(16, 32), ciphertext)).substring(2);
if (computedMAC !== searchPath(data, 'crypto/mac').toLowerCase()) {
reject(new Error('invalid password'));
return;
}
var privateKey = decrypt(key.slice(0, 16), ciphertext);
if (!privateKey) {
reject(new Error('unsupported cipher'));
return;
}
var signingKey = new SigningKey(privateKey);
if (signingKey.address !== utils.getAddress(data.address)) {
reject(new Error('address mismatch'));
return;
}
if (progressCallback) { progressCallback(1); } if (progressCallback) { progressCallback(1); }
resolve(signingKey); resolve(signingKey);
@ -201,11 +213,42 @@ utils.defineProperty(secretStorage, 'decrypt', function(json, password, progress
} }
}); });
} else if (kdf.toLowerCase() === 'pbkdf2') {
var salt = arrayify(searchPath(data, 'crypto/kdfparams/salt'), 'crypto/kdfparams/salt');
var prfFunc = null;
var prf = searchPath(data, 'crypto/kdfparams/prf');
if (prf === 'hmac-sha256') {
prfFunc = hmac.createSha256Hmac;
} else if (prf === 'hmac-sha512') {
prfFunc = hmac.createSha512Hmac;
} else {
reject(new Error('unsupported prf'));
return;
}
var c = parseInt(searchPath(data, 'crypto/kdfparams/c'));
var dkLen = parseInt(searchPath(data, 'crypto/kdfparams/dklen'));
if (dkLen !== 32) {
reject( new Error('unsupported key-derivation derived-key length'));
return;
}
var key = pbkdf2(password, salt, c, dkLen, prfFunc);
var signingKey = getSigningKey(key);
if (!signingKey) { return; }
resolve(signingKey);
} else { } else {
// @TOOD: Support pbkdf2 kdf
reject(new Error('unsupported key-derivation function')); reject(new Error('unsupported key-derivation function'));
} }
} else {
reject(new Error('unsupported key-derivation function'));
}
}); });
}); });