| var aes = require('./aes') |
| var Buffer = require('safe-buffer').Buffer |
| var Transform = require('cipher-base') |
| var inherits = require('inherits') |
| var GHASH = require('./ghash') |
| var xor = require('buffer-xor') |
| var incr32 = require('./incr32') |
| |
| function xorTest (a, b) { |
| var out = 0 |
| if (a.length !== b.length) out++ |
| |
| var len = Math.min(a.length, b.length) |
| for (var i = 0; i < len; ++i) { |
| out += (a[i] ^ b[i]) |
| } |
| |
| return out |
| } |
| |
| function calcIv (self, iv, ck) { |
| if (iv.length === 12) { |
| self._finID = Buffer.concat([iv, Buffer.from([0, 0, 0, 1])]) |
| return Buffer.concat([iv, Buffer.from([0, 0, 0, 2])]) |
| } |
| var ghash = new GHASH(ck) |
| var len = iv.length |
| var toPad = len % 16 |
| ghash.update(iv) |
| if (toPad) { |
| toPad = 16 - toPad |
| ghash.update(Buffer.alloc(toPad, 0)) |
| } |
| ghash.update(Buffer.alloc(8, 0)) |
| var ivBits = len * 8 |
| var tail = Buffer.alloc(8) |
| tail.writeUIntBE(ivBits, 0, 8) |
| ghash.update(tail) |
| self._finID = ghash.state |
| var out = Buffer.from(self._finID) |
| incr32(out) |
| return out |
| } |
| function StreamCipher (mode, key, iv, decrypt) { |
| Transform.call(this) |
| |
| var h = Buffer.alloc(4, 0) |
| |
| this._cipher = new aes.AES(key) |
| var ck = this._cipher.encryptBlock(h) |
| this._ghash = new GHASH(ck) |
| iv = calcIv(this, iv, ck) |
| |
| this._prev = Buffer.from(iv) |
| this._cache = Buffer.allocUnsafe(0) |
| this._secCache = Buffer.allocUnsafe(0) |
| this._decrypt = decrypt |
| this._alen = 0 |
| this._len = 0 |
| this._mode = mode |
| |
| this._authTag = null |
| this._called = false |
| } |
| |
| inherits(StreamCipher, Transform) |
| |
| StreamCipher.prototype._update = function (chunk) { |
| if (!this._called && this._alen) { |
| var rump = 16 - (this._alen % 16) |
| if (rump < 16) { |
| rump = Buffer.alloc(rump, 0) |
| this._ghash.update(rump) |
| } |
| } |
| |
| this._called = true |
| var out = this._mode.encrypt(this, chunk) |
| if (this._decrypt) { |
| this._ghash.update(chunk) |
| } else { |
| this._ghash.update(out) |
| } |
| this._len += chunk.length |
| return out |
| } |
| |
| StreamCipher.prototype._final = function () { |
| if (this._decrypt && !this._authTag) throw new Error('Unsupported state or unable to authenticate data') |
| |
| var tag = xor(this._ghash.final(this._alen * 8, this._len * 8), this._cipher.encryptBlock(this._finID)) |
| if (this._decrypt && xorTest(tag, this._authTag)) throw new Error('Unsupported state or unable to authenticate data') |
| |
| this._authTag = tag |
| this._cipher.scrub() |
| } |
| |
| StreamCipher.prototype.getAuthTag = function getAuthTag () { |
| if (this._decrypt || !Buffer.isBuffer(this._authTag)) throw new Error('Attempting to get auth tag in unsupported state') |
| |
| return this._authTag |
| } |
| |
| StreamCipher.prototype.setAuthTag = function setAuthTag (tag) { |
| if (!this._decrypt) throw new Error('Attempting to set auth tag in unsupported state') |
| |
| this._authTag = tag |
| } |
| |
| StreamCipher.prototype.setAAD = function setAAD (buf) { |
| if (this._called) throw new Error('Attempting to set AAD in unsupported state') |
| |
| this._ghash.update(buf) |
| this._alen += buf.length |
| } |
| |
| module.exports = StreamCipher |