NodeJS Crypto не может проверить подпись, созданную с помощью Web Crypto API - PullRequest
0 голосов
/ 15 марта 2019

У меня проблемы с проверкой подписей, созданных API Web Crypto.

Вот код, который я использую для генерации ключей RSA в браузере:

let keys;

const generateKeys = async () => {
  const options = {
    name: 'RSASSA-PKCS1-v1_5',
    modulusLength: 2048, 
    publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
    hash: { name: 'SHA-256' }, 
  };

  keys = await window.crypto.subtle.generateKey(
    options,
    false, // non-exportable (public key still exportable)
    ['sign', 'verify'],
  );
};

И чтобыэкспортировать открытый ключ:

const exportPublicKey = async () => {    
  const publicKey = await window.crypto.subtle.exportKey('spki', keys.publicKey);

  let body = window.btoa(String.fromCharCode(...new Uint8Array(publicKey)));
  body = body.match(/.{1,64}/g).join('\n');

  return `-----BEGIN PUBLIC KEY-----\n${body}\n-----END PUBLIC KEY-----`;

  // Output:
  //
  // -----BEGIN PUBLIC KEY-----
  // MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx7J3SUG4sq/HSGIaGZWY
  // 8b26cfEpVFYHoDUDUORIJzA/fLE9aj+uOKpGUTSfW69rMm7DAOLDz05KaEJJSI5+
  // YbDPr2S82A2ByHHQt+Vu168sGz4noXTTSX2HIdVutaR/IJ0a5pNOa1vRR4MUW/ZO
  // YaRir3yC5YXgcFLwwQaifNZ3lZ7WndbYEjTGOcieQQ81IUP2221PZCJI52S95nYm
  // VfslsLiPhOFH7XhGSqelGYDi0cKyl0p6dKvYxFswfKKLTuWnu2BEFLjVq4S5Y9Ob
  // SGm0KL/8g7pAqjac2sMzzhHtxZ+7k8tynzAf4slJJhHMm5U4DcSelTe5zOkprCJg
  // muyv0H1Acb3tfXsBwfURjiE0cvSMhfum5I5epF+f139tsr1zNF24F2WgvEZZbXcG
  // g1LveGCJ/0BY0pzE71DU2SYiUhl+HGDv2u32vJO80jCDf2lu7izEt544a+XE+2X0
  // zVpwjNQGa2Nd4ApGosa1fbcS5MsEdbyrjMf80SAmOeb9g3y5Zt2MY7M0Njxbvmmd
  // mF20PkklpH0L01lhg2AGma4o4ojolYHzDoM5a531xTw1fZIdgbSTowz0SlAHAKD3
  // c2KCCsKlBbFcqy4q7yNX63SqmI3sNA3kTH9CQJdBloRvV103Le9C0iY8CAWQmow5
  // N/sDJUabgOMqe9yopSjb7LUCAwEAAQ==
  // -----END PUBLIC KEY-----
};

Чтобы подписать сообщение:

const generateHash = async (message) => {
  const encoder = new TextEncoder();
  const buffer = encoder.encode(message);

  const digest = await window.crypto.subtle.digest('SHA-256', buffer);
  return digest;
};

const signMessage = async (message) => {
  const { privateKey } = keys;
  const digest = await generateHash(message);
  const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, digest);
  return signature;
};

Чтобы проверить сообщение в браузере:

const verifyMessage = async (signature, message) => {
  const { publicKey } = keys;
  const digest = await generateHash(message);
  const result = await window.crypto.subtle.verify('RSASSA-PKCS1-v1_5', publicKey, signature, digest);
  return result;
};

Когда ключи созданы,открытый ключ экспортируется и отправляется на сервер.Позже:

const message = 'test';
const signature = await signMessage(message);
await verifyMessage(signature, message); // true

sendToServer(message, bufferToHex(signature));

Поскольку подпись является ArrayBuffer, я конвертирую ее в шестнадцатеричный код со следующим кодом:

const bufferToHex = input => [...new Uint8Array(input)]
  .map(v => v.toString(16).padStart(2, '0')).join('');

На сервере (NodeJS 8.11.0):

const publicKey = getPublicKey(userId);

const verifier = crypto.createVerify('RSA-SHA256');
verifier.update(message, 'utf-8');

const sigBuf = Buffer.from(signature, 'hex');
verifier.verify(publicKey, sigBuf); // false

Я преследовал эту проблему в течение нескольких дней и просто не могу понять это.Я пробовал оба RSA-SHA256 и sha256WithRSAEncryption для проверки безрезультатно.Кроме того, никаких ошибок не выбрасывается.Буду очень признателен за любую помощь!

Ответы [ 3 ]

1 голос
/ 20 марта 2019

Так что я не до конца понимаю, почему это так, но для решения этой проблемы мне нужно было преобразовать хеш SHA из ArrayBuffer в шестнадцатеричную строку, а затем прочитать обратно в буфер массива с помощью TextEncoder.

const generateHash = async (message) => {
  const encoder = new TextEncoder();
  const buffer = encoder.encode(message);

  const digest = await window.crypto.subtle.digest('SHA-256', buffer);

  // Convert to hex string
  return [...new Uint8Array(digest)]
    .map(v => v.toString(16).padStart(2, '0')).join('');;
};

Затем при подписании:

const signMessage = async (message) => {
  const encoder = new TextEncoder();
  const { privateKey } = keys;
  const digest = await generateHash(message);
  const signature = await window.crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, encoder.encode(digest));
  return signature;
};

Подпись больше не проверяется на клиенте, но проверяется на узле.100

0 голосов
/ 28 июля 2019

Проблема заключается в том, что вы подписываете хэш хэша своего ввода, когда вы действительно должны подписывать хэш своего ввода.SubtleCrypto внутренне хэширует ввод.Вам не нужно предоставлять хешированный ввод.Так как вы предоставили хэшированный ввод в качестве аргумента, SubtleCrypto снова хэшировал его, а затем подписал, что привело к несоответствию подписей.

0 голосов
/ 16 марта 2019

Не прямой ответ, но может быть проще использовать это: https://www.npmjs.com/package/@peculiar/webcrypto, поэтому ваш код на клиенте и сервере согласован при одновременном решении этой проблемы.

...