presets: add v2ray-vmess support
This commit is contained in:
parent
ceeb47d945
commit
e10ae03f74
@ -10,6 +10,9 @@ import SsBasePreset from './ss-base';
|
||||
import SsStreamCipherPreset from './ss-stream-cipher';
|
||||
import SsAeadCipherPreset from './ss-aead-cipher';
|
||||
|
||||
// v2ray
|
||||
import V2rayVmessPreset from './v2ray-vmess';
|
||||
|
||||
// obfuscator
|
||||
import ObfsRandomPaddingPreset from './obfs-random-padding';
|
||||
import ObfsHttpPreset from './obfs-http';
|
||||
@ -36,6 +39,9 @@ const mapping = {
|
||||
'ss-stream-cipher': SsStreamCipherPreset,
|
||||
'ss-aead-cipher': SsAeadCipherPreset,
|
||||
|
||||
// v2ray
|
||||
'v2ray-vmess': V2rayVmessPreset,
|
||||
|
||||
// obfuscator
|
||||
'obfs-random-padding': ObfsRandomPaddingPreset,
|
||||
'obfs-http': ObfsHttpPreset,
|
||||
|
529
src/presets/v2ray-vmess.js
Normal file
529
src/presets/v2ray-vmess.js
Normal file
@ -0,0 +1,529 @@
|
||||
import net from 'net';
|
||||
import crypto from 'crypto';
|
||||
import ip from 'ip';
|
||||
import {MIDDLEWARE_DIRECTION_UPWARD} from '../core/middleware';
|
||||
import {IPreset, CONNECT_TO_REMOTE, CONNECTION_WILL_CLOSE} from './defs';
|
||||
import {
|
||||
hmac,
|
||||
hash,
|
||||
shake128,
|
||||
fnv1a,
|
||||
getRandomInt,
|
||||
getChunks,
|
||||
getCurrentTimestampInt,
|
||||
numberToBuffer as ntb,
|
||||
AdvancedBuffer
|
||||
} from '../utils';
|
||||
|
||||
const ATYP_V4 = 0x01;
|
||||
const ATYP_DOMAIN = 0x02;
|
||||
const ATYP_V6 = 0x03;
|
||||
const TIME_TOLERANCE = 30;
|
||||
|
||||
function getAddrType(host) {
|
||||
if (net.isIPv4(host)) {
|
||||
return ATYP_V4;
|
||||
}
|
||||
if (net.isIPv6(host)) {
|
||||
return ATYP_V6;
|
||||
}
|
||||
return ATYP_DOMAIN;
|
||||
}
|
||||
|
||||
const SECURITY_TYPE_AES_128_GCM = 3;
|
||||
// const SECURITY_TYPE_CHACHA20_POLY1305 = 4;
|
||||
const SECURITY_TYPE_NONE = 5;
|
||||
|
||||
const securityTypes = {
|
||||
'aes-128-gcm': SECURITY_TYPE_AES_128_GCM,
|
||||
// 'chacha20-poly1305': SECURITY_TYPE_CHACHA20_POLY1305,
|
||||
'none': SECURITY_TYPE_NONE
|
||||
};
|
||||
|
||||
/**
|
||||
* @description
|
||||
* v2ray vmess protocol implementation.
|
||||
*
|
||||
* @params
|
||||
* id: client uuid.
|
||||
* security(optional): encryption method, client only, "aes-128-gcm"(default) or "none".
|
||||
*
|
||||
* @examples
|
||||
* {
|
||||
* "name": "v2ray-vmess",
|
||||
* "params": {
|
||||
* "id": "a3482e88-686a-4a58-8126-99c9df64b7bf",
|
||||
* "security": "aes-128-gcm"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @protocols
|
||||
*
|
||||
* # Client Request
|
||||
* +----------+-------------+---------------+
|
||||
* | AuthInfo | Command | Data Chunks |
|
||||
* +----------+-------------+---------------+
|
||||
* | 16 | Variable | Variable |
|
||||
* +----------+-------------+---------------+
|
||||
*
|
||||
* AuthInfo = HMAC("md5", UUID, UTC)
|
||||
*
|
||||
* # Command
|
||||
* +-----+------+-------+-----+-----+------+------+-----+-----+------+-----+------------+------------+-----+
|
||||
* | Ver | IV | Key | V | Opt | PLen | Sec | RSV | Cmd | Port | T | Addr | Padding | F |
|
||||
* +-----+------+-------+-----+-----+------+------+-----+-----+------+-----+------------+------------+-----+
|
||||
* | 1 | 16 | 16 | 1 | 1 | 4bit | 4bit | 1 | 1 | 2 | 1 | Variable | Variable | 4 |
|
||||
* +-----+------+-------+-----+-----+------+------+-----+-----+------+-----+------------+------------+-----+
|
||||
*
|
||||
* Ver = 0x01
|
||||
* IV = rand(16), Key = rand(16), V = rand(1)
|
||||
* Opt = 0x05
|
||||
* 0x01: standard data stream
|
||||
* 0x02: reuse TCP connection(- deprecated -)
|
||||
* 0x03: metadata obfuscate
|
||||
* PLen = 0~15
|
||||
* Sec
|
||||
* 0x00 aes-128-cfb(- deprecated -)
|
||||
* 0x03 aes-128-gcm
|
||||
* 0x04 chacha20-poly1305(not support)
|
||||
* 0x05 none
|
||||
* RSV = 0x00
|
||||
* Cmd = 0x01
|
||||
* 0x01 TCP
|
||||
* 0x02 UDP(not support)
|
||||
* Port: Big-Endian
|
||||
* T
|
||||
* 0x01: ipv4
|
||||
* 0x02: hostname
|
||||
* 0x03: ipv6
|
||||
* Addr
|
||||
* T=0x01: [ip4v], 4 bytes
|
||||
* T=0x02: [len,hostname]
|
||||
* T=0x03: [ipv6], 16 bytes
|
||||
* Padding = rand(PLen)
|
||||
* F = fnv1a(all the above)
|
||||
*
|
||||
* # Data Chunk
|
||||
* +-----------+------------+
|
||||
* | DataLen | Data |
|
||||
* +-----------+------------+
|
||||
* | 2 | Variable |
|
||||
* +-----------+------------+
|
||||
*
|
||||
* DataLen: Big-Endian, 0 ~ 0x3FFF
|
||||
*
|
||||
* # Data(when using "aes-128-gcm")
|
||||
* +-----------+---------+
|
||||
* | Payload | Tag |
|
||||
* +-----------+---------+
|
||||
* | Variable | 16 |
|
||||
* +-----------+---------+
|
||||
*
|
||||
* # Data(when using "none")
|
||||
* +-----------+
|
||||
* | Payload |
|
||||
* +-----------+
|
||||
* | Variable |
|
||||
* +-----------+
|
||||
*
|
||||
* # Server Response
|
||||
* +-----+-----+-----+----------+---------+-------------+
|
||||
* | V | Opt | Cmd | CmdLen | CmdData | Data Chunks |
|
||||
* +-----+-----+-----+----------+---------+-------------+
|
||||
* | 1 | 1 | 1 | 1 | Fixed | Variable |
|
||||
* +-----+-----+-----+----------+---------+-------------+
|
||||
*
|
||||
* V = Request V
|
||||
* Opt
|
||||
* 0x01: reuse TCP connection(- deprecated -)
|
||||
* Cmd
|
||||
* 0x01: dynamic port(not support)
|
||||
* Data Chunks: see above
|
||||
*
|
||||
* @notes
|
||||
* 1. "Opt" of client request is always 0x05.
|
||||
* 2. "alterId" of vmess client must be the default(0).
|
||||
* 3. "mux" of vmess client must be disabled.
|
||||
* 4. client UDP relay(CMD = 0x02, typically DNS requests) will be dropped.
|
||||
* 5. dynamic port is not implemented yet.
|
||||
*
|
||||
* @reference
|
||||
* [1] VMess Spec
|
||||
* https://www.v2ray.com/chapter_04/03_vmess.html
|
||||
* [2] VMess Config
|
||||
* https://www.v2ray.com/chapter_02/protocols/vmess.html
|
||||
*/
|
||||
export default class V2rayVmessPreset extends IPreset {
|
||||
|
||||
static uuid = null;
|
||||
|
||||
static security = null;
|
||||
|
||||
static userHashCache = [
|
||||
// {timestamp, authInfo}
|
||||
];
|
||||
|
||||
_atyp = null;
|
||||
_host = null; // buffer
|
||||
_port = null; // buffer
|
||||
|
||||
_isBroadCasting = false;
|
||||
_staging = Buffer.alloc(0);
|
||||
|
||||
_isHeaderSent = false;
|
||||
_isHeaderRecv = false;
|
||||
|
||||
_v = null;
|
||||
_opt = 0x05; // 0x01(S) or 0x05(S+M)
|
||||
_dataEncKey = null;
|
||||
_dataEncIV = null;
|
||||
_dataDecKey = null;
|
||||
_dataDecIV = null;
|
||||
_chunkLenEncMaskGenerator = null;
|
||||
_chunkLenDecMaskGenerator = null;
|
||||
|
||||
// nonce counter
|
||||
_cipherNonce = 0;
|
||||
_decipherNonce = 0;
|
||||
|
||||
static checkParams({id, security = 'aes-128-gcm'}) {
|
||||
if (Buffer.from(id.split('-').join(''), 'hex').length !== 16) {
|
||||
throw Error('id is not a valid uuid');
|
||||
}
|
||||
const securities = Object.keys(securityTypes);
|
||||
if (!securities.includes(security)) {
|
||||
throw Error(`security must be one of ${securities}`);
|
||||
}
|
||||
}
|
||||
|
||||
static onInit({id, security = 'aes-128-gcm'}) {
|
||||
this.uuid = Buffer.from(id.split('-').join(''), 'hex');
|
||||
if (__IS_CLIENT__) {
|
||||
this.security = securityTypes[security];
|
||||
}
|
||||
setInterval(() => this.updateAuthCache(), 1e3);
|
||||
this.updateAuthCache();
|
||||
}
|
||||
|
||||
static updateAuthCache() {
|
||||
const items = this.userHashCache;
|
||||
const now = getCurrentTimestampInt();
|
||||
let from = now - TIME_TOLERANCE;
|
||||
const to = now + TIME_TOLERANCE;
|
||||
let newItems = [];
|
||||
if (items.length !== 0) {
|
||||
const {timestamp: end} = items[items.length - 1];
|
||||
newItems = items.slice(now - end - TIME_TOLERANCE - 1);
|
||||
from = end + 1;
|
||||
}
|
||||
for (let ts = from; ts <= to; ++ts) {
|
||||
// account auth info, 16 bytes
|
||||
const uuid = this.uuid;
|
||||
const authInfo = hmac('md5', uuid, ntb(ts, 8));
|
||||
newItems.push({timestamp: ts, authInfo: authInfo});
|
||||
}
|
||||
this.userHashCache = newItems;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._adBuf = new AdvancedBuffer({getPacketLength: this.onReceiving.bind(this)});
|
||||
this._adBuf.on('data', this.onChunkReceived.bind(this));
|
||||
}
|
||||
|
||||
onDestroy() {
|
||||
this._adBuf.clear();
|
||||
this._adBuf = null;
|
||||
this._host = null;
|
||||
this._port = null;
|
||||
this._dataEncKey = null;
|
||||
this._dataEncIV = null;
|
||||
this._dataDecKey = null;
|
||||
this._dataDecIV = null;
|
||||
this._chunkLenEncMaskGenerator = null;
|
||||
this._chunkLenDecMaskGenerator = null;
|
||||
}
|
||||
|
||||
onNotified(action) {
|
||||
if (__IS_CLIENT__ && action.type === CONNECT_TO_REMOTE) {
|
||||
const {host, port} = action.payload;
|
||||
const type = getAddrType(host);
|
||||
this._atyp = type;
|
||||
this._port = ntb(port);
|
||||
this._host = (type === ATYP_DOMAIN) ? Buffer.from(host) : ip.toBuffer(host);
|
||||
}
|
||||
if (action.type === CONNECTION_WILL_CLOSE) {
|
||||
this.next(MIDDLEWARE_DIRECTION_UPWARD, Buffer.alloc(2));
|
||||
}
|
||||
}
|
||||
|
||||
beforeOut({buffer}) {
|
||||
if (!this._isHeaderSent) {
|
||||
this._isHeaderSent = true;
|
||||
const header = __IS_CLIENT__ ? this.createRequestHeader() : this.createResponseHeader();
|
||||
const chunks = this.getBufferChunks(buffer);
|
||||
return Buffer.concat([header, ...chunks]);
|
||||
} else {
|
||||
const chunks = this.getBufferChunks(buffer);
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
}
|
||||
|
||||
clientIn({buffer, next, fail}) {
|
||||
if (!this._isHeaderRecv) {
|
||||
this._isHeaderRecv = true;
|
||||
const decipher = crypto.createDecipheriv('aes-128-cfb', this._dataDecKey, this._dataDecIV);
|
||||
const header = decipher.update(buffer.slice(0, 4));
|
||||
const v = header[0];
|
||||
// const opt = header[1];
|
||||
// const cmd = header[2];
|
||||
const cmdLen = header[3];
|
||||
if (this._v !== v) {
|
||||
return fail(`server response v doesn't match, expect ${this._v} but got ${v}`);
|
||||
}
|
||||
return this._adBuf.put(buffer.slice(4 + cmdLen)/* drop command */, {next, fail});
|
||||
}
|
||||
this._adBuf.put(buffer, {next, fail});
|
||||
}
|
||||
|
||||
serverIn({buffer, next, fail}) {
|
||||
if (!this._isHeaderRecv) {
|
||||
|
||||
if (this._isBroadCasting) {
|
||||
this._staging = Buffer.concat([this._staging, buffer]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.length < 16) {
|
||||
return fail(`fail to parse request header: ${buffer.toString('hex')}`);
|
||||
}
|
||||
|
||||
const {uuid, userHashCache} = V2rayVmessPreset;
|
||||
|
||||
// verify auth info
|
||||
const authInfo = buffer.slice(0, 16);
|
||||
const cacheItem = userHashCache.find(({authInfo: auth}) => auth.equals(authInfo));
|
||||
if (cacheItem === undefined) {
|
||||
return fail(`cannot find ${authInfo.toString('hex')} in cache, maybe a wrong auth info`);
|
||||
}
|
||||
|
||||
// decrypt request header
|
||||
const ts = ntb(cacheItem.timestamp, 8);
|
||||
const decipher = crypto.createDecipheriv(
|
||||
'aes-128-cfb',
|
||||
hash('md5', Buffer.concat([uuid, Buffer.from('c48619fe-8f02-49e0-b9e9-edf763e17e21')])),
|
||||
hash('md5', Buffer.concat([ts, ts, ts, ts]))
|
||||
);
|
||||
const reqCommand = Buffer.from(buffer.slice(16));
|
||||
if (reqCommand.length < 41) {
|
||||
return fail(`request command is too short: ${reqCommand.length}bytes, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
|
||||
// decrypt the leading 41 bytes
|
||||
const reqHeader = decipher.update(reqCommand.slice(0, 41));
|
||||
|
||||
// verify version number
|
||||
const ver = reqHeader[0];
|
||||
if (ver !== 0x01) {
|
||||
return fail(`invalid version number: ${ver}`);
|
||||
}
|
||||
|
||||
this._dataDecIV = reqHeader.slice(1, 17);
|
||||
this._dataDecKey = reqHeader.slice(17, 33);
|
||||
this._dataEncIV = hash('md5', this._dataDecIV);
|
||||
this._dataEncKey = hash('md5', this._dataDecKey);
|
||||
this._chunkLenDecMaskGenerator = shake128(this._dataDecIV);
|
||||
this._chunkLenEncMaskGenerator = shake128(this._dataEncIV);
|
||||
this._v = reqHeader[33];
|
||||
this._opt = reqHeader[34];
|
||||
|
||||
const paddingLen = reqHeader[35] >> 4;
|
||||
const securityType = reqHeader[35] & 0x0f;
|
||||
// const rsv = reqHeader[36];
|
||||
|
||||
const cmd = reqHeader[37];
|
||||
if (![0x01/* tcp, 0x02 udp */].includes(cmd)) {
|
||||
return fail(`unsupported cmd: ${cmd}`);
|
||||
}
|
||||
const port = reqHeader.readUInt16BE(38);
|
||||
|
||||
// addressing
|
||||
const addrType = reqHeader[40];
|
||||
let addr = null;
|
||||
let offset = 40;
|
||||
if (addrType === ATYP_V4) {
|
||||
if (reqCommand.length < 45) {
|
||||
return fail(`request command is too short ${reqCommand.length}bytes to get ipv4, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
addr = decipher.update(reqCommand.slice(41, 45));
|
||||
offset += 4;
|
||||
} else if (addrType === ATYP_V6) {
|
||||
if (reqCommand.length < 57) {
|
||||
return fail(`request command is too short: ${reqCommand.length}bytes to get ipv6, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
addr = decipher.update(reqCommand.slice(41, 57));
|
||||
offset += 16;
|
||||
} else if (addrType === ATYP_DOMAIN) {
|
||||
if (reqCommand.length < 42) {
|
||||
return fail(`request command is too short: ${reqCommand.length}bytes to get host name, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
const addrLen = decipher.update(reqCommand.slice(41, 42))[0];
|
||||
if (reqCommand.length < 42 + addrLen) {
|
||||
return fail(`request command is too short: ${reqCommand.length}bytes, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
addr = decipher.update(reqCommand.slice(42, 42 + addrLen));
|
||||
offset += 1 + addrLen;
|
||||
} else {
|
||||
return fail(`unknown address type: ${addrType}, command=${reqHeader.toString('hex')}`);
|
||||
}
|
||||
if (reqCommand.length < offset + paddingLen + 4) {
|
||||
return fail(`request command is too short: ${reqCommand.length}bytes to get padding and f, command=${reqCommand.toString('hex')}`);
|
||||
}
|
||||
|
||||
// padding and F
|
||||
const padding = decipher.update(reqCommand.slice(offset, offset + paddingLen));
|
||||
offset += paddingLen;
|
||||
const f = decipher.update(reqCommand.slice(offset, offset + 4));
|
||||
|
||||
// verify F
|
||||
const plainReqHeader = Buffer.from([
|
||||
...reqHeader.slice(0, 41),
|
||||
...(addrType === ATYP_DOMAIN ? [addr.length] : []), ...addr,
|
||||
...padding
|
||||
]);
|
||||
if (fnv1a(plainReqHeader).equals(f)) {
|
||||
return fail('fail to verify request command');
|
||||
}
|
||||
const data = buffer.slice(16 + plainReqHeader.length + 4);
|
||||
V2rayVmessPreset.security = securityType;
|
||||
|
||||
this._isBroadCasting = true;
|
||||
this.broadcast({
|
||||
type: CONNECT_TO_REMOTE,
|
||||
payload: {
|
||||
host: addrType === ATYP_DOMAIN ? addr.toString() : ip.toString(addr),
|
||||
port: port,
|
||||
onConnected: () => {
|
||||
this._adBuf.put(Buffer.concat([data, this._staging]), {next, fail});
|
||||
this._isHeaderRecv = true;
|
||||
this._isBroadCasting = false;
|
||||
this._staging = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._adBuf.put(buffer, {next, fail});
|
||||
}
|
||||
}
|
||||
|
||||
createRequestHeader() {
|
||||
const rands = crypto.randomBytes(33);
|
||||
|
||||
// IV and Key for data chunks encryption/decryption
|
||||
this._dataEncIV = rands.slice(0, 16);
|
||||
this._dataEncKey = rands.slice(16, 32);
|
||||
this._dataDecKey = hash('md5', this._dataEncKey);
|
||||
this._dataDecIV = hash('md5', this._dataEncIV);
|
||||
this._chunkLenEncMaskGenerator = shake128(this._dataEncIV);
|
||||
this._chunkLenDecMaskGenerator = shake128(this._dataDecIV);
|
||||
|
||||
this._v = rands[32];
|
||||
this._opt = 0x05;
|
||||
|
||||
const {userHashCache, uuid} = V2rayVmessPreset;
|
||||
const {timestamp, authInfo} = userHashCache[getRandomInt(0, userHashCache.length - 1)];
|
||||
|
||||
// utc timestamp: Big-Endian, 8 bytes
|
||||
const ts = ntb(timestamp, 8);
|
||||
|
||||
const paddingLen = getRandomInt(0, 15);
|
||||
const padding = crypto.randomBytes(paddingLen);
|
||||
|
||||
// create encrypted command
|
||||
let command = Buffer.from([
|
||||
0x01, // Ver
|
||||
...this._dataEncIV, ...this._dataEncKey, this._v, this._opt,
|
||||
paddingLen << 4 | V2rayVmessPreset.security,
|
||||
0x00, // RSV
|
||||
0x01, // Cmd
|
||||
...this._port, this._atyp,
|
||||
...Buffer.concat([
|
||||
(this._atyp === ATYP_DOMAIN) ? ntb(this._host.length, 1) : Buffer.alloc(0),
|
||||
this._host
|
||||
]),
|
||||
...padding
|
||||
]);
|
||||
command = Buffer.concat([command, fnv1a(command)]);
|
||||
const cipher = crypto.createCipheriv(
|
||||
'aes-128-cfb',
|
||||
hash('md5', Buffer.concat([uuid, Buffer.from('c48619fe-8f02-49e0-b9e9-edf763e17e21')])),
|
||||
hash('md5', Buffer.concat([ts, ts, ts, ts]))
|
||||
);
|
||||
command = cipher.update(command);
|
||||
return Buffer.concat([authInfo, command]);
|
||||
}
|
||||
|
||||
createResponseHeader() {
|
||||
const cipher = crypto.createCipheriv('aes-128-cfb', this._dataEncKey, this._dataEncIV);
|
||||
return cipher.update(Buffer.from([this._v, 0x01, 0x00, 0x00]));
|
||||
}
|
||||
|
||||
getBufferChunks(buffer) {
|
||||
return getChunks(buffer, 0x3fff).map((chunk) => {
|
||||
let _chunk = chunk;
|
||||
if (V2rayVmessPreset.security === SECURITY_TYPE_AES_128_GCM) {
|
||||
_chunk = Buffer.concat(this.encrypt(_chunk));
|
||||
}
|
||||
let _len = _chunk.length;
|
||||
if (this._opt === 0x05) {
|
||||
const mask = this._chunkLenEncMaskGenerator.nextBytes(2).readUInt16BE(0);
|
||||
_len = mask ^ _len;
|
||||
}
|
||||
return Buffer.concat([ntb(_len), _chunk]);
|
||||
});
|
||||
}
|
||||
|
||||
onReceiving(buffer) {
|
||||
let len = buffer.readUInt16BE(0);
|
||||
if (this._opt === 0x05) {
|
||||
const mask = this._chunkLenDecMaskGenerator.nextBytes(2).readUInt16BE(0);
|
||||
len = mask ^ len;
|
||||
}
|
||||
return 2 + len;
|
||||
}
|
||||
|
||||
onChunkReceived(chunk, {next, fail}) {
|
||||
if (V2rayVmessPreset.security === SECURITY_TYPE_AES_128_GCM) {
|
||||
const tag = chunk.slice(-16);
|
||||
const data = this.decrypt(chunk.slice(2, -16), tag);
|
||||
if (data === null) {
|
||||
return fail(`fail to verify data chunk, dump=${chunk.slice(0, 60).toString('hex')}`);
|
||||
}
|
||||
return next(data);
|
||||
}
|
||||
return next(chunk.slice(2));
|
||||
}
|
||||
|
||||
encrypt(plaintext) {
|
||||
const iv = Buffer.concat([ntb(this._cipherNonce), this._dataEncIV.slice(2, 12)]);
|
||||
const cipher = crypto.createCipheriv('aes-128-gcm', this._dataEncKey, iv);
|
||||
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
||||
const tag = cipher.getAuthTag();
|
||||
this._cipherNonce += 1;
|
||||
return [encrypted, tag];
|
||||
}
|
||||
|
||||
decrypt(ciphertext, tag) {
|
||||
const iv = Buffer.concat([ntb(this._decipherNonce), this._dataDecIV.slice(2, 12)]);
|
||||
const decipher = crypto.createDecipheriv('aes-128-gcm', this._dataDecKey, iv);
|
||||
decipher.setAuthTag(tag);
|
||||
try {
|
||||
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
||||
this._decipherNonce += 1;
|
||||
return decrypted;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user