Переход от Node.js crypto к Web Crypto API

Переход от Node.js crypto к Web Crypto API

KOD

Введение

Web Crypto API - это новый инструмент Javascript для криптографии. Он совместим с современными браузерами и ведущими платформами, включая Cloudflare Workers и Vercel, а также Node.js. Такая взаимозаменяемость подразумевает мечту разработчика - напишите свой криптографический код один раз и выполните его на множестве платформ с использованием Web Crypto API.

Однако у Node.js уже есть модуль "crypto" с долгой историей, оставляющий значительное количество устаревшего кода, нуждающегося в миграции. В этой статье мы подробно рассмотрим опыт перехода, предоставляя всестороннее руководство, сосредоточенное на 3 общих сценариях. Мы стремимся осветить путь к успешной миграции.

Что такое Web Crypto API

Web Crypto API, открытый стандарт W3C для JavaScript, - это коллекция стандартизированных криптографических примитивов, определенных в спецификации Web Cryptography API. Он был создан после того, как несколько браузеров и платформ начали добавлять свои собственные несовместимые криптографические функции.

API предлагает примитивы для генерации ключей, шифрования и дешифрования, цифровых подписей, ключевого и битового вывода и криптографического дайджеста. Он основывается на интерфейсе, называемом SubtleCrypto, вы можете найти больше деталей и руководств в документации Mozilla MDN.

Но у Node.js уже есть модуль крипто?

Разработчики Node.js обычно знакомы с крипто модулем. Он предлагает полный набор криптографических примитивов. Этот модуль не только предоставляет механизмы для тех же криптографических операций, определенных в Web Crypto API, но часто включает более широкий диапазон криптографических алгоритмов.

Так зачем нам все еще нужен Web Crypto API? Поскольку Javascript становится популярным на многих платформах и средах, от клиентской стороны до серверов и особенно на периферии, важно иметь кросс-платформенный криптографический инструмент для упрощения процессов.

Кроме того, функции в стандарте Web Crypto API все возвращают обещания и поддерживают синтаксис async / await. Это значительное преимущество перед модулем crypto, который синхронен и может блокировать цикл событий.

А есть интересная вещь: Node.js добавляет его поддержку для Web Crypto API, что означает, что в большинстве случаев Web Crypto API идеален для всех известных платформ.

Использование Web Crypto API

На большинстве платформ коллекция API Web Crypto доступна через глобальный объект crypto, который включает 3 основных утилиты: getRandomValuesrandomUUID и subtle.

Есть много отличий по сравнению с традиционным модулем крипто. Они могут быть суммированы в 3 наиболее распространенных части. Давайте пройдемся по ним и посмотрим, как перейти от существующего кода.

#1 Генерация случайных значений

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

import { randomBytes } from 'crypto';

const generateRandomString = (length = 64) => randomBytes(length).toString('hex');

В Web Crypto есть похожая функция под названием getRandomValues, но возвращаемое значение - это ArrayBuffer, поэтому нам нужен еще один шаг для его преобразования в строку.

const generateRandomString = (length = 64) => {
  const array = new Uint8Array(10);
  crypto.getRandomValues(array);
  return Array.from(array)
    .map((byte) => byte.toString(16).padStart(2, '0'))
    .join('');
};

#2 Хеширование (или дайджест)

createHash легко использовать в крипто модуле:

export const sha256 = (text: string): string => {
  return createHash('sha256').update(text).digest('hex');
};

В Web Crypto мы можем использовать subtle.createHash

export const sha256 = async (text: string): Promise<string> => {
  const encoder = new TextEncoder();
  const data = encoder.encode(text);
  const hash = await crypto.subtle.digest('SHA-256', data);

  return Array.from(new Uint8Array(hash))
    .map((byte) => byte.toString(16).padStart(2, '0'))
    .join('');
};

Как видите, также требуется преобразование из ArrayBuffer в строку hex.

#3 Шифрование и дешифрование

В крипто модуле AES-шифрование и дешифрование можно реализовать так:

export const encrypt = (text: string, password: string) => {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-gcm', password, iv);
  const encrypted = cipher.update(text, 'utf8', 'base64');
  return {
    ciphertext: `${encrypted}${cipher.final('base64')}`, 
    iv: iv.toString('base64'),
  };
};

export const decrypt = (ciphertext: string, iv: string, password: string) => {
  const decipher = createDecipheriv('aes-256-gcm', password, iv);
  const decrypted = decipher.update(encrypted, 'base64', 'utf8');
  return `${decrypted}${decipher.final('utf8')}`;
};

В Web Crypto нам нужно сначала создать ключ с помощью importKey:

async function encrypt(text: string, password: string) {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encodedPlaintext = new TextEncoder().encode(text);

  const secretKey = await crypto.subtle.importKey(
    'raw',
    Buffer.from(await getKeyFromPassword(password, crypto), 'hex'),
    {
      name: 'AES-GCM',
      length: 256,
    },
    true,
    ['encrypt', 'decrypt']
  );

  const ciphertext = await crypto.subtle.encrypt(
    {
      name: 'AES-GCM',
      iv, 
    },
    secretKey,
    encodedPlaintext
  );

  return {
    ciphertext: Buffer.from(ciphertext).toString('base64'),
    iv: Buffer.from(iv).toString('base64'),
  };
}

async function decrypt(ciphertext: string, iv: string, password: string) {
  const secretKey = await crypto.subtle.importKey(
    'raw',
    Buffer.from(await getKeyFromPassword(password, crypto), 'hex'),
    {
      name: 'AES-GCM',
      length: 256,
    },
    true,
    ['encrypt', 'decrypt']
  );

  const cleartext = await crypto.subtle.decrypt(
    {
      name: 'AES-GCM',
      iv: Buffer.from(iv, 'base64'),
    },
    secretKey,
    Buffer.from(ciphertext, 'base64')
  );

  return new TextDecoder().decode(cleartext);
}

Вывод

Как вы можете видеть, миграция не сложна, главное - изменить синтаксис, чтобы соответствовать новому API и обработать ArrayBuffer с помощью TextEncoder.

Как продукт идентификации, Logto использует криптографию во многих местах. Мы перешли от модуля крипто к Web Crypto API. Этот переход позволяет нам лучше адаптироваться к окружениям на периферии и делает возможным безопасное выполнение кода SDK в браузере.

Report Page