1 | var aes = require('./aes')
|
2 | var Transform = require('cipher-base')
|
3 | var inherits = require('inherits')
|
4 | var GHASH = require('./ghash')
|
5 | var xor = require('buffer-xor')
|
6 | inherits(StreamCipher, Transform)
|
7 | module.exports = StreamCipher
|
8 |
|
9 | function StreamCipher (mode, key, iv, decrypt) {
|
10 | if (!(this instanceof StreamCipher)) {
|
11 | return new StreamCipher(mode, key, iv)
|
12 | }
|
13 | Transform.call(this)
|
14 | this._finID = Buffer.concat([iv, new Buffer([0, 0, 0, 1])])
|
15 | iv = Buffer.concat([iv, new Buffer([0, 0, 0, 2])])
|
16 | this._cipher = new aes.AES(key)
|
17 | this._prev = new Buffer(iv.length)
|
18 | this._cache = new Buffer('')
|
19 | this._secCache = new Buffer('')
|
20 | this._decrypt = decrypt
|
21 | this._alen = 0
|
22 | this._len = 0
|
23 | iv.copy(this._prev)
|
24 | this._mode = mode
|
25 | var h = new Buffer(4)
|
26 | h.fill(0)
|
27 | this._ghash = new GHASH(this._cipher.encryptBlock(h))
|
28 | this._authTag = null
|
29 | this._called = false
|
30 | }
|
31 | StreamCipher.prototype._update = function (chunk) {
|
32 | if (!this._called && this._alen) {
|
33 | var rump = 16 - (this._alen % 16)
|
34 | if (rump < 16) {
|
35 | rump = new Buffer(rump)
|
36 | rump.fill(0)
|
37 | this._ghash.update(rump)
|
38 | }
|
39 | }
|
40 | this._called = true
|
41 | var out = this._mode.encrypt(this, chunk)
|
42 | if (this._decrypt) {
|
43 | this._ghash.update(chunk)
|
44 | } else {
|
45 | this._ghash.update(out)
|
46 | }
|
47 | this._len += chunk.length
|
48 | return out
|
49 | }
|
50 | StreamCipher.prototype._final = function () {
|
51 | if (this._decrypt && !this._authTag) {
|
52 | throw new Error('Unsupported state or unable to authenticate data')
|
53 | }
|
54 | var tag = xor(this._ghash.final(this._alen * 8, this._len * 8), this._cipher.encryptBlock(this._finID))
|
55 | if (this._decrypt) {
|
56 | if (xorTest(tag, this._authTag)) {
|
57 | throw new Error('Unsupported state or unable to authenticate data')
|
58 | }
|
59 | } else {
|
60 | this._authTag = tag
|
61 | }
|
62 | this._cipher.scrub()
|
63 | }
|
64 | StreamCipher.prototype.getAuthTag = function getAuthTag () {
|
65 | if (!this._decrypt && Buffer.isBuffer(this._authTag)) {
|
66 | return this._authTag
|
67 | } else {
|
68 | throw new Error('Attempting to get auth tag in unsupported state')
|
69 | }
|
70 | }
|
71 | StreamCipher.prototype.setAuthTag = function setAuthTag (tag) {
|
72 | if (this._decrypt) {
|
73 | this._authTag = tag
|
74 | } else {
|
75 | throw new Error('Attempting to set auth tag in unsupported state')
|
76 | }
|
77 | }
|
78 | StreamCipher.prototype.setAAD = function setAAD (buf) {
|
79 | if (!this._called) {
|
80 | this._ghash.update(buf)
|
81 | this._alen += buf.length
|
82 | } else {
|
83 | throw new Error('Attempting to set AAD in unsupported state')
|
84 | }
|
85 | }
|
86 | function xorTest (a, b) {
|
87 | var out = 0
|
88 | if (a.length !== b.length) {
|
89 | out++
|
90 | }
|
91 | var len = Math.min(a.length, b.length)
|
92 | var i = -1
|
93 | while (++i < len) {
|
94 | out += (a[i] ^ b[i])
|
95 | }
|
96 | return out
|
97 | }
|