【问题标题】:CryptoKey ArrayBuffer to base64 and BackCryptoKey ArrayBuffer 到 base64 并返回
【发布时间】:2016-07-30 19:28:04
【问题描述】:

我想知道如何解决这个问题。我使用 WebCrypto API 生成 RSA-OAEP 密钥对,然后从导出为 ArrayBuffer 的密钥对中导出 pkcs8 中的私钥,我想将此 ArrayBuffer 编码为 base64,以便将其存储为 PEM。

在此测试示例中,我将密钥导出为 pkcs8 并将此 pkcs8 导入回 CryptoKey。问题是有时有效,有时无效。

这些是代码的结果: 注意:只发生这些状态之一,而不是同时发生。 注2:本示例不包含 -----BEGIN PRIVATE KEY----- 前缀和后缀,我只是对密钥进行编码。

Case1: Uncaught (in promise) URIError: URI malformed(...)b64DecodeUnicode @ try.php:20b64toab @ try.php:70wayBack @ try.php:66(anonymous function) @ try.php:56

Case2: undefined:1 Uncaught (in promise) DOMException

案例 3:好的 - 一直有效。

我不知道是什么导致了错误,但我认为这与 base64 编码有关。正如我所说,有时私钥会生成 OK,有时则不会。

非常感谢您提前提供的每一个帮助。

function b64EncodeUnicode(str) {
    return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
        return String.fromCharCode('0x' + p1);
    }));
}

function b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
        return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
}

function addNewLines(str) {
    var finalString = '';
    for(var i=0; i < str.length; i++) {
        finalString += str.substring(0, 64) + '\n';
        str = str.substring(64);
    }

     return finalString;
}

window.crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",
        modulusLength: 2048,
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        hash: {name: "SHA-256"}
    },
    true,
    ["encrypt", "decrypt"]
).then(function(keyPair) {
    window.crypto.subtle.exportKey(
        "pkcs8",
        keyPair.privateKey
    ).then(function(exportedPrivateKey) {
        var byteArray = new Uint8Array(exportedPrivateKey);
        console.log(byteArray);
        var byteString = '';
        for(var i=0; i < byteArray.byteLength; i++) {
            byteString += String.fromCodePoint(byteArray[i]);
        }

        wayBack(addNewLines(b64EncodeUnicode(byteString)));
    });
});

function wayBack(pem) {
    var lines = pem.split('\n');
    var encodedString = '';
    for(var i=0; i < lines.length; i++) {
        encodedString += lines[i].trim();
    }
    b64toab(encodedString);
}

function b64toab(b64) {
    var byteString = b64DecodeUnicode(b64);
    console.log(byteString);
    var byteArray = new Uint8Array(byteString.length);
    for(var i=0; i < byteString.length; i++) {
        byteArray[i] = byteString.codePointAt(i);
    }
    console.log(byteArray);

    window.crypto.subtle.importKey(
        "pkcs8",
        byteArray,
        {
            name: "RSA-OAEP",
            hash: {name: "SHA-256"}
        },
        true,
        ["decrypt"]
    ).then(function(importedPrivateKey) {
        console.log(importedPrivateKey);
    });
}

【问题讨论】:

    标签: javascript base64 pem arraybuffer webcrypto-api


    【解决方案1】:

    当您将字符串拆分为 64 个字符的块时,您忘记包含 PEM 的最后一部分。只需将finalString += str; 添加到addNewLines

    function addNewLines(str) {
        var finalString = '';
        for(var i=0; i < str.length; i++) {
            finalString += str.substring(0, 64) + '\n';
            str = str.substring(64);
        }
        finalString += str;
    
        return finalString;
    }
    

    我已经重构了您的示例以查看发生了什么。如果您认为它有用,请使用以下代码

    function b64EncodeUnicode(str) {
        return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
            return String.fromCharCode('0x' + p1);
        }));
    }
    
    function b64DecodeUnicode(str) {
        return decodeURIComponent(Array.prototype.map.call(atob(str), function(c) {
            return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
        }).join(''));
    }
    
    function addNewLines(str) {
        var finalString = '';
        for(var i=0; i < str.length; i++) {
            finalString += str.substring(0, 64) + '\n';
            str = str.substring(64);
        }
        finalString += str;
    
        return finalString;
    }
    
    function removeLines(pem) {
        var lines = pem.split('\n');
        var encodedString = '';
        for(var i=0; i < lines.length; i++) {
            encodedString += lines[i].trim();
        }
        return encodedString;
    }
    
    function stringToArrayBuffer(byteString){
        var byteArray = new Uint8Array(byteString.length);
        for(var i=0; i < byteString.length; i++) {
            byteArray[i] = byteString.codePointAt(i);
        }
        return byteArray;
    }
    
    function  arrayBufferToString(exportedPrivateKey){
        var byteArray = new Uint8Array(exportedPrivateKey);
        var byteString = '';
        for(var i=0; i < byteArray.byteLength; i++) {
            byteString += String.fromCodePoint(byteArray[i]);
        }
        return byteString;
    }
    
    window.crypto.subtle.generateKey(
        {
            name: "RSA-OAEP",
            modulusLength: 2048,
            publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
            hash: {name: "SHA-256"}
        },
        true,
        ["encrypt", "decrypt"]
    ).then(function(keyPair) {
        window.crypto.subtle.exportKey(
            "pkcs8",
            keyPair.privateKey
        ).then(function(exportedPrivateKey) {
    
            var privateKeyDer = arrayBufferToString(exportedPrivateKey); //pkcs#8 to DER
            var privateKeyB64 = b64EncodeUnicode(privateKeyDer); //btoa(privateKeyDer);
            var privateKeyPEMwithLines = addNewLines(privateKeyB64);  //split PEM into 64 character strings
            var privateKeyPEMwithoutLines = removeLines(privateKeyPEMwithLines);  //join PEM
            var privateKeyDerDecoded = b64DecodeUnicode(privateKeyPEMwithoutLines);  // atob(privateKeyB64);
            var privateKeyArrayBuffer = stringToArrayBuffer(privateKeyDerDecoded);  //DER to arrayBuffer
            window.crypto.subtle.importKey(  //importKEy
                "pkcs8",
                privateKeyArrayBuffer,
                {
                    name: "RSA-OAEP",
                    hash: {name: "SHA-256"}
                },
                true,
                ["decrypt"]
            ).then(function(importedPrivateKey) {
                console.log(importedPrivateKey);
            });
        });
    });
    

    【讨论】:

    • 嗨佩德罗,非常感谢你再次帮助我!
    • Pedro 我想知道您是否可以查看我的代码,我实际上在您发布答案之前使其工作,但我不确定解决方案是否正确。我没有包含 encodeUnicode 函数,只使用了 window.btoa 和 window.atob,我会将代码作为附加答案发布。我刷新了页面 50 次,它的工作效率是 100%
    • 因为我不确定它是否会一直适用于所有可能生成的私钥。如果 .fromCharCode 和 .charCodeAt 将捕获所有内容,或者如果我必须使用 .fromCodePoint 和 .codePointAt 并且如果 unicode 编码是一个问题,但据我所知,只有当你想显示它的字符时才需要这个 unicode 修复如果我只是不可见地对其进行编码,则没有任何效果。
    【解决方案2】:

    我正在发布额外的工作代码: (注意:代码没有 -----BEGIN PRIVATE KEY----- 和 ----- END PRIVATE KEY ----- base64 only)

    function addNewLines(str) {
        var finalString = '';
        while(str.length > 0) {
            finalString += str.substring(0, 64) + '\n';
            str = str.substring(64);
        }
    
        return finalString;
    }
    
    function removeLines(str) {
        return str.replace("\n", "");
    }
    
    function arrayBufferToBase64(arrayBuffer) {
        var byteArray = new Uint8Array(arrayBuffer);
        var byteString = '';
        for(var i=0; i < byteArray.byteLength; i++) {
            byteString += String.fromCharCode(byteArray[i]);
        }
        var b64 = window.btoa(byteString);
    
        return b64;
    }
    
    function base64ToArrayBuffer(b64) {
        var byteString = window.atob(b64);
        var byteArray = new Uint8Array(byteString.length);
        for(var i=0; i < byteString.length; i++) {
            byteArray[i] = byteString.charCodeAt(i);
        }
    
        return byteArray;
    }
    
    window.crypto.subtle.generateKey(
        {
            name: "RSA-OAEP",
            modulusLength: 2048,
            publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
            hash: {name: "SHA-256"}
        },
        true,
        ["encrypt", "decrypt"]
    ).then(function(keyPair) {
        window.crypto.subtle.exportKey(
            "pkcs8",
            keyPair.privateKey
        ).then(function(exportedPrivateKey) {
            var pem = addNewLines(arrayBufferToBase64(exportedPrivateKey));
            importKey(pem);
        });
    });
    
    function importKey(b64) {
        b64 = removeLines(b64);
        arrayBuffer = base64ToArrayBuffer(b64);
    
        window.crypto.subtle.importKey(
            "pkcs8",
            arrayBuffer,
            {
                name: "RSA-OAEP",
                hash: {name: "SHA-256"}
            },
            true,
            ["decrypt"]
        ).then(function(importedPrivateKey) {
            console.log(importedPrivateKey);
        });
    }
    

    更新: 我写了一个小的加密库,你可以用它来进行 PEM 转换等等。 https://github.com/PeterBielak/OpenCrypto

    使用示例:

    var crypt = new OpenCrypto();
    
    crypt.getKeyPair().then(function(keyPair) {
        crypt.cryptoPrivateToPem(keyPair.privateKey).then(function(pemPrivateKey) {
            console.log(pemPrivateKey);
        });
    });
    

    【讨论】:

    • 嗨彼得,这段代码更简单,所以通常更好。要生成有效的 PEM,您必须使用 base64(不替换字符)。 .atob().btoa() 工作正常,但在较旧的 IE 版本中除外。我通常使用.fromCharCode() 和.charCodeAt()。真的我不知道 .fromCodePoint 和 .codePointAt 的区别
    • 佩德罗感谢您的评论!我将使用 .fromCharCode() 和 .charCodeAt() 函数。 stackoverflow.com/questions/34680898/…
    猜你喜欢
    • 1970-01-01
    • 2012-03-05
    • 2011-09-23
    • 2020-02-19
    • 2022-01-14
    • 2016-03-09
    • 1970-01-01
    • 2015-01-30
    • 2018-10-16
    相关资源
    最近更新 更多