1 | var aes = require('./aes');
|
2 | var Transform = require('./cipherBase');
|
3 | var inherits = require('inherits');
|
4 | var GHASH = require('./ghash');
|
5 | var xor = require('./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 | }
|
98 |
|
99 |
|