Firefox trigger a "DOMException: The operation failed for an operation-specific reason" when using 'window.crypto.subtle.decrypt'

107 views Asked by At

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 answers