-
Notifications
You must be signed in to change notification settings - Fork 7
/
aead.js
107 lines (107 loc) · 2.56 KB
/
aead.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
var inherits = require('inherits');
var CipherBase = require('./cipherbase');
var Chacha20 = require('./chacha20');
var CipherBase = require('./cipherbase');
var Poly1305 = require('./poly1305');
inherits(Cipher, CipherBase);
module.exports = Cipher;
function Cipher(key, iv, decrypt){
if (!(this instanceof Cipher)) {
return new Cipher(key, iv, decrypt);
}
CipherBase.call(this);
this.alen = 0;
this.clen = 0;
this.chacha = new Chacha20(key, iv);
this.poly = new Poly1305(this.chacha.getBytes(64));
this.tag = null;
this._decrypt = decrypt;
this._hasData = false;
}
Cipher.prototype.setAAD = function (aad) {
if (this._hasData) {
throw new Error('Attempting to set AAD in unsupported state');
}
this.alen = aad.length;
this.poly.update(aad);
var padding = new Buffer(padAmount(this.alen));
if (padding.length) {
padding.fill(0);
this.poly.update(padding);
}
};
Cipher.prototype._update = function (chunk) {
if (!this._hasData) {
this._hasData = true;
}
var len = chunk.length;
if (!len) {
return next();
}
this.clen += len;
var pad = this.chacha.getBytes(len);
var i = -1;
while (++i < len) {
pad[i] ^= chunk[i];
}
if (this._decrypt) {
this.poly.update(chunk);
} else {
this.poly.update(pad);
}
return pad;
};
Cipher.prototype._final = function () {
if (this._decrypt && !this.tag) {
throw new Error('Unsupported state or unable to authenticate data');
}
var padding = new Buffer(padAmount(this.clen));
if (padding.length) {
padding.fill(0);
this.poly.update(padding);
}
var lens = new Buffer(16);
lens.fill(0);
lens.writeUInt32LE(this.alen, 0);
lens.writeUInt32LE(this.clen, 8);
var tag = this.poly.update(lens).finish();
if (this._decrypt) {
if (xorTest(tag, this.tag)) {
throw new Error('Unsupported state or unable to authenticate data');
}
} else {
this.tag = tag;
}
};
Cipher.prototype.getAuthTag = function () {
if(this._decrypt || this.tag === null) {
throw new Error('Attempting to get auth tag in unsupported state');
}
return this.tag;
};
Cipher.prototype.setAuthTag = function setAuthTag (tag) {
if (this._decrypt) {
this.tag = tag;
} else {
throw new Error('Attempting to set auth tag in unsupported state');
}
};
function padAmount(len) {
var rem = len % 16;
if (!rem) {
return 0;
}
return 16 - rem;
}
function xorTest(a, b) {
var out = 0;
if (a.length !== b.length) {
out++;
}
var len = Math.min(a.length, b.length);
var i = -1;
while (++i < len) {
out += (a[i] ^ b[i]);
}
return out;
}