From dfc129b7667cc4e8ce67334d42c1a9e3d95dee22 Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Wed, 9 Jul 2014 01:05:16 -0700 Subject: [PATCH 1/2] test vector that passes in node, but fails in browser/sjcl There is some kind of problem either in bitcore or sjcl involving the decodeURIComponent function. I discovered this issue while working on the network protocol for Copay. Decrypting binary data in sjcl produces problems due to the way sjcl is interpreting data as strings. I will have to investigate further tomorrow. For now I am producing this test vector to demonstrate the issue. --- test/test.ECIES.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test.ECIES.js b/test/test.ECIES.js index 88138e1..d19fb9b 100644 --- a/test/test.ECIES.js +++ b/test/test.ECIES.js @@ -75,6 +75,20 @@ describe('ECIES', function() { decrypted.toString().should.equal('this is my message'); }); + it('should decrypt this known problematic encrypted message', function() { + var privhex = 'e0224327f5e4a9daea6c7b996cb013775f90821d15d7d0d25db517c7cd0c1a8e'; + var key = new bitcore.Key(); + key.private = new Buffer(privhex, 'hex'); + key.regenerateSync(); + + var encryptedhex = '02f773c550bf228327f773b1dc63802055ba7333ee4ea86323e1a77365f14fede041dbe628dc636c5eebb572578e79184a96eee82db57b456328ca080a9e8b0b856474119f65b942b088ce09dcfb8536632d57343d533e9b55c8f17cc52466a6dfada1848923782e99e8f2210cfd6a04510ea0a482f38e43a88b018b6e9cc27511df873f7aea04fd342a42f651481f42f91a7a672ef9d56080d072417ca6cb1a2771b6838f08ab49470d84fa67f85886382b503ab86fefd02195e49c0f8516884a3adc62bf176c5ff1665bafe1c9af59f6857531e86c2a650bebdbc60970f6b1ce'; + var encrypted = new Buffer(encryptedhex, 'hex'); + + var decrypted = bitcore.ECIES.decrypt(key.private, encrypted); + decrypted.slice(10).toString().should.equal('{"type":"hello","copayerId":"024c0ec590ba86bbaf7beb9823c6610d02eacb9c3345bc678c09cc266590681af0"}'); + + }); + it('should not fail for long messages', function() { var key = new bitcore.Key(); key.private = bitcore.util.sha256('test'); From 65ab3a663a54ac8cad82b235e227ab9bc9674f6a Mon Sep 17 00:00:00 2001 From: "Ryan X. Charles" Date: Wed, 9 Jul 2014 16:25:48 -0700 Subject: [PATCH 2/2] fix string/buffer sjcl issue ...by using sjcl.mode.cbc.encrypt/decrypt rather than sjcl.encrypt/decrypt. The difference is that the sjcl.encrypt/decrypt functions are really convenience methods designed to encrypt and decrypt strings, but don't play nice with binary data, as revealed in the tests in this commit and the previous commit. Basically, if you use them to encrypt and decrypt binary data as a string, it will return the wrong result or an error. The solution is to use the block cipher directly, in this case sjcl.mode.cbc. This also has the advantage of fewer format conversions - no converting to base64 and JSON strings. This makes things faster. Also, it is actually correct unlike the previous method. --- lib/browser/ECIES.js | 36 ++++++++---------------------------- test/test.ECIES.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 28 deletions(-) diff --git a/lib/browser/ECIES.js b/lib/browser/ECIES.js index 681601e..ca89fa3 100644 --- a/lib/browser/ECIES.js +++ b/lib/browser/ECIES.js @@ -9,18 +9,10 @@ ECIES.symmetricEncrypt = function(key, iv, message) { var smessage = sjcl.codec.hex.toBits(message.toString('hex')); sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."](); - var params = { - iv: siv, - ks: 256, - ts: 128, - iter: 1000, - mode: 'cbc' - }; - var encrypted = sjcl.encrypt(skey, smessage, params); - var enchex = sjcl.codec.hex.fromBits(sjcl.codec.base64.toBits(JSON.parse(encrypted).ct)); - - var encbuf = new Buffer(enchex, 'hex'); + var cipher = new sjcl.cipher.aes(skey); + var encrypted = sjcl.mode.cbc.encrypt(cipher, smessage, siv); + var encbuf = new Buffer(sjcl.codec.hex.fromBits(encrypted), 'hex'); var r = Buffer.concat([iv, encbuf]); return r; @@ -31,25 +23,13 @@ ECIES.symmetricDecrypt = function(key, encrypted) { var iv = encrypted.slice(0, 16); var todecrypt = encrypted.slice(16, encrypted.length); - var siv = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(iv.toString('hex'))); - var sct = sjcl.codec.base64.fromBits(sjcl.codec.hex.toBits(todecrypt.toString('hex'))); - sjcl.beware["CBC mode is dangerous because it doesn't protect message integrity."](); - var obj = { - iv: siv, - v: 1, - iter: 1000, - ks: 256, - ts: 128, - mode: 'cbc', - adata: '', - cipher: 'aes', - ct: sct - }; - var str = JSON.stringify(obj); - var decrypted = sjcl.decrypt(skey, str); - var decbuf = new Buffer(decrypted); + var encbits = sjcl.codec.hex.toBits(todecrypt.toString('hex')); + var ivbits = sjcl.codec.hex.toBits(iv.toString('hex')); + var cipher = new sjcl.cipher.aes(skey); + var decrypted = sjcl.mode.cbc.decrypt(cipher, encbits, ivbits); + var decbuf = new Buffer(sjcl.codec.hex.fromBits(decrypted), 'hex'); return decbuf; }; diff --git a/test/test.ECIES.js b/test/test.ECIES.js index d19fb9b..22a1727 100644 --- a/test/test.ECIES.js +++ b/test/test.ECIES.js @@ -75,6 +75,48 @@ describe('ECIES', function() { decrypted.toString().should.equal('this is my message'); }); + it('should encrypt and decrypt 0x80 correctly, the first bad byte', function() { + var privhex = 'e0224327f5e4a9daea6c7b996cb013775f90821d15d7d0d25db517c7cd0c1a8e'; + var key = new bitcore.Key(); + key.private = new Buffer(privhex, 'hex'); + key.regenerateSync(); + + var data = new Buffer([0x80]); + var encrypted = bitcore.ECIES.encrypt(key.public, data); + var decrypted = bitcore.ECIES.decrypt(key.private, encrypted); + decrypted.toString('hex').should.equal('80'); + decrypted.toString('hex').should.not.equal('c280'); + }); + + it('should encrypt and decrypt this known problematic encrypted message', function() { + var privhex = 'e0224327f5e4a9daea6c7b996cb013775f90821d15d7d0d25db517c7cd0c1a8e'; + var key = new bitcore.Key(); + key.private = new Buffer(privhex, 'hex'); + key.regenerateSync(); + + var data = new Buffer('010053bdae9b000000017b2274797065223a2268656c6c6f222c22636f70617965724964223a22303237323735366234366561386564313763376166613934303861306161333535616266326432623263353134373637343766353135326332623535653163656230227d', 'hex'); + var data = new Buffer('53bdae00', 'hex'); + + var encrypted = bitcore.ECIES.encrypt(key.public, data); + var decrypted = bitcore.ECIES.decrypt(key.private, encrypted); + decrypted.toString('hex').should.not.equal('53c2bdc2ae00'); + decrypted.toString('hex').should.equal('53bdae00'); + + }); + + it('should encrypt and decrypt this known problematic encrypted message', function() { + var privhex = 'e0224327f5e4a9daea6c7b996cb013775f90821d15d7d0d25db517c7cd0c1a8e'; + var key = new bitcore.Key(); + key.private = new Buffer(privhex, 'hex'); + key.regenerateSync(); + + var data = new Buffer('010053bdae9b000000017b2274797065223a2268656c6c6f222c22636f70617965724964223a22303237323735366234366561386564313763376166613934303861306161333535616266326432623263353134373637343766353135326332623535653163656230227d', 'hex'); + var encrypted = bitcore.ECIES.encrypt(key.public, data); + var decrypted = bitcore.ECIES.decrypt(key.private, encrypted); + decrypted.toString('hex').should.equal('010053bdae9b000000017b2274797065223a2268656c6c6f222c22636f70617965724964223a22303237323735366234366561386564313763376166613934303861306161333535616266326432623263353134373637343766353135326332623535653163656230227d'); + + }); + it('should decrypt this known problematic encrypted message', function() { var privhex = 'e0224327f5e4a9daea6c7b996cb013775f90821d15d7d0d25db517c7cd0c1a8e'; var key = new bitcore.Key();