RSA 加密,在 PHP (phpseclib) 中加密,在 JavaScript (crypto.subtle) 中解密

RSA Encryption, Encrypt in PHP (phpseclib) and Decrypt in JavaScript (crypto.subtle)

我想做的是在 javascript 中生成一个密钥对,并在 PHP 中使用这些密钥对进行加密,然后用 JS 解密。

我在附加的代码中遇到了两个问题

  1. 它不会从装甲文本块重新加载私钥
  2. 它不会解密 PHP 加密的内容

两者都抛出错误 DOMException,而不是有用的错误。

所以这是我的代码...

PHP/JAVASCRIPT

<?php
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA;
if ($_POST) {
    $public=$_POST['public'];
    $data='some text to encrypt';
    $key = PublicKeyLoader::load($public);
    $key = $key->withPadding(RSA::ENCRYPTION_OAEP);
    $encoded=base64_encode($key->encrypt($data));
    header('Content-Type: application/json');
    echo json_encode(array('encrypted'=>$encoded));
    exit;
}
?>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js"></script>
<script>
jQuery(document).ready(function($) {
    function ab2str(buf) {
        return String.fromCharCode.apply(null, new Uint8Array(buf));
    }
    function str2ab(str) {
        var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
        var bufView = new Uint16Array(buf);
        for (var i=0, strLen=str.length; i < strLen; i++) {
            bufView[i] = str.charCodeAt(i);
        }
        return buf;
    }
    function importPrivateKey(pem) {
        // fetch the part of the PEM string between header and footer
        const pemHeader = "-----BEGIN PRIVATE KEY-----\n";
        const pemFooter = "\n-----END PRIVATE KEY-----";
        const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
        // base64 decode the string to get the binary data
        const binaryDerString = window.atob(pemContents);
        // convert from a binary string to an ArrayBuffer
        const binaryDer = str2ab(binaryDerString);

        return window.crypto.subtle.importKey(
            "pkcs8",
            binaryDer,
            {
                name: "RSA-OAEP",
                modulusLength: 1024,
                publicExponent: new Uint8Array([1, 0, 1]),
                hash: {name: "SHA-256"}
            },
            true,
            ["decrypt"]
          );
    }
    (async() => {
        let keyPair = await window.crypto.subtle.generateKey(
          {
                name: "RSA-OAEP",
                modulusLength: 1024,
                publicExponent: new Uint8Array([1, 0, 1]),
                hash: {name: "SHA-256"}
          },
          true,
          ["encrypt", "decrypt"]
        );
        
        var exported=await window.crypto.subtle.exportKey("pkcs8",keyPair.privateKey);
        var exportedAsString = ab2str(exported);
        var exportedAsBase64 = window.btoa(exportedAsString);
        var private = `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`;
        
        var exported = await window.crypto.subtle.exportKey(
            "spki",
            keyPair.publicKey
        );
        var exportedAsString = ab2str(exported);
        var exportedAsBase64 = window.btoa(exportedAsString);
        var public = `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;

        console.log(public);
        console.log(private);
        
        $.ajax({
            url:window.location,
            type:'POST',
            data:{
                public:public
            },
            success:function(data) {
                (async() => {
                    console.log('*ENCRYPTED BY PHP*',data.encrypted);
                    // HELP!!! NEED TO BE ABLE TO RELOAD THE KEY FROM ARMORED STRING
                    var key=await importPrivateKey(private); // Error - Uncaught (in promise) DOMException
                    var buffer=str2ab(window.atob(data.encrypted));
                    // HELP!!! WONT DECRYPT WHAT PHP ENCODED USING THE PUBLIC KEY
                    var decrypted=await window.crypto.subtle.decrypt({name:"RSA-OAEP"},key,buffer);
                    
                    console.log('DECRYPTED',decrypted);
                })();
            }
        });
    })();
});
</script>

错误出现在 str2ab() 函数中,它使用 Uint16Array 而不是 Uint8Array

如果解决了,可以导入私钥,解密PHP密码生成的密文:

function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

function str2ab(str) {
    // Fix: Don't double the size 
    var buf = new ArrayBuffer(str.length);                  
    // Fix: Apply a Uint8Array!
    var bufView = new Uint8Array(buf);                      
    for (var i=0, strLen=str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}

function importPrivateKey(pem) {

    const pemHeader = "-----BEGIN PRIVATE KEY-----\n";
    const pemFooter = "\n-----END PRIVATE KEY-----";
    const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);

    const binaryDerString = window.atob(pemContents);
    const binaryDer = str2ab(binaryDerString);

    return window.crypto.subtle.importKey(
        "pkcs8",
        binaryDer,
        {
            name: "RSA-OAEP",
            modulusLength: 1024,
            publicExponent: new Uint8Array([1, 0, 1]),
            hash: {name: "SHA-256"}
        },
        true,
        ["decrypt"]
    );
}

(async function() {

    // Apply the private key from key pair generated with the posted JavaScript code
    var privateKey = 
    `-----BEGIN PRIVATE KEY-----                     
        MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKeQUdBu3zTX6QyfGfRWYxWWOnxd2xssTOIu6XczDByQEMfBbpQO9iM3u/Mn84zZFPFNvOKUNxcnftmrPiqUO9fBI2aAh77d2m65FBGsm4k/oUPzMNORGaDdBY4gg8FPMKo60kqBaMXAwzF8I4EUS/ot2fkBzSL0BGXT9o1NaO8bAgMBAAECgYAO2OPW8ywF86ervaFAHDN1YzVVdb+HXdqGJB/9tuE42q8R9BrHNbgrkLGvrveOoGGRrBCzhuyGubIsuVat0SqoI6qEnB9uahaIBfF5FZ7+bNW5OfkgerUUYP1S1MGFxUqINnUY1YHITmo6pUKHsiJtP7sihnCT6uEx8LqVNf1quQJBANs+VCZVUDq6eMy3E/u03HiAB8cyqLVMVQ4cLyoiWmFlnEFzZwMd20ZMjtcxICiizW3dlDvyxWYKH93irL0JyM0CQQDDp/VFsh83vKICVvM9IZHwE/Z8vZA3eTkGbWmgnr6qaxqge3FU02kUvIHHlvLmXYIt30lTq0Rn+Lz+TGV/jDeHAkBHYSaSiGojhLx5og1+gKbbEIv3vbWRuTVj76cnZ6HXXfaelIzwRdMzMw+6XgMjV8XcRCzTy7ma/Cbd3cPxk/LtAkEAwkehMVexz/KrHI+icG1JMI9iDnNdJPhmO4+hdzCqOyanBfwNiSF0Encslze4ci8f+NTjRwWlo2hGomzRzFk7OQJAPPd/o0azkg9nF+JxLiz7hF+/6MLVZgIfw04u05ANtOSVVQP4UTmJ/tNAe3OBUQVlRQAJ1m3jzUlir0ACPypC1Q==
    -----END PRIVATE KEY-----`;

    // Use the ciphertext generated with the PHP code
    var ciphertext = 'a8gEZ6/DymB8dTGPytQPNS8QiYFuUULK+/c0vtie1l722isC0Z/jSeC2ytA6MjVUuTdq7sPuNW850gEZ2XvKujLQzl9sjJ8pcsxznBzMK8v03YJCTBr2lbfHpEEtuSLaAR2UbovXDoCyIIvOnMjqlIS3Ug2PG4hALThn/aAUwE0=';

    var key = await importPrivateKey(privateKey);
    var decryptedBuffer = str2ab(window.atob(ciphertext));
    var decrypted = await window.crypto.subtle.decrypt(
        {name:"RSA-OAEP"},
        key,
        decryptedBuffer
    );
    
    console.log(ab2str(decrypted)); // some text to encrypt
})();

此处,发布的 JavaScript 代码用于生成 RSA 密钥对。 public键:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCnkFHQbt801+kMnxn0VmMVljp8XdsbLEziLul3MwwckBDHwW6UDvYjN7vzJ/OM2RTxTbzilDcXJ37Zqz4qlDvXwSNmgIe+3dpuuRQRrJuJP6FD8zDTkRmg3QWOIIPBTzCqOtJKgWjFwMMxfCOBFEv6Ldn5Ac0i9ARl0/aNTWjvGwIDAQAB
-----END PUBLIC KEY-----

用于使用发布的PHP代码执行加密,私钥在上面的代码中用于解密。