So I am trying to implement some methods to encrypt then decrypt some data. I don't have any experience with this, and I've tried to follow along with some posts online on how to go about this.
When i pass the encrypted 'hello' into the decrypt function, i get this:
let a = importPublicKeyAndEncrypt('hello')
CryptoKey {type: "public", extractable: true, algorithm: {…}, usages: Array(1)} W29iamVjdCBBcnJheUJ1ZmZlcl0=
importPrivateKeyAndDecrypt(a)
Promise {<pending>}
DOMException: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.
at importPrivateKeyAndDecrypt (<anonymous>:26:60)
The decrypt function works correctly when i use an encrypted messages from one of the posts i saw for PKCS#8 but not when i generate my own keys.
Here is the code What am i doing wrong?
// PEM encoded X.509 key
const publicKey = `
-----BEGIN PUBLIC KEY-----
<removed for space>
-----END PUBLIC KEY-----`;
// PEM encoded PKCS#8 key
const privateKey = `
-----BEGIN PRIVATE KEY-----
<removed for space>
-----END PRIVATE KEY-----`;
async function importPublicKeyAndEncrypt(str) {
try {
const pub = await importPublicKey(publicKey);
console.log(pub);
const encrypted = await encryptRSA(pub, new TextEncoder().encode(str));
const encryptedBase64 = window.btoa(ab2str(encrypted));
console.log(encryptedBase64.replace(/(.{64})/g, '$1\n'));
} catch (error) {
console.log(error);
}
}
async function importPrivateKeyAndDecrypt(str) {
try {
const priv = await importPrivateKey(privateKey);
const decrypted = await decryptRSA(priv, str2ab(window.atob(str)));
console.log(decrypted);
} 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 importPrivateKey(pkcs8Pem) {
return await window.crypto.subtle.importKey(
'pkcs8',
getPkcs8DerDecode(pkcs8Pem),
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['decrypt']
);
}
async function encryptRSA(key, plaintext) {
let encrypted = await window.crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
key,
plaintext
);
return encrypted;
}
async function decryptRSA(key, ciphertext) {
let decrypted = await window.crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
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);
}
function getPkcs8DerDecode(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);
}
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));
}
There are only two minor flaws.
First, the
return
statements are missing inimportPublicKeyAndEncrypt()
and inimportPrivateKeyAndDecrypt()
(although the latter is not necessary for the current code snippet).Also, it is necessary to wait for the promise of
importPublicKeyAndEncrypt()
beforeimportPrivateKeyAndDecrypt()
can be called.With these fixes the code works: