var Random = require('../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;