CryptoKey ArrayBuffer для base64 и обратно

Мне было интересно, как мне решить эту проблему. Я генерирую пару ключей RSA-OAEP с помощью API-интерфейса WebCrypto, затем экспортирую закрытый ключ в pkcs8 из пары ключей, которая экспортируется как ArrayBuffer, и я хочу закодировать этот ArrayBuffer в base64, чтобы я мог сохранить его как PEM.

В этом примере тестирования я экспортирую ключ как pkcs8 и импортирую этот pkcs8 обратно в CryptoKey. Проблема в том, что иногда это работает, а иногда нет.

Это результаты кода: ПРИМЕЧАНИЕ. Только одно из этих состояний происходит не сразу. ПРИМЕЧАНИЕ 2. Этот пример не содержит ----- BEGIN PRIVATE KEY ----- префикс и суффикс. Я только кодирую ключ.

Case1: Uncaught (в обещании) URIError: URI неверно сформирован (...) b64DecodeUnicode @ try.php: 20b64toab @ try.php: 70wayBack @ try.php: 66 (анонимная функция) @ try.php: 56

Case2: undefined: 1 Uncaught (в обещании) DOMException

Case3: ОК - работает до конца.

Я не знаю, что вызывает ошибки, но я думаю, что это как-то связано с кодировкой base64. Как я сказал, иногда закрытый ключ генерирует нормально, а иногда нет.

Большое спасибо за каждую помощь заранее.

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);
    });
}

Ответы на вопрос(2)

Решение Вопроса

Вы забыли включить последнюю часть PEM, когда разбили строку на блоки по 64 символа. Просто добавь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);
        });
    });
});
 Peter Bielak02 авг. 2016 г., 13:16
потому что я не уверен, будет ли он работать постоянно для каждого возможного закрытого ключа, который когда-либо может быть сгенерирован. если .fromCharCode и .charCodeAt перехватят все или если мне придется использовать .fromCodePoint и .codePointAt, а также если проблема заключается в кодировке Юникода, но, насколько я знаю, это исправление Юникода необходимо, только если вы хотите отобразить символы, которые оно делает не имеет никакого эффекта, если я просто незаметно кодирую его.
 Peter Bielak02 авг. 2016 г., 13:01
Привет Педро, большое спасибо за помощь мне еще раз!
 Peter Bielak02 авг. 2016 г., 13:06
Педро Мне было интересно, если бы вы могли просмотреть мой код, я действительно заставил его работать до того, как вы отправили ответ, но я не уверен, что решение верное. Я не включил функции encodeUnicode и просто использовал window.btoa & window.atob, я выложу код в качестве дополнительного ответа. Я обновил страницу примерно 50 раз, и она заработала на 100%.

Я публикую дополнительный рабочий код: (ПРИМЕЧАНИЕ: код без ----- BEGIN PRIVATE KEY ----- и ----- END PRIVATE KEY ----- только base64)

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);
    });
});
 Peter Bielak02 авг. 2016 г., 14:52
Педро, спасибо за ваш отзыв! Я пойду с функциями .fromCharCode () и .charCodeAt ().stackoverflow.com/questions/34680898/...
 pedrofb02 авг. 2016 г., 15:07
Разъяснил разницу, спасибо
 pedrofb02 авг. 2016 г., 13:59
Привет Питер, этот код проще, поэтому обычно лучше. Для генерации действительного PEM вы должны использовать base64 (без замены символов)..atob()  а также.btoa() отлично работает, кроме старых версий IE. Я обычно использую.fromCharCode()  а также .charCodeAt(), На самом деле я не знаю разницу с .fromCodePoint и .codePointAt

Ваш ответ на вопрос