Comments 9
Вы про PKIJS в курсе? Или просто было интересно велосипед пописать?
Зачем тащить в проект лишнюю зависимость, если ты в состоянии написать нужную тебе реализацию?
Либа вообще кажется весьма нишевой. Для простого encrypt/decrypt уж точно не подходит.
скажите пожалуйста, что за магия в вашем коде? инструкция
key = await generateKey()
при каждом запуске генерирует новый ключ. Как сервер расшифровывает сообщения клиента, если ключ явно ему не передается?
Пригодилось, спасибо.
Но нюансов много, конечно, о чём следовало бы написать.
Самый главный минус, который имхо следовало бы упомянуть, это то, что window.crypto.subtle недоступно при подключении через HTTP, оно обязательно требует установки сертификата и работы через HTTPS (что на мой взгляд какой-то дебилизм), а это не всегда нужно и удобно (для внутренних сервисов, например).
В итоге приходится использовать сторонние либы. Я решил через forge.
// ArrayBuffer -> Base64
const pack = buffer => window.btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
// Base64 -> ArrayBuffer
const unpack = (base64) => {
const binaryString = window.atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
};
const key = unpack('BASE64_KEY');
const decrypt = (ciphertext, key, iv) => {
const decipher = forge.cipher.createDecipher('AES-CTR', forge.util.createBuffer(key));
decipher.start({ iv: forge.util.createBuffer(iv) });
decipher.update(forge.util.createBuffer(ciphertext));
decipher.finish();
// Получаем бинарные данные и конвертируем в UTF-8, иначе побъётся русский текст
const bytes = decipher.output.getBytes();
const decoder = new TextDecoder('utf-8');
return decoder.decode(new Uint8Array(forge.util.binary.raw.decode(bytes)));
};
const getAndDecryptMsg = async () => {
const output = document.querySelector('.output');
const res = await fetch('/bidzaar/secure-api')
const { data, v: vector } = await res.json()
// Распаковка данных
const msg = decrypt(unpack(data), key, unpack(vector));
output.innerHTML = `<div>${objectToHTML(JSON.parse(msg))}</div>`
}
На стороне NodeJS, соответственно:
const pack = (buffer) => Buffer.from(buffer).toString('base64'); // Из буфера в base64
const unpack = (packed) => Buffer.from(packed, 'base64'); // Из base64 в буфер
const generateKey = () => crypto.randomBytes(16); // 128-битный ключ
// Функция шифрования
const encrypt = (data, key, iv) => {
const cipher = crypto.createCipheriv('aes-128-ctr', key, iv);
let encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
return encrypted; // Возвращает Buffer
};
const encryptAndSendMsg = async (msg) => {
const iv = generateKey();
const key = unpack('LnP02IO8s638ThgOQTvo6Q==');
const encryptedData = await encrypt(Buffer.from(msg, 'utf8'), key, iv);
return {
data: pack(encryptedData), // Данные в base64
v: pack(iv)
};
}
Вы про этот проект https://github.com/digitalbazaar/forge?
Приятно видеть, что на свете еще остались люди, способные делать библиотеки со вменяемым интерфейсом. Спасибо за наводку, от души плюс куда следует!
так и не понял почему у webcryptoapi такой мудацкий интерфейс. Одни функции возвращают промисы, другие - сразу дают результат. Тот же getRandomValues спроектирован в синхронном стиле, а вот хеш считается asynv'ом. Хотя по логике должно быть наоборот (случайные числа поставляются аппаратурой и здесь могут быть недетерминированные задержки, в отличие от простой как репа функции вычисления хеша). А эти все пляски с бубном вокруг шифрования. Вы же привели простой и лаконичный код.
Благодаря разработчикам интерфейсов webcryptoapi я неожиданно узнал, что, оказывается при создании ключа для симметричного шифра AES нужно указывать опции ['encrypt', 'decrypt'], а то работать не будет. Странно, везде до этого писали что один и тот же ключ годится как для шифрования так и для расшифровки. И вообще ключ - это какой то сверхсекретный магический объект, который должен быть обязательно сформирован специальными функциями generateKey или importKey. О как! Я-то наивный думал, что подойдет любая числовая 128-ми, 192- или 256-битная последовательность. Век живи - век учись!
Так что когда увидел ваш образец, воскликнул: "А что так можно было?" Спасибо.
Web Cryptography API: пример использования