javascript - 使用原生 javascript/sealCrypto 使用 RSA 进行加密

标签 javascript encryption

我正在尝试关注 subtleCrypto 的网络文档:
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt

To use RSA-OAEP, pass an RsaOaepParams object.

RSA 怎么样 key 应该被格式化?以下代码只是使用明文rsaPublicKeyrsaPrivateKey : 应该怎么改
let rsaPublicKey = "ssh-rsa AAAAB3 ..."

function encrypt(rsaPublicKey, msg) {
    let emsg = new TextEncoder().encode(msg)
    let encrypted = crypto.subtle.encrypt(
        {
            name: "RSA-OAEP"
        },
        rsaPublicKey,
        msg
    );
    return encrypted
}
let rsaPrivateKey = "MIIEvQIBADU ..."
function decrypt(rsaPrivateKey, encrypted) {
  return window.crypto.subtle.decrypt(
    {
      name: "RSA-OAEP"
    },
    rsaPrivateKey,
    encrypted
  );
}
这是尝试进行往返的(可能不正确的)代码:
let enc = encrypt(rsaKey, "hello world!") // ERROR on this line
console.log(enc)
let dec = decrypt(rsaPrivateKey, enc)
console.log(dec)
错误是:

Uncaught (in promise) TypeError: Failed to execute 'encrypt' on 'SubtleCrypto': parameter 2 is not of type 'CryptoKey'.


那么应该如何对公钥(和私钥)进行编码/格式化?

最佳答案

Web Crypto API 提供 SubtleCrypto.importKey() key 导入方法,支持多种 key 格式,特别是私钥的PKCS#8格式(PrivateKeyInfo结构的ASN.1 DER编码,见RFC5208 sec 5)和X.509格式(ASN. 1 SubjectPublicKeyInfo结构的DER编码,简称SPKI,见RFC5280 sec 4.1)的公钥。 key 应采用 DER 编码。如果它们采用 PEM 编码,则必须首先对其进行转换。为此,必须删除页眉和页脚,其余部分必须进行 Base64 解码。
导入 X.509 格式的公共(public) PEM 编码 key 和加密明文的示例:

// PEM encoded X.509 key
const publicKey = 
`-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunF5aDa6HCfLMMI/MZLT
5hDk304CU+ypFMFiBjowQdUMQKYHZ+fklB7GpLxCatxYJ/hZ7rjfHH3Klq20/Y1E
bYDRopyTSfkrTzPzwsX4Ur/l25CtdQldhHCTMgwf/Ev/buBNobfzdZE+Dhdv5lQw
KtjI43lDKvAi5kEet2TFwfJcJrBiRJeEcLfVgWTXGRQn7gngWKykUu5rS83eAU1x
H9FLojQfyia89/EykiOO7/3UWwd+MATZ9HLjSx2/Lf3g2jr81eifEmYDlri/OZp4
OhZu+0Bo1LXloCTe+vmIQ2YCX7EatUOuyQMt2Vwx4uV+d/A3DP6PtMGBKpF8St4i
GwIDAQAB
-----END PUBLIC KEY-----`;

importPublicKeyAndEncrypt();
    
async function importPublicKeyAndEncrypt() {

    const plaintext = 'This text will be encoded UTF8 and may contain special characters like § and €.';
                
    try {
        const pub = await importPublicKey(publicKey);
        const encrypted = await encryptRSA(pub, new TextEncoder().encode(plaintext));
        const encryptedBase64 = window.btoa(ab2str(encrypted));
        console.log(encryptedBase64.replace(/(.{64})/g, "$1\n")); 
    } catch(error) {
        console.log(error);
    }
}

async function importPublicKey(spkiPem) {       
    return await window.crypto.subtle.importKey(
        "spki",
        getSpkiDer(spkiPem),
        {
            name: "RSA-OAEP",
            hash: "SHA-256",
        },
        true,
        ["encrypt"]
    );
}

async function encryptRSA(key, plaintext) {
    let encrypted = await window.crypto.subtle.encrypt(
        {
            name: "RSA-OAEP"
        },
        key,
        plaintext
    );
    return encrypted;
}

function getSpkiDer(spkiPem){
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    var pemContents = spkiPem.substring(pemHeader.length, spkiPem.length - pemFooter.length);
    var binaryDerString = window.atob(pemContents);
    return str2ab(binaryDerString); 
}

//
// Helper
//

// https://stackoverflow.com/a/11058858
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 ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

与之对应的是 PKCS#8 格式的私有(private) PEM 编码 key 的导入和密文的解密:

// PEM encoded PKCS#8 key
const privateKey = 
`-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC6cXloNrocJ8sw
wj8xktPmEOTfTgJT7KkUwWIGOjBB1QxApgdn5+SUHsakvEJq3Fgn+FnuuN8cfcqW
rbT9jURtgNGinJNJ+StPM/PCxfhSv+XbkK11CV2EcJMyDB/8S/9u4E2ht/N1kT4O
F2/mVDAq2MjjeUMq8CLmQR63ZMXB8lwmsGJEl4Rwt9WBZNcZFCfuCeBYrKRS7mtL
zd4BTXEf0UuiNB/KJrz38TKSI47v/dRbB34wBNn0cuNLHb8t/eDaOvzV6J8SZgOW
uL85mng6Fm77QGjUteWgJN76+YhDZgJfsRq1Q67JAy3ZXDHi5X538DcM/o+0wYEq
kXxK3iIbAgMBAAECggEASlJj0ExIomKmmBhG8q8SM1s2sWG6gdQMjs6MEeluRT/1
c2v79cq2Dum5y/+UBl8x8TUKPKSLpCLs+GXkiVKgHXrFlqoN+OYQArG2EUWzuODw
czdYPhhupBXwR3oX4g41k/BsYfQfZBVzBFEJdWrIDLyAUFWNlfdGIj2BTiAoySfy
qmamvmW8bsvc8coiGlZ28UC85/Xqx9wOzjeGoRkCH7PcTMlc9F7SxSthwX/k1VBX
mNOHa+HzGOgO/W3k1LDqJbq2wKjZTW3iVEg2VodjxgBLMm0MueSGoI6IuaZSPMyF
EM3gGvC2+cDBI2SL/amhiTUa/VDlTVw/IKbSuar9uQKBgQDd76M0Po5Lqh8ZhQ3o
bhFqkfO5EBXy7HUL15cw51kVtwF6Gf/J2HNHjwsg9Nb0eJETTS6bbuVd9bn884Jo
RS986nVTFNZ4dnjEgKjjQ8GjfzdkpbUxsRLWiIxuOQSpIUZGdMi2ctTTtspvMsDs
jRRYdYIQCe/SDsdHGT3vcUCybwKBgQDXDz6iVnY84Fh5iDDVrQOR4lYoxCL/ikCD
JjC6y1mjR0eVFdBPQ4j1dDSPU9lahBLby0VyagQCDp/kxQOl0z2zBLRI4I8jUtz9
/9KW6ze7U7dQJ7OTfumd5I97OyQOG9XZwKUkRgfyb/PAMBSUSLgosi38f+OC3IN3
qlvHFzvxFQKBgQCITpUDEmSczih5qQGIvolN1cRF5j5Ey7t7gXbnXz+Umah7kJpM
IvdyfMVOAXJABgi8PQwiBLM0ySXo2LpARjXLV8ilNUggBktYDNktc8DrJMgltaya
j3HNd2IglD5rjfc2cKWRgOd7/GlKcHaTEnbreYhfR2sWrWLxJOyoMfuVWwKBgFal
CbMV6qU0LfEo8aPlBN8ttVDPVNpntP4h0NgxPXgPK8Pg+gA1UWSy4MouGg/hzkdH
aj9ifyLlCX598a5JoT4S0x/ZeVHd/LNI8mtjcRzD6cMde7gdFbpLb5NSjIAyrsIA
X4hxvpnqiOYRePkVIz0iLGziiaMbfMwlkrxvm/LRAoGBALPRbtSbE2pPgvOHKHTG
Pr7gKbmsWVbOcQA8rG801T38W/UPe1XtynMEjzzQ29OaVeQwvUN9+DxFXJ6Yvwj6
ih4Wdq109i7Oo1fDnMczOQN9DKch2eNAHrNSOMyLDCBm++wbyHAsS2T0VO8+gzLA
BviZm5AFCQWfke4LZo5mOS10
-----END PRIVATE KEY-----`;

importPrivateKeyAndDecrypt();
    
async function importPrivateKeyAndDecrypt() {

    // A ciphertext produced with the first code
    const ciphertextB64 = "q/g0YQ+CbFwCb9QxAeKk/X8vjUUKpBGCVe6OvFoBlTfRF24BQlWpLFhxVQv+Gn29CzAXfSJjU+C8taYXQ4wofyOaRx0etkATDbmIV1gVdxNnqVKTx2RSj1L3uACZ3aWYIGRjtaBMBNAW81mPEjxEWCvRW3uI/rOn3LAc4N05CkofOnsIpaafgcEjhZoTxp1Dpkm328bwRJ3g1Dn+vQk6JBiAXSiF7GHvMvnD6q+CQiO1dcv0lrrXlibE8/P2LHWpqQ9g5xWWUHl70q2WB+IxLgX9OkqX8XQ1GHjP5EaQFfo1HerBpa+Uf5DaienI/XT4n64DWM1S7t0dbhFDskc9HQ==";
        
    try {
        const priv = await importPrivateKey(privateKey);
        const decrypted = await decryptRSA(priv, str2ab(window.atob(ciphertextB64)));
        console.log(decrypted);
    } catch(error) {
        console.log(error);
    }
}

async function importPrivateKey(pkcs8Pem) {     
    return await window.crypto.subtle.importKey(
        "pkcs8",
        getPkcs8Der(pkcs8Pem),
        {
            name: "RSA-OAEP",
            hash: "SHA-256",
        },
        true,
        ["decrypt"]
    );
}

async function decryptRSA(key, ciphertext) {
    let decrypted = await window.crypto.subtle.decrypt(
        {
            name: "RSA-OAEP"
        },
        key,
        ciphertext
    );
    return new TextDecoder().decode(decrypted);
}

function getPkcs8Der(pkcs8Pem){
    const pemHeader = "-----BEGIN PRIVATE KEY-----";
    const pemFooter = "-----END PRIVATE KEY-----";
    var pemContents = pkcs8Pem.substring(pemHeader.length, pkcs8Pem.length - pemFooter.length);
    var binaryDerString = window.atob(pemContents);
    return str2ab(binaryDerString); 
}

//
// Helper
//
    
// https://stackoverflow.com/a/11058858
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 ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint8Array(buf));
}

请注意,您的公钥在 SSH public key format 中指定。 , importKey 无法处理因此必须首先转换为 X.509 格式,例如与 ssh-keygen .

关于javascript - 使用原生 javascript/sealCrypto 使用 RSA 进行加密,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62948516/

相关文章:

javascript - 在 JQXgrid 中设置列​​的可编辑属性

javascript - 将鼠标悬停在 SVG 路径上时,如何使 div 出现?

ios - 如何使用 EncryptedCoreData 快速加密?

android - java.io.IOException : Unexpected crypto version 0 异常

javascript(ajax) 和 php 一起工作吗?

javascript - Annyang in React - 最佳实践

email - 在数据库中存储用户电子邮件地址的最佳和最安全的方法是什么?

encryption - 为什么在对对称算法的暴力攻击中,尝试一半后有 50% 的机会找到 key ?

encryption - AES 中的 key 验证

javascript - 无法从 aws lambda (java) 读取 json 正文响应。它说可读流