Как загрузить открытый ключ в формате PEM для шифрования?
до сих пор я использовал JSEncrypt, который может загружать открытый ключ из строки в формате PEM. А затем используйте его с RSA для шифрования строки. Например :
<textarea id="pubkey">-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+iOltdDtDdUq6u67L2Mb4HW5j
7E1scmYtg2mnnQD85LxFICZv3I3rQ4wMulfcH+n9VCrifdu4vN89lRLKgsb9Kzim
GUrbOWEZdKZ9D5Sfo90EXocM5NtHou14aN8xkRWbN7x/RK5o9jfJwKmrC1fCm6tx
2Qwvx5kypWQUN6UpCQIDAQAB
-----END PUBLIC KEY-----
</textarea>
и затем:
var encrypt = new JSEncrypt();
encrypt.setPublicKey($('#pubkey').val());
Я хотел бы сделать то же самое с WebCrypto, но я не понимаю, как это сделать. Я пробовал следующие шаги:
- удалить заголовок PEM
- снимите нижний колонтитул PEM
- удалить CR / LF
- обрезать строка
- декодировать строку Base64
- преобразовать результат в ArrayBuffer
затем я попытался импортировать ключ:
cryptoSubtle.importKey("spki", publicKey, {name: "RSA-OAEP", hash: {name: "SHA-256"}}, false, ["encrypt"]);
Я пробовал много способов (распаковать формат ASN / DER и т. д.) Но я получаю различные ошибки (данные DOMException и т. д.). Я не знаю, является ли формат PEM приемлемым в качестве поддерживаемого формата или я должен преобразовать ключ в формат веб-ключа JSON и т. д.
есть ли простой способ сделать это без сторонней библиотеки JS ?
2 ответов
Я нашел ответ после некоторых тестов. В моем случае я использовал JSEncrypt с PHP/openssl или phpseclib в качестве резервного.
С помощью JSEncrypt вы не можете выбрать алгоритм шифрования. И это оказывает влияние на обивка используется, когда PHP расшифровывает зашифрованное значение. JSEncrypt использует:
- RSASSA-PKCS1-v1_5
- SHA-1 как метод хэша
если вы хотите расшифровать сообщение, вы должны использовать отступы по умолчанию вариант:
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_PADDING);
но WebCrypto не совместим с JSEncrypt (мы не можем расшифровать сообщение с PHP с теми же параметрами), потому что:
- WebCrypto может использовать SHA-1 в качестве хэш-метода, даже если это не рекомендуется.
- но WebCrypto запрещает вам использовать RSASSA-PKCS1-v1_5 для целей шифрования (это разрешено только для целей подписания). Вместо этого вы должны использовать RSA-OAEP.
если вы пытаетесь декодировать зашифрованное значение с помощью параметры по умолчанию, вы получите это сообщение:
RSA_EAY_PRIVATE_DECRYPT:padding check failed
Итак, вы должны изменить опцию заполнения следующим образом (в PHP):
openssl_private_decrypt(base64_decode($_POST['CipheredValue']), $ouput, $privateKey, OPENSSL_PKCS1_OAEP_PADDING);
что касается моего первоначального вопроса, да, вы можете импортировать ключ в формате PEM, если выполните шаги, которые я упомянул в сообщении
- удалить заголовок PEM
- удалить thePEM футер
- удалить CR / LF
- обрезать строку
- декодировать в base64 строка
- преобразовать результат в ArrayBuffer
полный код:
var crypto = window.crypto || window.msCrypto;
var encryptAlgorithm = {
name: "RSA-OAEP",
hash: {
name: "SHA-1"
}
};
function arrayBufferToBase64String(arrayBuffer) {
var byteArray = new Uint8Array(arrayBuffer)
var byteString = '';
for (var i=0; i<byteArray.byteLength; i++) {
byteString += String.fromCharCode(byteArray[i]);
}
return btoa(byteString);
}
function base64StringToArrayBuffer(b64str) {
var byteStr = atob(b64str);
var bytes = new Uint8Array(byteStr.length);
for (var i = 0; i < byteStr.length; i++) {
bytes[i] = byteStr.charCodeAt(i);
}
return bytes.buffer;
}
function textToArrayBuffer(str) {
var buf = unescape(encodeURIComponent(str)); // 2 bytes for each char
var bufView = new Uint8Array(buf.length);
for (var i=0; i < buf.length; i++) {
bufView[i] = buf.charCodeAt(i);
}
return bufView;
}
function convertPemToBinary(pem) {
var lines = pem.split('\n');
var encoded = '';
for(var i = 0;i < lines.length;i++){
if (lines[i].trim().length > 0 &&
lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 &&
lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 &&
lines[i].indexOf('-BEGIN PUBLIC KEY-') < 0 &&
lines[i].indexOf('-END PUBLIC KEY-') < 0 &&
lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 &&
lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) {
encoded += lines[i].trim();
}
}
return base64StringToArrayBuffer(encoded);
}
function importPublicKey(pemKey) {
return new Promise(function(resolve) {
var importer = crypto.subtle.importKey("spki", convertPemToBinary(pemKey), encryptAlgorithm, false, ["encrypt"]);
importer.then(function(key) {
resolve(key);
});
});
}
if (crypto.subtle) {
start = new Date().getTime();
importPublicKey($('#pubkey').val()).then(function(key) {
crypto.subtle.encrypt(encryptAlgorithm, key, textToArrayBuffer($('#txtClear').val())).then(function(cipheredData) {
cipheredValue = arrayBufferToBase64String(cipheredData);
console.log(cipheredValue);
});
});
}
Сначала выберите свой любимый Base64-to-ArrayBuffer и String-to-ArrayBuffer методы, например,
function b64ToArrayBuffer(b64) {
return new Promise((res, rej) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'data:application/octet-stream;base64,' + b64);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', e => res(xhr.response));
xhr.addEventListener('error', e => rej(xhr));
xhr.send();
});
}
function stringToArrayBuffer(str) {
return new Promise((res, rej) => {
var xhr = new XMLHttpRequest();
xhr.open('GET', 'data:text/plain,' + str);
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', e => res(xhr.response));
xhr.addEventListener('error', e => rej(xhr));
xhr.send();
});
}
(есть шанс, что это даст Uncaught (in promise) DOMException: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).
)
тогда вы можете написать