Skip to content

Commit e665cff

Browse files
committed
Merge branch 'fix/encryption' of github.com:gabrielbazan7/bitcore
2 parents 7c90dca + 412b719 commit e665cff

4 files changed

Lines changed: 158 additions & 11 deletions

File tree

packages/bitcore-wallet-client/src/lib/common/encryption.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
import crypto from 'crypto';
4+
import sjcl from 'sjcl';
45

56
const PBKDF2_ITERATIONS = 1000;
67
const DEFAULT_KEY_SIZE = 256; // bits
@@ -103,14 +104,30 @@ class EncryptionClass {
103104
}
104105

105106
decryptWithKey(data: string | IEncrypted, key: string | Buffer) {
106-
key = Buffer.isBuffer(key) ? key : Buffer.from(key, 'base64');
107-
return this._baseDecrypt(data, key);
107+
try {
108+
const keyBuffer = Buffer.isBuffer(key) ? key : Buffer.from(key, 'base64');
109+
return this._baseDecrypt(data, keyBuffer);
110+
} catch (err) {
111+
try {
112+
return sjcl.decrypt(key, data);
113+
} catch {
114+
throw err;
115+
}
116+
}
108117
}
109118

110119
decryptWithPassword(data: string | IEncrypted, password: string) {
111-
const json = typeof data === 'string' ? JSON.parse(data) : data;
112-
const key = crypto.pbkdf2Sync(password, Buffer.from(json.salt, 'base64'), json.iter, json.ks / 8, 'sha256');
113-
return this._baseDecrypt(json, key);
120+
try {
121+
const json = typeof data === 'string' ? JSON.parse(data) : data;
122+
const key = crypto.pbkdf2Sync(password, Buffer.from(json.salt, 'base64'), json.iter, json.ks / 8, 'sha256');
123+
return this._baseDecrypt(json, key);
124+
} catch (err) {
125+
try {
126+
return sjcl.decrypt(password, data);
127+
} catch {
128+
throw err;
129+
}
130+
}
114131
}
115132
}
116133

packages/bitcore-wallet-client/src/lib/verifier.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export class Verifier {
8585
const uniq = [];
8686
let error;
8787
for (const copayer of copayers || []) {
88-
if (uniq[copayers.xPubKey]++) {
88+
if (uniq[copayer.xPubKey]++) {
8989
log.error('Repeated public keys in server response');
9090
error = true;
9191
}

packages/bitcore-wallet-client/test/key.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22

33
import * as chai from 'chai';
4+
import sjcl from 'sjcl';
45
import { Key } from '../src/lib/key';
56

67
const should = chai.should();
@@ -572,4 +573,100 @@ describe('Key', function() {
572573
should.exist(obj.fingerPrint);
573574
});
574575
});
576+
describe('Backward compatibility with sjcl', function() {
577+
describe('#checkPassword with sjcl encrypted key', function() {
578+
it('should check password on sjcl encrypted key', function() {
579+
const k = new Key({
580+
seedType: 'mnemonic',
581+
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
582+
});
583+
const password = 'testpassword';
584+
const xPrivKey = k.get().xPrivKey;
585+
586+
const obj = k.toObj();
587+
obj.xPrivKeyEncrypted = sjcl.encrypt(password, xPrivKey);
588+
obj.xPrivKey = undefined;
589+
590+
const k2 = new Key({ seedType: 'object', seedData: obj });
591+
k2.checkPassword(password).should.equal(true);
592+
k2.checkPassword('wrongpassword').should.equal(false);
593+
});
594+
});
595+
596+
describe('#get with sjcl encrypted key', function() {
597+
it('should decrypt sjcl encrypted mnemonic and privatekey', function() {
598+
const k = new Key({
599+
seedType: 'mnemonic',
600+
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
601+
});
602+
const password = 'testpassword';
603+
const expectedMnemonic = k.get().mnemonic;
604+
const expectedXPrivKey = k.get().xPrivKey;
605+
606+
const obj = k.toObj();
607+
obj.xPrivKeyEncrypted = sjcl.encrypt(password, expectedXPrivKey);
608+
obj.mnemonicEncrypted = sjcl.encrypt(password, expectedMnemonic);
609+
obj.xPrivKey = undefined;
610+
obj.mnemonic = undefined;
611+
612+
const k2 = new Key({ seedType: 'object', seedData: obj });
613+
const decrypted = k2.get(password);
614+
decrypted.mnemonic.should.equal(expectedMnemonic);
615+
decrypted.xPrivKey.should.equal(expectedXPrivKey);
616+
});
617+
});
618+
619+
describe('#decrypt with sjcl encrypted key', function() {
620+
it('should decrypt sjcl key', function() {
621+
const k = new Key({
622+
seedType: 'mnemonic',
623+
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
624+
});
625+
const password = 'testpassword';
626+
const expectedXPrivKey = k.get().xPrivKey;
627+
const expectedMnemonic = k.get().mnemonic;
628+
629+
const obj = k.toObj();
630+
obj.xPrivKeyEncrypted = sjcl.encrypt(password, expectedXPrivKey);
631+
obj.mnemonicEncrypted = sjcl.encrypt(password, expectedMnemonic);
632+
obj.xPrivKey = undefined;
633+
obj.mnemonic = undefined;
634+
635+
const k2 = new Key({ seedType: 'object', seedData: obj });
636+
k2.isPrivKeyEncrypted().should.be.true;
637+
k2.decrypt(password);
638+
k2.isPrivKeyEncrypted().should.be.false;
639+
640+
const decrypted = k2.toObj();
641+
decrypted.xPrivKey.should.equal(expectedXPrivKey);
642+
decrypted.mnemonic.should.equal(expectedMnemonic);
643+
should.not.exist(decrypted.xPrivKeyEncrypted);
644+
should.not.exist(decrypted.mnemonicEncrypted);
645+
});
646+
});
647+
648+
describe('#addKeyFromExistingPrivateKey with sjcl', function() {
649+
it('should create new algo key from sjcl encrypted existing key', function() {
650+
const k = new Key({
651+
seedType: 'mnemonic',
652+
seedData: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'
653+
});
654+
const password = 'testpassword';
655+
const expectedXPrivKey = k.get().xPrivKey;
656+
657+
const obj = k.toObj();
658+
obj.xPrivKeyEncrypted = sjcl.encrypt(password, expectedXPrivKey);
659+
obj.xPrivKey = undefined;
660+
obj.mnemonic = undefined;
661+
obj.mnemonicEncrypted = undefined;
662+
663+
const k2 = new Key({ seedType: 'object', seedData: obj });
664+
k2.addKeyByAlgorithm('EDDSA', { password, existingAlgo: 'ECDSA' });
665+
666+
should.exist(k2.fingerPrintEDDSA);
667+
const eddsaKey = k2.get(password, 'EDDSA');
668+
should.exist(eddsaKey.xPrivKey);
669+
});
670+
});
671+
});
575672
});

packages/bitcore-wallet-client/test/utils.test.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import * as chai from 'chai';
44
import { BitcoreLib as Bitcore } from 'crypto-wallet-core';
55
import { Utils } from '../src/lib/common';
6+
import sjcl from 'sjcl';
67

78
const should = chai.should();
89

@@ -212,6 +213,14 @@ describe('Utils', () => {
212213
const msg = Utils.decryptMessage(ct, pwd);
213214
msg.should.equal('hello world');
214215
});
216+
it('should decrypt sjcl encrypted message', () => {
217+
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
218+
const message = 'hello world';
219+
const key = sjcl.codec.base64.toBits(pwd);
220+
const ct = sjcl.encrypt(key, message);
221+
const msg = Utils.decryptMessage(ct, pwd);
222+
msg.should.equal(message);
223+
});
215224
});
216225

217226

@@ -223,6 +232,16 @@ describe('Utils', () => {
223232
Utils.decryptMessage(ct, 'test');
224233
}).should.throw('Invalid key length');
225234
});
235+
it('should throw on sjcl encrypted message with wrong password', () => {
236+
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
237+
const wrongPwd = 'wrongpassword12345==';
238+
const message = 'hello world';
239+
const key = sjcl.codec.base64.toBits(pwd);
240+
const ct = sjcl.encrypt(key, message);
241+
(() => {
242+
Utils.decryptMessage(ct, wrongPwd);
243+
}).should.throw();
244+
});
226245
});
227246

228247
describe('#decryptMessageNoThrow should not throw', () => {
@@ -234,14 +253,32 @@ describe('Utils', () => {
234253
msg.should.equal('hello world');
235254
});
236255

237-
it('should encrypt and fail to decrypt', () => {
256+
it('should encrypt and fail to decrypt', () => {
238257
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
239258
const ct = Utils.encryptMessage('hello world', pwd);
240259
const msg = Utils.decryptMessageNoThrow(ct, 'hola');
241260

242261
msg.should.equal('<ECANNOTDECRYPT>');
243262
});
244263

264+
it('should decrypt sjcl encrypted message', () => {
265+
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
266+
const message = 'hello world';
267+
const key = sjcl.codec.base64.toBits(pwd);
268+
const ct = sjcl.encrypt(key, message);
269+
const msg = Utils.decryptMessageNoThrow(ct, pwd);
270+
msg.should.equal(message);
271+
});
272+
273+
it('should return error string on sjcl encrypted message with wrong password', () => {
274+
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
275+
const wrongPwd = 'wrongpassword12345==';
276+
const message = 'hello world';
277+
const key = sjcl.codec.base64.toBits(pwd);
278+
const ct = sjcl.encrypt(key, message);
279+
const msg = Utils.decryptMessageNoThrow(ct, wrongPwd);
280+
msg.should.equal('<ECANNOTDECRYPT>');
281+
});
245282

246283
it('should failover to decrypt a non-encrypted msg', () => {
247284
const pwd = 'ezDRS2NRchMJLf1IWtjL5A==';
@@ -257,19 +294,15 @@ describe('Utils', () => {
257294
msg.should.equal('{"pepe":1}');
258295
});
259296

260-
261297
it('should no try to decrypt empty', () => {
262298
const msg = Utils.decryptMessageNoThrow('', 'hola');
263299
msg.should.equal('');
264300
});
265301

266-
267302
it('should no try to decrypt null', () => {
268303
const msg = Utils.decryptMessageNoThrow(null, 'hola');
269304
msg.should.equal('');
270305
});
271-
272-
273306
});
274307

275308

0 commit comments

Comments
 (0)