如何加载PEM格式的公钥进行加密?

时间:2016-01-15 15:30:08

标签: javascript rsa webcryptoapi

到目前为止,我使用了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
  7. 然后我尝试导入密钥:

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

    我尝试了很多方法(解压缩ASN / DER格式等)但是我得到了各种错误(DOMException数据等)。我不知道PEM格式是否可以作为受支持的格式接受,或者我是否必须以JSON Web Key格式转换密钥等。

    如果没有第三方JS库,有没有简单的方法呢?

2 个答案:

答案 0 :(得分:9)

经过一些测试,我找到了答案。就我而言,我使用JSEncrypt和PHP / openssl或phpseclib作为后备。

使用JSEncrypt,您无法选择加密算法。这会对PHP解密加密值时使用的 padding 产生影响。 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. 删除PDF页脚
  3. 删除CR / LF
  4. 修剪字符串
  5. 解码Base64字符串
  6. 将结果转换为ArrayBuffer
  7. 完整代码:

    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);
    
            });
          });
    }
    

答案 1 :(得分:0)

首先选择您喜欢的 Base64-to-ArrayBuffer String-to-ArrayBuffer 方法,例如

y

(这些可能会给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(); }); }

然后你可以在 Promise 样式中编写Uncaught (in promise) DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).函数

encrypt

最后,使用它

function encrypt(b64_key, clear_text) {
    return b64ToArrayBuffer(b64_key)
        .then(buffer => window.crypto.subtle.importKey("spki", buffer, {name: "RSA-OAEP", hash: {name: "SHA-256"}}, false, ["encrypt"]))
        .then(key => new Promise((res, rej) => stringToArrayBuffer(clear_text).then(buffer => res({key, buffer}))))
        .then(data => window.crypto.subtle.encrypt({name: "RSA-OAEP", hash: {name: "SHA-256"}}, data.key, data.buffer));
}