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