узел crypto.publicEncrypt возвращает разные значения каждый раз, когда он используется

Я пытаюсь реализовать базовое асимметричное шифрование; одна служба имеет открытый ключ и шифрует значение с помощью этого открытого ключа, а затем другая служба получает зашифрованное сообщение, декодирует его с помощью закрытого ключа и что-то делает с расшифрованными данными.

Проблема, с которой я сталкиваюсь, заключается в том, что каждый раз, когда я использую встроенный crypto.publicEncrypt, я получаю другое зашифрованное значение. Насколько я могу судить, я использую одни и те же входные данные, поэтому, насколько я понимаю, я должен видеть тот же результат. Может, я неправильно это понял?

Вот моя утилита шифрования;

import { createPublicKey, createPrivateKey, privateDecrypt, publicEncrypt, constants } from "crypto";

const privateKeyPem = process.env.ENCRYPTION_PRIVATE_KEY;
const privateKeyPemFixed = privateKeyPem.replace(/\\n/g, "\n");
const privateKey = createPrivateKey(privateKeyPemFixed);
const publicKey = createPublicKey(privateKey);

// const private1 = privateKey.export({
//   type: 'pkcs1',
//   format: 'pem',
// }).toString("base64");

// const public1 = publicKey.export({
//   type: 'pkcs1',
//   format: 'pem',
// }).toString("base64");

export const encrypt = (text: string): string => {
  const buffer = Buffer.from(text);

  const encrypted1 = publicEncrypt(   {
      key: publicKey,
      oaepHash: 'sha256',
      padding: constants.RSA_PKCS1_OAEP_PADDING,
  }, buffer);

  const encrypted2 = publicEncrypt({
    key: publicKey,
    oaepHash: 'sha256',
    padding: constants.RSA_PKCS1_OAEP_PADDING,
  }, buffer);

  console.log(encrypted1.toString("base64"));
  console.log(encrypted2.toString("base64"));

  return encrypted1.toString("base64");
}

export const decrypt = (cipher: string): string => {
  const buffer = Buffer.from(cipher);
  const decrypted = privateDecrypt(privateKey, buffer);
  return decrypted.toString("utf8");
}

У меня есть тест-шутка, который выглядит так:

import { encrypt } from "./encryption";

describe("encryption", () => {

  const helloWorld = "Hello world";
  const encryptedHelloWorld = "IIisobkVsZxKiR0e5nwyIHjsww/ebrKXI0hzDbdTdC8KMU2rc57IRX9krhVThVma2no7gZcMvbfwJsRjHz1s7NoBiT+BitgYlI/LE1jMpFd5Bmghy2S93F/wGFRWA4DMAqdw32I9s8CRKVvellxkh3ZlJ5NyzxWG8kVfc11CrEMD+1sqo2e9cFCcTdx5jEVYpCgITy7X2vDxUwOPQ7bK8K56kU5ivQhUfyoHjd9VclRUxfBaSzOwLJQqK6RJPbNwuUfILcCaR72GTf4zWMhQqIvs/zHhSu+S9QQYPVvmZ1SzqqJaCM9mM6Cvl8Gn2brwcMB003f0CFb8WFimOgM6lQ==";

  it("should encrypt text", () => {
    const received = encrypt(helloWorld);
    expect(received).toEqual(encryptedHelloWorld);
  });
});

Однако он постоянно терпит неудачу, так как результат всегда кажется другим.

Я дважды запускал процесс шифрования в функции encrypt, чтобы продемонстрировать проблему; два значения, которые он выводит из системы, совершенно разные, и я не понимаю, почему.

  console.log
    aDWDWcE+Zs92/rp2DLJN8UTgwHPTg6TDqFPIrC3ODVIfZgo5uaQV0NTSESPPPAGHhHeKiWB8JFnVewJaEN7iz9StzRepaL3+DFpD/CvhA8L7o8CQ5CTeScqL9HedVkM7O4MziMHkTJy0Li7EjP/6xdp8Caw+m6EsqvQ9Yd3qN4OTwrsMWmItLIaAHmkB/4UPhMqVnddVnwBUVb7toJ5rvGc/uktZkZPuHdzJRI0XSW//ltHHFCi3zneoJ92v/myYZOtWTyBDTmrgUtzC5fHbsSVdnD9IyWTRf72fz1Hjf2z8xFdFsdugo/+0qzOwE77K4BkgukeIDwhAxmdIr5yo4w==

      at encrypt (utils/encryption.ts:33:11)

  console.log
    LROC3KIjXJVoQVawJYZUYqT7rhXC8enb6O9ipY9VnOFMilFM00NHGiF3FHJQLWqac5zWFFZg2ofygANqT7Y5rQRtePcUEM5bLEUHvMaDdOAEXSdOK4PTbiCqZCAIPd79VVsW9gk2+vhKHbsq78AXhycCgUiOVjv25ooluDvqj3CQ+sTR+5cbatYO5kpXWwpu/BmPlRZYwsLUldpCuUPAYbkItKmQmiq/FWw1+z9Vx8mMKYhPtLuSTxnRrJ2Hn1eQm2EkuEeWQAEp+TJYaBsi93NalqmcWDo5swNe5HFPUH4hV7xtMtTZv82Wu9uNJ+ADUTD1B2mKDzKr0M0yNEYcGA==

      at encrypt (utils/encryption.ts:34:11)

Сначала я подумал, есть ли проблема с моим многострочным закрытым ключом в моем .env, но я могу экспортировать свои закрытый и открытый ключи (см. Закомментированный код), и когда я выхожу из системы, они выглядят так, как я ожидал, что, как мне кажется означает, что keyObjects успешно создаются. Если ключи не были созданы успешно, возможно, он будет создавать новые ключи каждый раз, и это вызовет этот сбой? Но насколько я могу судить, они успешно создаются.

Я также прочитал этот ответ, в котором говорилось, что может быть проблема с реализацией OpenSSL на MacOS - я использую MacOs Big Sur, узел 14.16.0 (LTS). Итак, я brew install openssl, а затем связал его, и теперь я вижу, что использую OpenSSL, а не LibreSSL, проверяя так;

➜  website git:(master) ✗ openssl version
OpenSSL 1.1.1j  16 Feb 2021

Однако это, похоже, не имело значения.

Итак, что я могу сделать, чтобы функция шифрования надежно возвращала тот же вывод при том же вводе?

ИЗМЕНИТЬ

Я обновил свою утилиту шифрования следующим образом и согласен с тем, что результат шифрования будет другим, потому что он зашифрован с помощью уникального сеансового ключа, а также открытого ключа, однако все выходные значения правильно расшифровываются с помощью закрытого ключа.

import { createPublicKey, createPrivateKey, privateDecrypt, publicEncrypt } from "crypto";

const privateKeyPem = process.env.ENCRYPTION_PRIVATE_KEY;
const privateKeyPemFixed = privateKeyPem.replace(/\\n/g, "\n");
const privateKey = createPrivateKey(privateKeyPemFixed);
const publicKey = createPublicKey(privateKey);

export const encrypt = (text: string): string => {
  const buffer = Buffer.from(text, "utf8");
  const encrypted = publicEncrypt(publicKey, buffer);
  return encrypted.toString("base64");
}

export const decrypt = (cipher: string): string => {
  const buffer = Buffer.from(cipher, "base64");
  const decrypted = privateDecrypt(privateKey, buffer);
  return decrypted.toString("utf8");
}

person sauntimo    schedule 01.03.2021    source источник
comment
Для вашего шифрования вы используете RSA_PKCS1_OAEP_PADDING, который имеет важную функцию безопасности - он добавляет некоторые случайные данные, поэтому в конечном итоге каждый зашифрованный текст выглядит по-разному с одним и тем же ключом и вводом. Существуют и другие режимы заполнения, такие как PKCS # 1.5, но некоторые библиотеки помечают его как устаревший, поскольку он генерирует детермистические сигнатуры (выглядят одинаково для каждого запуска), что является небезопасным во многих средах.   -  person Michael Fehr    schedule 01.03.2021
comment
@MichaelFehr спасибо за это, это полезно для моего понимания   -  person sauntimo    schedule 01.03.2021


Ответы (1)


Оказывается, мои предположения о crypto.PublicEncrypt были неверными. Цитата из этого ответа

Критерий чистой функции 1: вызов функции с одинаковыми значениями всегда должен давать одно и то же возвращаемое значение.

Это невозможно при асимметричном шифровании, потому что для каждой операции генерируется случайный сеансовый ключ. Ключ сеанса шифруется открытым ключом, а затем ключ сеанса используется для шифрования полезной нагрузки. Возвращаемое значение обычно представляет собой просто закодированную версию двух значений: (1) ключ сеанса, зашифрованный с помощью открытого ключа, и (2) полезные данные, зашифрованные с помощью ключа сеанса.

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

Однако, несмотря на то, что возвращаемые значения не сравниваются как равные, я бы сказал, что они семантически равны - то есть, если вы расшифруете каждое значение с помощью соответствующего закрытого ключа, дешифрованные значения будут сравниваться как равные.

Итак, я обновил свой тест до:

import { decrypt, encrypt } from "./encryption";

describe("encryption", () => {

  it("should encrypt and decrypt text", () => {
    const encrypted = encrypt("Hello World");
    const decrypted = decrypt(encrypted);
    expect(decrypted).toEqual("Hello World");
  });
});

И теперь это работает.

person sauntimo    schedule 01.03.2021