You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
104 lines
3.2 KiB
104 lines
3.2 KiB
2 years ago
|
import * as aes from "./aes";
|
||
|
import Transform from "../cipher-base";
|
||
|
import { inherits as inherits$0 } from "util";
|
||
|
import GHASH from "./ghash";
|
||
|
import xor from "../buffer-xor";
|
||
|
import incr32 from "./incr32";
|
||
|
var inherits = { inherits: inherits$0 }.inherits;
|
||
|
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;
|
||
|
};
|
||
|
export default StreamCipher;
|