Here is the minimum code needed to replicate the issue, the error I'm getting is generic and not descriptive at all DOMException: The operation failed for an operation-specific reason in short it failed because it failed.

This is the code to run the test:

    encryptTextWithAES("Hello world!", "password1")
        .then((encryptedText) => {
            console.log (encryptedText);

            decryptTextWithAES(encryptedText, "password1")
                .then((decryptedText) => {
                    console.log(decryptedText);
                });
        });  

This is the code that performs the procedure:

  // Function to derive a key from a password and salt using PBKDF2
  async function deriveKeyFromPassword(password, salt) {
    const encoder = new TextEncoder();
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      encoder.encode(password),
      { name: 'PBKDF2' },
      false,
      ['deriveBits', 'deriveKey']
    );

    const derivedKey = await crypto.subtle.deriveKey(
      {
        name: 'PBKDF2',
        salt: encoder.encode(salt),
        iterations: 100000, //todo: is size ok??
        hash: 'SHA-256'
      },
      keyMaterial,
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );

    return derivedKey;
  }

  // Function to convert an ArrayBuffer to a Base64 string
  function arrayBufferToBase64(arrayBuffer) {
    const uint8Array = new Uint8Array(arrayBuffer);
    const byteArray = Array.from(uint8Array);
    return btoa(byteArray.map(byte => String.fromCharCode(byte)).join(''));
  }

  // Function to convert a Base64 string to an ArrayBuffer
  function base64ToArrayBuffer(base64String) {
    const binaryString = atob(base64String);
    const byteArray = new Uint8Array(binaryString.length);

    for (let i = 0; i < binaryString.length; i++) {
      byteArray[i] = binaryString.charCodeAt(i);
    }

    return byteArray.buffer;
  }
  

  // Function to encrypt text using AES-GCM with a password
  async function encryptTextWithAES(text, password) {
    // Generate a random salt
    const salt = window.crypto.getRandomValues(new Uint8Array(16)); //todo: is size ok??

    // Derive a key from the password and salt using PBKDF2
    const key = await deriveKeyFromPassword(password, salt);

    const iv = window.crypto.getRandomValues(new Uint8Array(12));
    const encoder = new TextEncoder();
    const data = encoder.encode(text);

    const encryptedData = await window.crypto.subtle.encrypt(
      { name: 'AES-GCM', iv },
      key,
      data
    );

    const encryptedText = `${arrayBufferToBase64(encryptedData)}:${arrayBufferToBase64(iv)}:${arrayBufferToBase64(salt)}`;
    return encryptedText;
  }

  // Function to decrypt text using AES-GCM with a password
  async function decryptTextWithAES(encryptedText, password) {
    const [encryptedDataBase64, ivBase64, saltBase64] = encryptedText.split(':');
    const encryptedData = base64ToArrayBuffer(encryptedDataBase64);
    const iv = base64ToArrayBuffer(ivBase64);
    const salt = base64ToArrayBuffer(saltBase64);

    // Derive a key from the password and salt using PBKDF2
    const key = await deriveKeyFromPassword(password, salt);

    const decryptedData = await window.crypto.subtle.decrypt(
      { name: 'AES-GCM', iv },
      key,
      encryptedData
    )

    const decoder = new TextDecoder();
    const decryptedText = decoder.decode(decryptedData);
    return decryptedText;
  }

I tested this also on different tools, thinking the problem was my environment (low memory??) and I always get the same outcome. It is those kind of bugs that require a new pair of eyes to pin-point what is wrong. In another answer someon suggested to enable a feature on Firefox, but this is not an acceptable solution since I cannot ask the users to do so, would be overly complicated to explain and not user friendly.

0

There are 0 best solutions below