| 'use strict'; |
| |
| var assert = require('minimalistic-assert'); |
| |
| function Cipher(options) { |
| this.options = options; |
| |
| this.type = this.options.type; |
| this.blockSize = 8; |
| this._init(); |
| |
| this.buffer = new Array(this.blockSize); |
| this.bufferOff = 0; |
| } |
| module.exports = Cipher; |
| |
| Cipher.prototype._init = function _init() { |
| // Might be overrided |
| }; |
| |
| Cipher.prototype.update = function update(data) { |
| if (data.length === 0) |
| return []; |
| |
| if (this.type === 'decrypt') |
| return this._updateDecrypt(data); |
| else |
| return this._updateEncrypt(data); |
| }; |
| |
| Cipher.prototype._buffer = function _buffer(data, off) { |
| // Append data to buffer |
| var min = Math.min(this.buffer.length - this.bufferOff, data.length - off); |
| for (var i = 0; i < min; i++) |
| this.buffer[this.bufferOff + i] = data[off + i]; |
| this.bufferOff += min; |
| |
| // Shift next |
| return min; |
| }; |
| |
| Cipher.prototype._flushBuffer = function _flushBuffer(out, off) { |
| this._update(this.buffer, 0, out, off); |
| this.bufferOff = 0; |
| return this.blockSize; |
| }; |
| |
| Cipher.prototype._updateEncrypt = function _updateEncrypt(data) { |
| var inputOff = 0; |
| var outputOff = 0; |
| |
| var count = ((this.bufferOff + data.length) / this.blockSize) | 0; |
| var out = new Array(count * this.blockSize); |
| |
| if (this.bufferOff !== 0) { |
| inputOff += this._buffer(data, inputOff); |
| |
| if (this.bufferOff === this.buffer.length) |
| outputOff += this._flushBuffer(out, outputOff); |
| } |
| |
| // Write blocks |
| var max = data.length - ((data.length - inputOff) % this.blockSize); |
| for (; inputOff < max; inputOff += this.blockSize) { |
| this._update(data, inputOff, out, outputOff); |
| outputOff += this.blockSize; |
| } |
| |
| // Queue rest |
| for (; inputOff < data.length; inputOff++, this.bufferOff++) |
| this.buffer[this.bufferOff] = data[inputOff]; |
| |
| return out; |
| }; |
| |
| Cipher.prototype._updateDecrypt = function _updateDecrypt(data) { |
| var inputOff = 0; |
| var outputOff = 0; |
| |
| var count = Math.ceil((this.bufferOff + data.length) / this.blockSize) - 1; |
| var out = new Array(count * this.blockSize); |
| |
| // TODO(indutny): optimize it, this is far from optimal |
| for (; count > 0; count--) { |
| inputOff += this._buffer(data, inputOff); |
| outputOff += this._flushBuffer(out, outputOff); |
| } |
| |
| // Buffer rest of the input |
| inputOff += this._buffer(data, inputOff); |
| |
| return out; |
| }; |
| |
| Cipher.prototype.final = function final(buffer) { |
| var first; |
| if (buffer) |
| first = this.update(buffer); |
| |
| var last; |
| if (this.type === 'encrypt') |
| last = this._finalEncrypt(); |
| else |
| last = this._finalDecrypt(); |
| |
| if (first) |
| return first.concat(last); |
| else |
| return last; |
| }; |
| |
| Cipher.prototype._pad = function _pad(buffer, off) { |
| if (off === 0) |
| return false; |
| |
| while (off < buffer.length) |
| buffer[off++] = 0; |
| |
| return true; |
| }; |
| |
| Cipher.prototype._finalEncrypt = function _finalEncrypt() { |
| if (!this._pad(this.buffer, this.bufferOff)) |
| return []; |
| |
| var out = new Array(this.blockSize); |
| this._update(this.buffer, 0, out, 0); |
| return out; |
| }; |
| |
| Cipher.prototype._unpad = function _unpad(buffer) { |
| return buffer; |
| }; |
| |
| Cipher.prototype._finalDecrypt = function _finalDecrypt() { |
| assert.equal(this.bufferOff, this.blockSize, 'Not enough data to decrypt'); |
| var out = new Array(this.blockSize); |
| this._flushBuffer(out, 0); |
| |
| return this._unpad(out); |
| }; |