首页 > 解决方案 > Webcrypto 从 PEM 编码中的私钥派生 RSA 公钥

问题描述

我正在尝试从 Webcrypto 环境中 PEM 编码中的给定私钥派生 RSA 公钥。我在网上和这里搜索了很多,但似乎没有简单的“内置”解决方案可用。

目前我分 5 步完成,我不敢相信没有编程解决方案 [不使用额外的外部库]。

这是我派生公钥的步骤

  1. 以 pem 编码导入私钥(剥离页眉和页脚行,将剩余数据从 Base64 编码转换为二进制格式
  2. 以 jwk 编码导出私钥
  3. 删除 jwk 对象中的 5 个元素:“d”;“dp”、“dq”、“q”、“qi”和“p”
  4. 将缩短的 jwk 作为 jwk 编码中的公钥导入
  5. 将公钥导出为带有附加页眉和页脚行的“SPKI”。

使用的私钥是PKCS8 格式的未加密示例 RSA 私钥,这是我的测试结果:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8EmWJUZ/Osz4vXtUU2S+
0M4BP9+s423gjMjoX+qP1iCnlcRcFWxthQGN2CWSMZwR/vY9V0un/nsIxhZSWOH9
iKzqUtZD4jt35jqOTeJ3PCSr48JirVDNLet7hRT37Ovfu5iieMN7ZNpkjeIG/CfT
/QQl7R+kO/EnTmL3QjLKQNV/HhEbHS2/44x7PPoHqSqkOvl8GW0qtL39gTLWgAe8
01/w5PmcQ38CKG0oT2gdJmJqIxNmAEHkatYGHcMDtXRBpOhOSdraFj6SmPyHEmLB
ishaq7Jm8NPPNK9QcEQ3q+ERa5M6eM72PpF93g2p5cjKgyzzfoIV09Zb/LJ2aW2g
QwIDAQAB
-----END PUBLIC KEY-----

此密钥与原始公钥匹配。

我的问题:Webcrypto 上还有其他可用的编程解决方案吗?

这是我没有异常处理的粗略代码(derive.html):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>SO Webcrypto derive an RSA public key from a private key in PEM encoding</title>
    <style>
        body {background-color: powderblue;}
        h1 {color: blue;}
        h2 {font-size: 200%;}
        p {font-size: 150%; }
        button {height: 100%; font-size: 150%;}
        textarea {font-size: 150%;}
        input[type="text"] {font-size: 100%; }
        input[type="button"] {font-size: 100%; }
    </style>
</head>
<body>
<h1>SO Webcrypto derive an RSA public key from a private key in PEM encoding</h1>

<hr>
<p>my sample private key (in PEM encoding):</p>
<textarea name="privateKeyPem" id="privateKeyPem" rows="10" cols="64">-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwSZYlRn86zPi9e1RTZL7QzgE/36zjbeCMyOhf6o/WIKeVxFwVbG2FAY3YJZIxnBH+9j1XS6f+ewjGFlJY4f2IrOpS1kPiO3fmOo5N4nc8JKvjwmKtUM0t63uFFPfs69+7mKJ4w3tk2mSN4gb8J9P9BCXtH6Q78SdOYvdCMspA1X8eERsdLb/jjHs8+gepKqQ6+XwZbSq0vf2BMtaAB7zTX/Dk+ZxDfwIobShPaB0mYmojE2YAQeRq1gYdwwO1dEGk6E5J2toWPpKY/IcSYsGKyFqrsmbw0880r1BwRDer4RFrkzp4zvY+kX3eDanlyMqDLPN+ghXT1lv8snZpbaBDAgMBAAECggEBAIVxmHzjBc11/73bPB2EGaSEg5UhdzZm0wncmZCLB453XBqEjk8nhDsVfdzIIMSEVEowHijYz1c4pMq9osXR26eHwCp47AI73H5zjowadPVluEAot/xgn1IdMN/boURmSj44qiI/DcwYrTdOi2qGA+jD4PwrUl4nsxiJRZ/x7PjLhMzRbvDxQ4/Q4ThYXwoEGiIBBK/iB3Z5eR7lFa8E5yAaxM2QP9PENBr/OqkGXLWVqA/YTxs3gAvkUjMhlScOi7PMwRX9HsrAeLKbLuC1KJv1p2THUtZbOHqrAF/uwHajygUblFaa/BTckTN7PKSVIhp7OihbD04bSRrh+nOilcECgYEA/8atV5DmNxFrxF1PODDjdJPNb9pzNrDF03TiFBZWS4Q+2JazyLGjZzhg5Vv9RJ7VcIjPAbMy2Cy5BUffEFE+8ryKVWfdpPxpPYOwHCJSw4Bqqdj0Pmp/xw928ebrnUoCzdkUqYYpRWx0T7YVRoA9RiBfQiVHhuJBSDPYJPoP34kCgYEA8H9wLE5L8raUn4NYYRuUVMa+1k4Q1N3XBixm5cccc/Ja4LVvrnWqmFOmfFgpVd8BcTGaPSsqfA4j/oEQp7tmjZqggVFqiM2mJ2YEv18cY/5kiDUVYR7VWSkpqVOkgiX3lK3UkIngnVMGGFnoIBlfBFF9uo02rZpC5o5zebaDImsCgYAE9d5wv0+nq7/STBj4NwKCRUeLrsnjOqRriG3GA/TifAsX+jw8XS2VF+PRLuqHhSkQiKazGr2Wsa9Y6d7qmxjEbmGkbGJBC+AioEYvFX9TaU8oQhvihgA6ZRNid58EKuZJBbe/3ek4/nR3A0oAVwZZMNGIH972P7cSZmb/uJXMOQKBgQCsFaQAL+4sN/TUxrkAkylqF+QJmEZ26l2nrzHZjMWROYNJcsn8/XkaEhD4vGSnazCu/B0vU6nMppmezF9Mhc112YSrw8QFK5GOc3NGNBoueqMYy1MG8Xcbm1aSMKVv8xbarh+BZQbxy6x61CpCfaT9hAoA6HaNdeoU6y05lBz1DQKBgAbYiIk56QZHeoZKiZxy4eicQS0sVKKRb24ZUd+04cNSTfeIuuXZrYJ48Jbr0fzjIM3EfHvLgh9rAZ+aHe/L84Ig17KiExe+qyYHjut/SC0wODDtzM/jtrpqyYa5JoEpPIaUSgPuTH/WhO3cDsx63PIW4/CddNs8mCSBOqTnoaxh
-----END PRIVATE KEY-----</textarea>
<br>
<button id="deriveRsaPublicKeyFromPrivateKeyPem" onclick="deriveRsaPublicKeyFromPrivateKeyPem()">derive public key</button>
<br>
<p>exported public key in PEM encoding:</p>
<textarea name="exportedPublicKeyPem" id="exportedPublicKeyPem" rows="10" cols="64">the exported key comes here...</textarea>
</body>

<SCRIPT LANGUAGE="JavaScript">
let privateKeyInternal;
let publicKeyInternal;
let publicKeyJwkInternal;

/*
Convert a string into an ArrayBuffer
from https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
*/
function str2ab(str) {
    const buf = new ArrayBuffer(str.length);
    const bufView = new Uint8Array(buf);
    for (let i = 0, strLen = str.length; i < strLen; i++) {
      bufView[i] = str.charCodeAt(i);
        }
    return buf;
}

function spkiToPEM(keydata){
    var keydataS = arrayBufferToString(keydata);
    var keydataB64 = window.btoa(keydataS);
    var keydataB64Pem = formatSpkiAsPem(keydataB64);
    return keydataB64Pem;
}

function arrayBufferToString( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return binary;
}

function formatSpkiAsPem(str) {
    var finalString = '-----BEGIN PUBLIC KEY-----\n';
    while(str.length > 0) {
        finalString += str.substring(0, 64) + '\n';
        str = str.substring(64);
    }
    finalString = finalString + "-----END PUBLIC KEY-----";
    return finalString;
}

// step 5 export the rsa public key in pem encoding
function exportPublicKeyPem() {
    var publicKey = publicKeyInternal;
    window.crypto.subtle.exportKey(
        "spki", // can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
        publicKey // can be a publicKey or privateKey, as long as extractable was true
    )
    .then(function(keydata){
        var publicKeyPem = spkiToPEM(keydata);
        document.getElementById('exportedPublicKeyPem').value = publicKeyPem;
    })
    .catch(function(err){
        console.error(err);
    });
}

// step 4 import the rsa public key in jwk encoding
function importPublicKeyJwk() {
    const binaryJwk = publicKeyJwkInternal;
    window.crypto.subtle.importKey(
        "jwk", // can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
        binaryJwk,
        {   // these are the algorithm options
            name: "RSASSA-PKCS1-v1_5",
            hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
        },
        true, //whether the key is extractable (i.e. can be used in exportKey)
        ["verify"] //"verify" for public key import, "sign" for private key imports
    )
    .then(function(publicKey){
        //returns a publicKey (or privateKey if you are importing a private key)
        publicKeyInternal = publicKey;
        exportPublicKeyPem();
    })
    .catch(function(err){
        console.error(err);
    });
}

// step 2 export the rsa private key in jwk encoding
function exportPrivateKeyJwk() {
    var privateKey = privateKeyInternal;
    window.crypto.subtle.exportKey(
        "jwk", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
        privateKey //can be a publicKey or privateKey, as long as extractable was true
    )
    .then(function(keydata){
        // step 3 strip off some jwk elements
        // derive the public key
        // by removing private data from JWK
        delete keydata.d;
        delete keydata.dp;
        delete keydata.dq;
        delete keydata.q;
        delete keydata.qi;
        delete keydata.p;
        keydata.key_ops = ["encrypt", "verify"];
        publicKeyJwkInternal = keydata; // save the keydata in publicKeyJwkInternal
        importPublicKeyJwk();
    })
    .catch(function(err){
        console.error(err);
    });
}

// step 1: import rsa private key in pem encoding
function deriveRsaPublicKeyFromPrivateKeyPem() {
    var privateKeyPem = document.getElementById('privateKeyPem').value;
    var pem = privateKeyPem;
    // fetch the part of the PEM string between header and footer
    const pemHeader = "-----BEGIN PRIVATE KEY-----";
    const pemFooter = "-----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);
    // import rsa private key
    window.crypto.subtle.importKey(
        "pkcs8", // can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
        binaryDer,
        {   // these are the algorithm options
            name: "RSASSA-PKCS1-v1_5",
            hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
        },
        true, //whether the key is extractable (i.e. can be used in exportKey)
        ["sign"] //"verify" for public key import, "sign" for private key imports
    )
    .then(function(privateKey){
        privateKeyInternal = privateKey;
        // now export the private key as jwk
        exportPrivateKeyJwk(); // the key is taken from privateKeyInternal
    })
    .catch(function(err){
        console.error(err);
    });
}
</SCRIPT>
</html>

标签: javascriptrsaprivate-keypublic-keywebcrypto-api

解决方案


推荐阅读