关于javascript:如何以PEM格式加载公钥进行加密?

How to load a public key in PEM format for encryption?

直到现在,我一直使用JSEncrypt,它能够从PEM格式的字符串中加载公共密钥。然后将其与RSA结合使用以加密字符串。例如:

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

然后:

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

我想对WebCrypto进行同样的操作,但是我不知道该怎么做。我尝试了以下步骤:

  • 卸下PEM接头连接器
  • 卸下PEM页脚
  • 删除CR / LF
  • 修剪字符串
  • 解码Base64字符串
  • 将结果转换为ArrayBuffer
  • 然后我尝试导入密钥:

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

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

    有没有第三方JS库的简单方法吗?


    经过一些测试,我找到了答案。就我而言,我将JSEncrypt与PHP / openssl或phpseclib一起用作备用。

    使用JSEncrypt,您无法选择加密算法。这会影响PHP解密加密值时使用的填充。 JSEncrypt使用:

    • RSASSA-PKCS1-v1_5
    • SHA-1作为哈希方法

    如果要解密消息,则必须使用默认的填充选项:

    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。

    如果尝试使用默认选项对加密值进行解码,则会收到以下消息:

    1
    RSA_EAY_PRIVATE_DECRYPT:padding check failed

    因此,您必须按照以下方式更改padding选项(在PHP中):

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

    关于我的原始问题,是的,如果您遵循我在帖子中提到的步骤,可以导入PEM格式的密钥

  • 卸下PEM接头连接器
  • 卸下PEM页脚
  • 删除CR / LF
  • 修剪字符串
  • 解码Base64字符串
  • 将结果转换为ArrayBuffer
  • 完整代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    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('\
    '
    );
      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方法,例如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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).)

    然后您可以编写Promise样式的encrypt函数

    1
    2
    3
    4
    5
    6
    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));
    }

    最后,使用它

    1
    2
    3
    4
    5
    6
    encrypt('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+iOltdDtDdUq6u67L2Mb4HW5j\\
    7E1scmYtg2mnnQD85LxFICZv3I3rQ4wMulfcH+n9VCrifdu4vN89lRLKgsb9Kzim\\
    GUrbOWEZdKZ9D5Sfo90EXocM5NtHou14aN8xkRWbN7x/RK5o9jfJwKmrC1fCm6tx\\
    2Qwvx5kypWQUN6UpCQIDAQAB'
    , 'Hello World')
        .then(result => console.log(String.fromCharCode.apply(null, new Uint16Array(result))));
    // ?鸵?????淓???????怀跰?偌诔?曶?????沶碅姮?????????茱?????蟌赅龙?偼幱?????勍