AES GCM шифрует в nodejs и расшифровывает в браузере?

Я пытаюсь зашифровать часть строки в nodejs, и мне нужно расшифровать ее во внешнем javascript. В nodejs я использовал криптографическую библиотеку, а в интерфейсе — веб-криптографию. Столкнулся с некоторой ошибкой при расшифровке во внешнем интерфейсе.

NodeJS

const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'passwordpasswordpasswordpassword';
let text = 'Hello World!';
let cipher = crypto.createCipheriv(algorithm, password, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
    content: encrypted,
    tag: tag,
    iv: iv
}

Внешний интерфейс

let cipherObj;  //GET FROM BE
let aesKey = await crypto.subtle.importKey(
  "raw",
  Buffer.from('passwordpasswordpasswordpassword'), //password
  "AES-GCM",
  true,
  ["decrypt"]
);

let decrypted = await window.crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv: Buffer.from(cipherObj.iv),
    tagLength: 128
  },
  aesKey,
  Buffer.concat([Buffer.from(cipherObj.content), Buffer.from(cipherObj.tag)])
);

Функция расшифровки во внешнем интерфейсе выдает ошибку.

ERROR Error: Uncaught (in promise): OperationError
    at resolvePromise (zone.js:814)
    at zone.js:724
    at rejected (main.js:231)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
    at Object.onInvoke (core.js:3820)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:387)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
    at zone.js:872
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
    at Object.onInvokeTask (core.js:3811)
    at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
    at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
    at drainMicroTaskQueue (zone.js:595)

PS: я использую Angular 7 во внешнем интерфейсе.


person Fenn Saji    schedule 28.03.2019    source источник
comment
ты решил это?   -  person Diana Ysabel    schedule 04.08.2020


Ответы (1)


Я смог заставить это работать с некоторыми изменениями:

  1. Я использую SHA-256 для хеширования пароля, поэтому он может быть любой длины. (Для OP требуется 32-байтовая строка.)
  2. Я добавил дополнительные вспомогательные функции из другого ответа для преобразования буферов в/из шестнадцатеричных.
  3. Я печатаю вывод cipherObj в JSON. Это ваша полезная нагрузка зашифрованного сообщения.

Помощники — NodeJS и браузер

// look up tables
var to_hex_array = [];
var to_byte_map = {};
for (var ord=0; ord<=0xff; ord++) {
    var s = ord.toString(16);
    if (s.length < 2) {
        s = "0" + s;
    }
    to_hex_array.push(s);
    to_byte_map[s] = ord;
}

// converter using lookups
function bufferToHex2(buffer) {
    var hex_array = [];
    //(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) });
    for (var i=0; i<buffer.length; i++) {
        hex_array.push(to_hex_array[buffer[i]]);
    }
    return hex_array.join('')
}
// reverse conversion using lookups
function hexToBuffer(s) {
    var length2 = s.length;
    if ((length2 % 2) != 0) {
        throw "hex string must have length a multiple of 2";
    }
    var length = length2 / 2;
    var result = new Uint8Array(length);
    for (var i=0; i<length; i++) {
        var i2 = i * 2;
        var b = s.substring(i2, i2 + 2);
        result[i] = to_byte_map[b];
    }
    return result;
}

Серверная часть использует hex2buffer, а внешняя — buffer2hex, но вы можете просто включить этот код в оба.

Таким образом, внутренний код — это вышеперечисленные помощники плюс:

NodeJS

const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'This is my password';
let key = crypto.createHash("sha256").update(password).digest();
let text = 'This is my test string, with ???? emoji in it!';
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
    content: encrypted,
    tag: bufferToHex2(tag),
    iv: bufferToHex2(iv)
}

console.log(JSON.stringify(cipherObj));

Вывод меняется при каждом запуске из-за случайного IV, но, например:

{"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}

Итак, пример внешнего кода — это вышеприведенные вспомогательные функции, а также:

Браузер

let cipherObj;  //GET FROM BACKEND
// For example:
cipherObj = {"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}

let password = 'This is my password';

let enc = new TextEncoder();
let key = await window.crypto.subtle.digest({ name:"SHA-256" }, enc.encode(password));
let aesKey = await crypto.subtle.importKey(
  "raw",
  key,
  "AES-GCM",
  true,
  ["decrypt"]
);

let decrypted = await window.crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv: hexToBuffer(cipherObj.iv),
    tagLength: 128
  },
  aesKey,
  hexToBuffer(cipherObj.content + cipherObj.tag)
);

let dec = new TextDecoder();
console.log(dec.decode(decrypted));
// This is my test string, with ???? emoji in it!

Некоторые криптографические справочные примеры.

person Jeff Ward    schedule 24.02.2021
comment
Серьезно, кто думал, что NodeJS и браузеры должны каждый раз иметь разные API. ???? - person Jeff Ward; 24.02.2021
comment
Большое спасибо! Лол, ты действительно написал это 2 часа назад? Я встаю в 2:30 ночи, борюсь именно с этой проблемой, и я уже собирался сдаться! Что, наконец, помогло мне, так это то, что мне нужно добавить тег аутентификации к сообщению перед расшифровкой! Кроме того, осознание того, что 16-БАЙТовый тег node.js crypto производит, соответствует 128-БИТовому тегу, который ожидает crypto.subtle! Через 2 минуты после того, как я нашел ваш пост, у меня все заработало! Хотел бы я купить тебе пива :) - person spartan_dev; 24.02.2021