Search code examples
javascriptencryptionaes-gcmwebcrypto-apinode-crypto

WebCrypto API: DOMException: The provided data is too small


I want to decrypt a message on the client-side(react.js) using Web Crypto API which is encrypted on the back-end(node.js), however I ran into a weird problem and don't have any idea what is wrong(I also checked this)

node.js

function encrypt(message){
    const KEY = crypto.randomBytes(32)
    const IV = crypto.randomBytes(16)
    const ALGORITHM = 'aes-256-gcm';
    const cipher = crypto.createCipheriv(ALGORITHM, KEY, IV);
    let encrypted = cipher.update(message, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    const tag = cipher.getAuthTag()
    let output = {
        encrypted,
        KEY: KEY.toString('hex'),
        IV: KEY.toString('hex'),
        TAG: tag.toString('hex'),
     }
    return output;
}

react.js

function decrypt() {
let KEY = hexStringToArrayBuffer(data.KEY);
let IV = hexStringToArrayBuffer(data.IV);
let encrypted = hexStringToArrayBuffer(data.encrypted);
let TAG = hexStringToArrayBuffer(data.TAG);

window.crypto.subtle.importKey('raw', KEY, 'AES-GCM', true, ['decrypt']).then((importedKey)=>{
  window.crypto.subtle.decrypt(
    {
      name: "AES-GCM",
      iv: IV,
    },
    importedKey,
    encrypted
  ).then((plaintext)=>{
    console.log('plainText: ', plaintext);
  })
})

function hexStringToArrayBuffer(hexString) {
  hexString = hexString.replace(/^0x/, '');
  if (hexString.length % 2 != 0) {
    console.log('WARNING: expecting an even number of characters in the hexString');
  }
  var bad = hexString.match(/[G-Z\s]/i);
  if (bad) {
    console.log('WARNING: found non-hex characters', bad);    
  }
  var pairs = hexString.match(/[\dA-F]{2}/gi);
  var integers = pairs.map(function(s) {
    return parseInt(s, 16);
  });
  var array = new Uint8Array(integers);
  return array.buffer;
} 

Encryption in back-end is done without any error, however when want to decrypt the message on the client-side, the browser(chrome) gives this error: DOMException: The provided data is too small and when I run the program on firefox browser it gives me this error: DOMException: The operation failed for an operation-specific reason. It's so unclear!!

By the way what's the usage of athentication tag in AES-GCM is it necessary for decryption on the client-side?


Solution

  • GCM is authenticated encryption. The authentication tag is required for decryption. It is used to check the authenticity of the ciphertext and only when this is confirmed decryption is performed.
    Since the tag is not applied in your WebCrypto Code, authentication and therefore decryption fail.

    WebCrypto expects that the tag is appended to the ciphertext: ciphertext | tag.

    The data in the code below was created using your NodeJS code (please note that there is a bug in the NodeJS code: instead of the IV, the key is stored in output):

    decrypt();
     
    function decrypt() {
    
        let KEY = hexStringToArrayBuffer('684aa9b1bb4630f802c5c0dd1428403a2224c98126c1892bec0de00b65cc42ba');
        let IV = hexStringToArrayBuffer('775a446e052b185c05716dd1955343bb');
        let encryptedHex = 'a196a7426a9b1ee64c2258c1575702cf66999a9c42290a77ab2ff30037e5901243170fd19c0092eed4f1f8';
        let TAGHex = '14c03526e18502e4c963f6055ec1e9c0';
        let encrypted = hexStringToArrayBuffer(encryptedHex + TAGHex)
    
        window.crypto.subtle.importKey(
            'raw', 
            KEY,
            'AES-GCM', 
            true, 
            ['decrypt']
        ).then((importedKey)=> {
            window.crypto.subtle.decrypt(
                {
                    name: "AES-GCM",
                    iv: IV,
                },
                importedKey,
                encrypted
            ).then((plaintext)=>{
                console.log('plainText: ', ab2str(plaintext));
            });
        });
    }
    
    function hexStringToArrayBuffer(hexString) {
        hexString = hexString.replace(/^0x/, '');
        if (hexString.length % 2 != 0) {
            console.log('WARNING: expecting an even number of characters in the hexString');
        }
        var bad = hexString.match(/[G-Z\s]/i);
        if (bad) {
            console.log('WARNING: found non-hex characters', bad);    
        }
        var pairs = hexString.match(/[\dA-F]{2}/gi);
        var integers = pairs.map(function(s) {
            return parseInt(s, 16);
        });
        var array = new Uint8Array(integers);
        return array.buffer;
    } 
    
    function ab2str(buf) {
        return String.fromCharCode.apply(null, new Uint8Array(buf));
    }