Skip to content

Commit

Permalink
Initial work on ECIES for Bitcore 0.8.
Browse files Browse the repository at this point in the history
  • Loading branch information
martindale committed Dec 17, 2014
1 parent 66c427f commit 4409492
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 0 deletions.
1 change: 1 addition & 0 deletions .coveralls.yml
@@ -0,0 +1 @@
service_name: travis-ci
4 changes: 4 additions & 0 deletions .travis.yml
@@ -0,0 +1,4 @@
language: node_js
node_js:
- '0.10'
after_script: istanbul cover ./node_modules/mocha/bin/_mocha --report lcovonly -- -R spec && cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
1 change: 1 addition & 0 deletions index.js
@@ -0,0 +1 @@
module.exports = require('./lib/ECIES');
47 changes: 47 additions & 0 deletions lib/AES.js
@@ -0,0 +1,47 @@
var aes = require('aes');

var AES = function AES() {
};

AES.encrypt = function(messagebuf, keybuf) {
var key = AES.buf2words(keybuf);
var message = AES.buf2words(messagebuf);
var a = new aes(key);
var enc = a.encrypt(message);
var encbuf = AES.words2buf(enc);
return encbuf;
};

AES.decrypt = function(encbuf, keybuf) {
var enc = AES.buf2words(encbuf);
var key = AES.buf2words(keybuf);
var a = new aes(key);
var message = a.decrypt(enc);
var messagebuf = AES.words2buf(message);
return messagebuf;
};

AES.buf2words = function(buf) {
if (buf.length % 4)
throw new Error('buf length must be a multiple of 4');

var words = [];

for (var i = 0; i < buf.length / 4; i++) {
words.push(buf.readUInt32BE(i * 4));
};

return words;
};

AES.words2buf = function(words) {
var buf = new Buffer(words.length * 4);

for (var i = 0; i < words.length; i++) {
buf.writeUInt32BE(words[i], i * 4);
};

return buf;
};

module.exports = AES;
35 changes: 35 additions & 0 deletions lib/AESCBC.js
@@ -0,0 +1,35 @@
var bitcore = require('bitcore');

var AES = require('./AES');
var CBC = require('./CBC');
var Random = bitcore.crypto.Random;
var Hash = bitcore.crypto.Hash;

// Symmetric encryption with AES and CBC convenience class
var AESCBC = function AESCBC() {};

AESCBC.encrypt = function(messagebuf, passwordstr) {
var cipherkeybuf = Hash.sha256(new Buffer(passwordstr));
return AESCBC.encryptCipherkey(messagebuf, cipherkeybuf);
};

AESCBC.decrypt = function(encbuf, passwordstr) {
var cipherkeybuf = Hash.sha256(new Buffer(passwordstr));
return AESCBC.decryptCipherkey(encbuf, cipherkeybuf);
};

AESCBC.encryptCipherkey = function(messagebuf, cipherkeybuf, ivbuf) {
ivbuf = ivbuf || Random.getRandomBuffer(128 / 8);
var ctbuf = CBC.encrypt(messagebuf, ivbuf, AES, cipherkeybuf);
var encbuf = Buffer.concat([ivbuf, ctbuf]);
return encbuf;
};

AESCBC.decryptCipherkey = function(encbuf, cipherkeybuf) {
var ivbuf = encbuf.slice(0, 128 / 8);
var ctbuf = encbuf.slice(128 / 8);
var messagebuf = CBC.decrypt(ctbuf, ivbuf, AES, cipherkeybuf);
return messagebuf;
};

module.exports = AESCBC;
138 changes: 138 additions & 0 deletions lib/CBC.js
@@ -0,0 +1,138 @@
var Random = require('bitcore').crypto.Random;

// Cipher Block Chaining
// http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher-block_chaining_.28CBC.29
var CBC = function CBC(blockcipher, cipherkeybuf, ivbuf) {
if (!(this instanceof CBC))
return new CBC(blockcipher, cipherkeybuf, ivbuf);

this.blockcipher = blockcipher;
this.cipherkeybuf = cipherkeybuf;
this.ivbuf = ivbuf;
};

CBC.buf2blockbufs = function(buf, blocksize) {
var bytesize = blocksize / 8;
var blockbufs = [];

for (var i = 0; i <= buf.length / bytesize; i++) {
var blockbuf = buf.slice(i * bytesize, i * bytesize + bytesize);

if (blockbuf.length < blocksize)
blockbuf = CBC.pkcs7pad(blockbuf, blocksize);

blockbufs.push(blockbuf);
}

return blockbufs;
};

CBC.blockbufs2buf = function(blockbufs, blocksize) {
var bytesize = blocksize / 8;

var last = blockbufs[blockbufs.length - 1];
last = CBC.pkcs7unpad(last);
blockbufs[blockbufs.length - 1] = last;

var buf = Buffer.concat(blockbufs);

return buf;
};

CBC.encrypt = function(messagebuf, ivbuf, blockcipher, cipherkeybuf) {
var blocksize = ivbuf.length * 8;
var blockbufs = CBC.buf2blockbufs(messagebuf, blocksize);
var encbufs = CBC.encryptblocks(blockbufs, ivbuf, blockcipher, cipherkeybuf);
var encbuf = Buffer.concat(encbufs);
return encbuf;
};

CBC.decrypt = function(encbuf, ivbuf, blockcipher, cipherkeybuf) {
var blocksize = ivbuf.length * 8;
var bytesize = ivbuf.length;
var encbufs = [];
for (var i = 0; i < encbuf.length / bytesize; i++) {
encbufs.push(encbuf.slice(i * bytesize, i * bytesize + bytesize));
}
var blockbufs = CBC.decryptblocks(encbufs, ivbuf, blockcipher, cipherkeybuf);
var buf = CBC.blockbufs2buf(blockbufs, blocksize);
return buf;
};

CBC.encryptblock = function(blockbuf, ivbuf, blockcipher, cipherkeybuf) {
var xorbuf = CBC.xorbufs(blockbuf, ivbuf);
var encbuf = blockcipher.encrypt(xorbuf, cipherkeybuf);
return encbuf;
};

CBC.decryptblock = function(encbuf, ivbuf, blockcipher, cipherkeybuf) {
var xorbuf = blockcipher.decrypt(encbuf, cipherkeybuf);
var blockbuf = CBC.xorbufs(xorbuf, ivbuf);
return blockbuf;
};

CBC.encryptblocks = function(blockbufs, ivbuf, blockcipher, cipherkeybuf) {
var encbufs = [];

for (var i = 0; i < blockbufs.length; i++) {
var blockbuf = blockbufs[i];
var encbuf = CBC.encryptblock(blockbuf, ivbuf, blockcipher, cipherkeybuf);

encbufs.push(encbuf);

ivbuf = encbuf;
}

return encbufs;
};

CBC.decryptblocks = function(encbufs, ivbuf, blockcipher, cipherkeybuf) {
var blockbufs = [];

for (var i = 0; i < encbufs.length; i++) {
var encbuf = encbufs[i];
var blockbuf = CBC.decryptblock(encbuf, ivbuf, blockcipher, cipherkeybuf);

blockbufs.push(blockbuf);

ivbuf = encbuf;
}

return blockbufs;
};

CBC.pkcs7pad = function(buf, blocksize) {
var bytesize = blocksize / 8;
var padbytesize = bytesize - buf.length;
var pad = new Buffer(padbytesize);
pad.fill(padbytesize);
var paddedbuf = Buffer.concat([buf, pad]);
return paddedbuf;
};

CBC.pkcs7unpad = function(paddedbuf, blocksize) {
var bytesize = blocksize / 8;
var padbytesize = bytesize - paddedbuf.length;
var padlength = paddedbuf[paddedbuf.length - 1];
var padbuf = paddedbuf.slice(paddedbuf.length - padlength, paddedbuf.length);
var padbuf2 = new Buffer(padlength);
padbuf2.fill(padlength);
if (padbuf.toString('hex') !== padbuf2.toString('hex'))
throw new Error('invalid padding');
return paddedbuf.slice(0, paddedbuf.length - padlength);
};

CBC.xorbufs = function(buf1, buf2) {
if (buf1.length !== buf2.length)
throw new Error('bufs must have the same length');

var buf = new Buffer(buf1.length);

for (var i = 0; i < buf1.length; i++) {
buf[i] = buf1[i] ^ buf2[i];
}

return buf;
};

module.exports = CBC;
78 changes: 78 additions & 0 deletions lib/ECIES.js
@@ -0,0 +1,78 @@
var bitcore = require('bitcore');

var Point = bitcore.crypto.Point;
var Hash = bitcore.crypto.Hash;
var Random = bitcore.crypto.Random;

var PublicKey = bitcore.PublicKey;
var PrivateKey = bitcore.PrivateKey;

var AESCBC = require('./AESCBC');

// http://en.wikipedia.org/wiki/Integrated_Encryption_Scheme
var ECIES = function ECIES() {
if (!(this instanceof ECIES)) return new ECIES();
};

ECIES.prototype.privateKey = function( privateKey ) {
if (!privateKey) throw new Error('no private key provided');

this._privateKey = privateKey || null;

return this;
};

ECIES.prototype.publicKey = function( publicKey ) {
if (!publicKey) throw new Error('no public key provided');

this._publicKey = publicKey || null;

return this;
};

ECIES.prototype.encrypt = function( message ) {
if (!Buffer.isBuffer( message )) message = new Buffer( message );
var ivbuf = Random.getRandomBuffer( 128 / 8 );

var r = this._privateKey.bn;
var R = this._privateKey.publicKey.point;
var Rpubkey = this._privateKey.publicKey;
var Rbuf = Rpubkey.toDER( true );
var KB = this._publicKey.point;
var P = KB.mul( r );
var S = P.getX();
var Sbuf = S.toBuffer({ size: 32 });
var kEkM = Hash.sha512(Sbuf);
var kE = kEkM.slice(0, 32);
var kM = kEkM.slice(32, 64);
var c = AESCBC.encryptCipherkey(message, kE, ivbuf);
var d = Hash.sha256hmac(c, kM);
var encbuf = Buffer.concat([Rbuf, c, d]);

return encbuf;
};

ECIES.prototype.decrypt = function( encbuf ) {
var kB = this._privateKey.bn;
var frompubkey = this._publicKey;
var R = frompubkey.point;
var P = R.mul(kB);
var S = P.getX();

var Sbuf = S.toBuffer({ size: 32 });
var kEkM = Hash.sha512(Sbuf);

var kE = kEkM.slice(0, 32);
var kM = kEkM.slice(32, 64);

var c = encbuf.slice(33, encbuf.length - 32);
var d = encbuf.slice(encbuf.length - 32, encbuf.length);

var d2 = Hash.sha256hmac(c, kM);
if (d.toString('hex') !== d2.toString('hex')) throw new Error('Invalid checksum');
var messagebuf = AESCBC.decryptCipherkey(c, kE);

return messagebuf;
};

module.exports = ECIES;
41 changes: 41 additions & 0 deletions package.json
@@ -0,0 +1,41 @@
{
"name": "bitcore-ecies",
"version": "0.0.1",
"description": "ECIES implemented for Bitcore.",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "mocha",
"coverage": "istanbul cover _mocha"
},
"repository": {
"type": "git",
"url": "https://github.com/bitpay/bitcore-ecies.git"
},
"keywords": [
"bitcoin",
"bitcore",
"ecies",
"crypto",
"cryptography",
"encryption"
],
"author": "BitPay",
"license": "MIT",
"bugs": {
"url": "https://github.com/bitpay/bitcore-ecies/issues"
},
"homepage": "https://github.com/bitpay/bitcore-ecies",
"devDependencies": {
"assert": "^1.1.2",
"coveralls": "^2.11.2",
"istanbul": "^0.3.5",
"mocha": "^2.0.1"
},
"dependencies": {
"aes": "^0.1.0",
"bitcore": "git://github.com/bitpay/bitcore#v0.8"
}
}

0 comments on commit 4409492

Please sign in to comment.