-2

I want to protect a RSA private key stored in localStorage by wrapping it with a key derived from the user's password.

However when unwrapping the key the error DOMException: An invalid or illegal string was specified is thrown.

This is a minimal snippet for reproducing the issue:

const base64ToArrayBuffer = (data) => {
 const binaryKey = atob(data);
 const keyBytes = new Uint8Array(binaryKey.length);
 for (let i = 0; i < binaryKey.length; i++) {
 keyBytes[i] = binaryKey.charCodeAt(i);
 }
 return keyBytes.buffer;
}
const bufferToBase64 = (data) => btoa(String.fromCharCode(... new Uint8Array(data)));
const fn = async () => {
// Generate RSA key pair
 const keyPair = await crypto.subtle.generateKey({
 name: "RSA-OAEP",
 modulusLength: 4096,
 publicExponent: new Uint8Array([1, 0, 1]),
 hash: "SHA-512"
 }, true, ["encrypt", "decrypt"]);
// Save private key
// Encrypt the private key
 const textEncoder = new TextEncoder();
 const salt = new Uint8Array(16);
 crypto.getRandomValues(salt);
 const passwordKey = await crypto.subtle.importKey("raw", textEncoder.encode(window.prompt("password")), "PBKDF2", true, ["deriveKey"]);
 const derivedKey = await crypto.subtle.deriveKey({
 name: "PBKDF2",
 hash: "SHA-256",
 salt,
 iterations: 210000
 }, passwordKey, {
 name: "AES-CBC",
 length: 256
 }, true, ["wrapKey", "unwrapKey"]);
 const iv = new Uint8Array(16);
 crypto.getRandomValues(iv);
 const wrappedPrivateKey = await crypto.subtle.wrapKey("pkcs8", keyPair.privateKey, derivedKey, {
 name: "AES-CBC",
 iv
 });
 const b64WrappedPrivateKey = bufferToBase64(wrappedPrivateKey);
 const b64Salt = bufferToBase64(salt);
 const b64IV = bufferToBase64(iv);
 const encryptedPrivateKey = base64ToArrayBuffer(b64WrappedPrivateKey);
 const unwrapSalt = base64ToArrayBuffer(b64Salt);
 const unwrapIV = base64ToArrayBuffer(b64IV);
 const unwrapPasswordKey = await crypto.subtle.importKey("raw", textEncoder.encode(window.prompt("password unwrap")), "PBKDF2", true, ["deriveKey"]);
 const unwrappingKey = await crypto.subtle.deriveKey({
 name: "PBKDF2",
 hash: "SHA-256",
 salt: unwrapSalt,
 iterations: 210000
 }, unwrapPasswordKey, {
 name: "AES-CBC",
 length: 256
 }, true, ["wrapKey", "unwrapKey"]);
 try {
 const privateKey = await crypto.subtle.unwrapKey("pkcs8", encryptedPrivateKey, unwrappingKey, {
 name: "AES-CBC",
 iv: unwrapIV
 }, {
 name: "RSA-OAEP",
 hash: "SHA-512"
 }, true, ["encrypt", "decrypt"]);
 console.log("Success");
 } catch (err) {
 console.log(err);
 }
};
(async () => {
 await fn();
})()

Security is considered up to a certain point because this is just a demo project.

asked May 5, 2025 at 18:34
6
  • 1
    Can you create and provide a minimal reproducible example? Commented May 5, 2025 at 19:30
  • It's the code i posted Commented May 5, 2025 at 20:06
  • 1
    The posted code isn't minimal. Can you create a script without login, reload and storage that I can copy and paste to locally reproduce and debug the problem? It's also not complete. What is resData.id? Can you post the code as Stack Snippet? Commented May 5, 2025 at 20:49
  • I think this should do and resData.id is just the user's ID. Commented May 5, 2025 at 22:42
  • Which call causes the error? Commented May 5, 2025 at 22:57

1 Answer 1

0

After debugging with the Node.js crypto library, I found out that the actual error was Unsupported key usage for an RSA-OAEP key because I was passing the encrypt usage for a RSA private key which is not possible.

answered May 7, 2025 at 11:25
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.