I tried to use SubtleCrypto implementation from this StackOverflow thread: Using native javascript / subtleCrypto to encrypt using RSA
I am using https://travistidwell.com/jsencrypt/demo/ to generate the keys. (only removing "RSA" from the private key as they generate it with this extra label)
Yet, I am unable to make it work bi-directional.
I always end up with "DOMException: Data provided to an operation does not meet requirements" error, but it won't tell WHAT does not meet requirements. I even cannot check the constructor, as window.crypto.subtle.decrypt is not printable, as it is [native code].
Here is the code I used. It is almost 1:1 with the code from the original thread, with only minor changes as follows:
- changed the order of functions for my better orientation in the code
- changed private/public key to ones from the generator
- added input params to importPublicKeyAndEncrypt and importPrivateKeyAndDecrypt function to pass what is being encrypted/decrypted. Also added return statements to those functions so I can work with the output.
- through various iteration, I tried adding hash:"SHA-256" and even length to match the lenght of the generated keys (bits).
- added chainTest function, that should, considering the things will work, encrypt the input, and decrypt it again, returning the same output as the original input.
const publicKey = `-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgbY/2cGDeAsBUtSAbNx9fV3p1V
i8lES4vW576Hafuf4ACUVwYimYF+WMw680f1uSMYFPZiPgG4ifZrQI510Qd60mnZ
hPyXRwdBXRex8T5gZVGtu3farONvYAH9xy182rQCfMhyYJw8WD9s1h0NA0i3yGlK
zOuPrBqmoZ9i45hbAgMBAAE=
-----END PUBLIC KEY-----`;
const privateKey = `-----BEGIN PRIVATE KEY-----
MIICWwIBAAKBgHgbY/2cGDeAsBUtSAbNx9fV3p1Vi8lES4vW576Hafuf4ACUVwYi
mYF+WMw680f1uSMYFPZiPgG4ifZrQI510Qd60mnZhPyXRwdBXRex8T5gZVGtu3fa
rONvYAH9xy182rQCfMhyYJw8WD9s1h0NA0i3yGlKzOuPrBqmoZ9i45hbAgMBAAEC
gYBdSEi8rANS+CvKBsUuI5zW0VB4ufw7cUOLdHnAzMNPnrgHOy7roOvAWzT0ScSx
WvNTglylj1/BTmY4cMxO2MpjCC5MAtwM9QOaU405WatHBgCKiRKNLHCGA2KoQlJ8
let5HLYCUf+1nRtGDVrKVRBpKn2uT9ukL2SCwbo3gz59IQJBALbsjHC35srxL8rT
rnhk9O4j8zJ+uIUIobY/duODP1JOP6+gtjCYPzAivLy6+6an3M2DSjAN34XPCsEk
wrwPof0CQQCoFpwKYznNx+73CCtT4Q9mWQZlr4IzjawRFEDNZrg6ZrfWnbX0wt+M
Wv6M74leqIWr6Q7cknxXZPKSzGG0N2c3AkEAk9EdP+zr/GzgIPfj0fhLELFOUiyi
sSYjf2FTklA5+CmxPxwQfb5ZuW0otR4oQyj8vbntVl2vlbKUTzWEg3HihQJARcul
nZaYMP99FayZuSmx6FC9HEolaVzBfxIG2oN1qiJu4bn5DRpCExjRrBnm05xsbPbI
SgS1huCO7S/avidnPwJAK1RFqYuawbMyl17eHFSnWvXHoUMle0Ywd1RT+dx8EI12
TJZDRWJirUZzTg+mN6Kryh3YkdtgYOvRDckFg43dug==
-----END PRIVATE KEY-----`;
async function chainTest(plaintext){
var d = await importPublicKeyAndEncrypt(plaintext);
console.log("d -> " + d);
var e = await importPrivateKeyAndDecrypt(d);
console.log("e -> " + e);
}
/********** import/export executable functions *******/
async function importPublicKeyAndEncrypt(plaintext) {
try {
const pub = await importPublicKey(publicKey);
const encrypted = await encryptRSA(pub, new TextEncoder().encode(plaintext));
const encryptedBase64 = window.btoa(ab2str(encrypted));
return encryptedBase64;
} catch(error) {
console.log(error);
}
}
async function importPrivateKeyAndDecrypt(ciphertextB64) {
try {
const priv = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(priv, str2ab(window.atob(ciphertextB64)));
return decrypted;
} catch(error) {
console.log(error);
}
}
/******* encrypt/decrypt **********/
async function encryptRSA(key, plaintext) {
let encrypted = await window.crypto.subtle.encrypt(
{
name: "RSA-OAEP",
hash: "SHA-256",
length:1024,
},
key,
plaintext
);
return encrypted;
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: "RSA-OAEP",
hash: "SHA-256",
length:1024,
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
/******** imports *************/
async function importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
"pkcs8",
getPkcs8Der(pkcs8Pem),
{
name: "RSA-OAEP",
hash: "SHA-256",
length:1024,
},
true,
["decrypt"]
);
}
async function importPublicKey(spkiPem) {
return await window.crypto.subtle.importKey(
"spki",
getSpkiDer(spkiPem),
{
name: "RSA-OAEP",
hash: "SHA-256",
length:1024,
},
true,
["encrypt"]
);
}
/******** filters *************/
function getSpkiDer(spkiPem){
const pemHeader = "-----BEGIN PUBLIC KEY-----";
const pemFooter = "-----END PUBLIC KEY-----";
var pemContents = spkiPem.substring(pemHeader.length, spkiPem.length - pemFooter.length);
console.log(pemContents);
var binaryDerString = window.atob(pemContents);
return str2ab(binaryDerString);
}
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);
}
/********* helpers ***********/
function ab2str(buf) {
return String.fromCharCode.apply(null, new Uint8Array(buf));
}
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;
}
The result is, the encrypt part works (I had logged it and it returns a hash, although, I have no back-validation that the encryption actually ran correctly and it has a valid value/output. The decryption part crashes on "DOMException: Data provided to an operation does not meet requirements".
Anyone please does know, what is wrong here?
Thank you!
Closing with the following solution:
The RSA generator does NOT provide compatible keys.
Found some old testing keys from my previous PowerShell implementation, that were generated from OpenSSL, and surprise, it just works flawlessly...