Как загрузить открытый ключ в формате PEM для шифрования?

до сих пор я использовал JSEncrypt, который может загружать открытый ключ из строки в формате PEM. А затем используйте его с RSA для шифрования строки. Например :

<textarea id="pubkey">-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+iOltdDtDdUq6u67L2Mb4HW5j
7E1scmYtg2mnnQD85LxFICZv3I3rQ4wMulfcH+n9VCrifdu4vN89lRLKgsb9Kzim
GUrbOWEZdKZ9D5Sfo90EXocM5NtHou14aN8xkRWbN7x/RK5o9jfJwKmrC1fCm6tx
2Qwvx5kypWQUN6UpCQIDAQAB
-----END PUBLIC KEY-----
</textarea>

и затем:

var encrypt = new JSEncrypt();
encrypt.setPublicKey($('#pubkey').val());

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

  1. удалить заголовок PEM
  2. снимите нижний колонтитул PEM
  3. удалить CR / LF
  4. обрезать строка
  5. декодировать строку Base64
  6. преобразовать результат в ArrayBuffer

затем я попытался импортировать ключ:

cryptoSubtle.importKey("spki", publicKey, {name: "RSA-OAEP", hash: {name: "SHA-256"}}, false, ["encrypt"]);

Я пробовал много способов (распаковать формат ASN / DER и т. д.) Но я получаю различные ошибки (данные DOMException и т. д.). Я не знаю, является ли формат PEM приемлемым в качестве поддерживаемого формата или я должен преобразовать ключ в формат веб-ключа JSON и т. д.

есть ли простой способ сделать это без сторонней библиотеки JS ?

2 ответов


Я нашел ответ после некоторых тестов. В моем случае я использовал JSEncrypt с PHP/openssl или phpseclib в качестве резервного.

С помощью JSEncrypt вы не можете выбрать алгоритм шифрования. И это оказывает влияние на обивка используется, когда PHP расшифровывает зашифрованное значение. JSEncrypt использует:

  • RSASSA-PKCS1-v1_5
  • SHA-1 как метод хэша

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

openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_PADDING);

но WebCrypto не совместим с JSEncrypt (мы не можем расшифровать сообщение с PHP с теми же параметрами), потому что:

  • WebCrypto может использовать SHA-1 в качестве хэш-метода, даже если это не рекомендуется.
  • но WebCrypto запрещает вам использовать RSASSA-PKCS1-v1_5 для целей шифрования (это разрешено только для целей подписания). Вместо этого вы должны использовать RSA-OAEP.

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

RSA_EAY_PRIVATE_DECRYPT:padding check failed

Итак, вы должны изменить опцию заполнения следующим образом (в PHP):

openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);

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

  1. удалить заголовок PEM
  2. удалить thePEM футер
  3. удалить CR / LF
  4. обрезать строку
  5. декодировать в base64 строка
  6. преобразовать результат в ArrayBuffer

полный код:

var crypto = window.crypto || window.msCrypto;
var encryptAlgorithm = {
  name: "RSA-OAEP",
  hash: {
    name: "SHA-1"
  }
};

function arrayBufferToBase64String(arrayBuffer) {
  var byteArray = new Uint8Array(arrayBuffer)
  var byteString = '';
  for (var i=0; i<byteArray.byteLength; i++) {
    byteString += String.fromCharCode(byteArray[i]);
  }
  return btoa(byteString);
}

function base64StringToArrayBuffer(b64str) {
  var byteStr = atob(b64str);
  var bytes = new Uint8Array(byteStr.length);
  for (var i = 0; i < byteStr.length; i++) {
    bytes[i] = byteStr.charCodeAt(i);
  }
  return bytes.buffer;
}

function textToArrayBuffer(str) {
  var buf = unescape(encodeURIComponent(str)); // 2 bytes for each char
  var bufView = new Uint8Array(buf.length);
  for (var i=0; i < buf.length; i++) {
    bufView[i] = buf.charCodeAt(i);
  }
  return bufView;
}

function convertPemToBinary(pem) {
  var lines = pem.split('\n');
  var encoded = '';
  for(var i = 0;i < lines.length;i++){
    if (lines[i].trim().length > 0 &&
        lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 && 
        lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-END PUBLIC KEY-') < 0 &&
        lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 &&
        lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) {
      encoded += lines[i].trim();
    }
  }
  return base64StringToArrayBuffer(encoded);
}

function importPublicKey(pemKey) {
  return new Promise(function(resolve) {
    var importer = crypto.subtle.importKey("spki", convertPemToBinary(pemKey), encryptAlgorithm, false, ["encrypt"]);
    importer.then(function(key) { 
      resolve(key);
    });
  });
}


if (crypto.subtle) {

      start = new Date().getTime();
      importPublicKey($('#pubkey').val()).then(function(key) {
        crypto.subtle.encrypt(encryptAlgorithm, key, textToArrayBuffer($('#txtClear').val())).then(function(cipheredData) {
            cipheredValue = arrayBufferToBase64String(cipheredData);
            console.log(cipheredValue);

        });
      });
}

Сначала выберите свой любимый Base64-to-ArrayBuffer и String-to-ArrayBuffer методы, например,

function b64ToArrayBuffer(b64) {
    return new Promise((res, rej) => {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'data:application/octet-stream;base64,' + b64);
        xhr.responseType = 'arraybuffer';
        xhr.addEventListener('load', e => res(xhr.response));
        xhr.addEventListener('error', e => rej(xhr));
        xhr.send();
    });
}

function stringToArrayBuffer(str) {
    return new Promise((res, rej) => {
        var xhr = new XMLHttpRequest();
        xhr.open('GET', 'data:text/plain,' + str);
        xhr.responseType = 'arraybuffer';
        xhr.addEventListener('load', e => res(xhr.response));
        xhr.addEventListener('error', e => rej(xhr));
        xhr.send();
    });
}

(есть шанс, что это даст Uncaught (in promise) DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).)

тогда вы можете написать