javascript - Webcrypto 从 PEM 编码中的私钥派生 RSA 公钥
问题描述
我正在尝试从 Webcrypto 环境中 PEM 编码中的给定私钥派生 RSA 公钥。我在网上和这里搜索了很多,但似乎没有简单的“内置”解决方案可用。
目前我分 5 步完成,我不敢相信没有编程解决方案 [不使用额外的外部库]。
这是我派生公钥的步骤
- 以 pem 编码导入私钥(剥离页眉和页脚行,将剩余数据从 Base64 编码转换为二进制格式
- 以 jwk 编码导出私钥
- 删除 jwk 对象中的 5 个元素:“d”;“dp”、“dq”、“q”、“qi”和“p”
- 将缩短的 jwk 作为 jwk 编码中的公钥导入
- 将公钥导出为带有附加页眉和页脚行的“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>
解决方案
推荐阅读
- javascript - 如何检测 Google Scripts 中的选定选项?
- amazon-web-services - 如何将 SAML 属性从您的 IdP 映射到 AWS Elastic Search 角色?
- amazon-web-services - 如何为数据库RDS设置定时器?
- google-sheets - 基于 IFS 的唯一 QUERY,以及 Google 表格中的公式
- ios - Firebase 存储 - URL 处的文件:无法访问。确保文件 url 不是目录、符号链接或无效 url
- c# - 将 advapi32.dll 加密功能移植到 Linux 容器 C#
- html - 有什么技巧可以让内容栏内的绝对定位元素在没有 javascript 的情况下“捕捉”到视口的边缘?
- reactjs - 在 npm install 中找不到 Esprima 错误 404
- javascript - 从 JSON API 简化 JS 查询
- r - 无法从 Cloud Run 连接到 Google Cloud SQL - 使用 R Shiny