Hamza Sajid
Hamza Sajid

Reputation: 45

AES GCM message authentication failed when decrypt in golang

I have an exisiting solution working good in ts and nextjs using web/crypto library and decrypts all data. Due to some reason I have to perform decryption in golang. For that purpose I wrote following go-code it works fine for some data whereas it doesn't work really well for remaining.

/* eslint-disable @typescript-eslint/ban-ts-comment */
/* eslint-disable @typescript-eslint/no-var-requires */
import { Controller, Get, Res, Param, Req, HttpStatus } from '@nestjs/common';

import { HttpService } from '@nestjs/axios';

import { response, type Response } from 'express';
import { Readable } from 'stream';
import { firstValueFrom, map } from 'rxjs';
import { FileService } from './file.service';
const fs = require('fs');
const crypto = require('crypto').webcrypto;
//import crypto, { subtle } from "crypto";

async function generateSecretKeyForEncryption(
  secreteKeyString: string,
  userSalt: string,
) {
  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(secreteKeyString),
    { name: 'PBKDF2', hash: 'SHA-256' },
    false,
    ['deriveKey'],
  );

  const derivedKey = await crypto.subtle.deriveKey(
    {
      name: 'PBKDF2',
      salt: new TextEncoder().encode(userSalt),
      iterations: 1000,
      hash: 'SHA-256',
    },
    key,
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt'],
  );
  return derivedKey;
}

const fromHexString = (hexString) =>
  Uint8Array.from(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const decryptedSecretKeyAndFile = async (
  data,
  secretKey,
  accessKey,
  iv,
  fileData,
  userSalt,
) => {
  console.log("🚀 ~ file: file.controller.ts:52 ~ iv:", iv)
  const newDataArray = fromHexString(data);
  console.log("🚀 ~ file: file.controller.ts:55 ~ newDataArray:", newDataArray)
  const key = await generateSecretKeyForEncryption(secretKey, userSalt);
  console.log("🚀 ~ file: file.controller.ts:57 ~ key:", key)
  const encryptionKey = await crypto.subtle.decrypt(
    {
      name: 'AES-GCM',
      iv: new TextEncoder().encode(accessKey),
      tagLength: 128,
    },
    key,
    newDataArray,
  );
  console.log("🚀 ~ file: file.controller.ts:65 ~ encryptionKey:", encryptionKey)

  const ecnryptionKeyForFile = await crypto.subtle.importKey(
    'raw',
    new Uint8Array(encryptionKey),
    { name: 'AES-GCM' },
    true,
    ['encrypt', 'decrypt'],
  );
  //console.log("🚀 ~ file: file.controller.ts:74 ~ ecnryptionKeyForFile:", ecnryptionKeyForFile)

  const encrtedData = await crypto.subtle.decrypt(
    { 
      name: 'AES-GCM', 
      iv: new TextEncoder().encode(iv)
    },
    ecnryptionKeyForFile,
    fileData,
  );
  console.log("🚀 ~ file: file.controller.ts:84 ~ fileData:", fileData.length)
  console.log("🚀 ~ file: file.controller.ts:81 ~ encrtedData:", encrtedData)
  return encrtedData;
};
This code decrypts the data very well I tried to write a golang script an exact copy of this.
// Function to decrypt filedata using decrypted key iv and filedata
func decryptor(key, trimiv, data []byte) ([]byte, error) {
    // Create a new AES block cipher with the key
    b, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }
    
    // Create a GCM cipher mode
    aesgcm, err := NewGCMWithNonceSize(b, 32)
    if err != nil {
        return nil, err
    }

    // Decrypt the data
    decryptedData, err := aesgcm.Open(nil, trimiv, data, nil)
    if err != nil {
        return nil, err
    }
    return decryptedData, nil
}

// Function to derive pbkf2 keyfrom secret key and salt
func deriveKey(secretKey string, userSalt string) ([]byte) {
    
    // Derive the key using PBKDF2 with provided salt and other parameters
    derivedKey := pbkdf2.Key([]byte(secretKey), []byte(userSalt), 1000, 32, sha256.New)
    // Return the derived key
    return derivedKey
}

// Function to decrypt key and then decrypt data using AES-GCM
func DecryptedSecretKeyAndFile(data, secretKey, accessKey, iv, userSalt string, fileData []byte) ([]byte, error) {
    
    //Nonce and data to decrypt Master Key
    //nonce/iv to decrypt key
    hexaccessKey, _ := hex.DecodeString(accessKey)
    trimaccessKey := hexaccessKey[:32]

    //data to decrypt key
    hexdata, _ := hex.DecodeString(data)
    
    //Nonce and data to decrypt original data
    //nonce/iv to decrypt data
    hexiv, _ :=hex.DecodeString(iv)
    trimiv := hexiv[:32]
    //fileData contains the original data to be decrypted

    //gcm method
    key:= deriveKey(secretKey, userSalt)

    decryptedKey, err := decryptor(key, trimaccessKey, hexdata)
    if err != nil {
        return nil, err
    }
    fmt.Println("DecryptedKey accessed",decryptedKey)
    //Decrypt the Data
    decryptedData, err := decryptor(decryptedKey, trimiv, fileData)
    if err != nil {
        fmt.Println("Error:", err)
        return nil, err
    }
    fmt.Println("Decrypted Data accessed",len(fileData), len(decryptedData), decryptedData[:32])
    //return Decrypted Data
    return decryptedData, nil
}

This code works well for some data but for some data it throws authentication failure. And when I forked the cipher library of go I found out.
Code is breaking on this check

if subtle.ConstantTimeCompare(expectedTag[:g.tagSize], tag) != 1 {
        // The AESNI code decrypts and authenticates concurrently, and
        // so overwrites dst in the event of a tag mismatch. That
        // behavior is mimicked here in order to be consistent across
        // platforms.
        for i := range out {
            out[i] = 0
        }
        return nil, errOpen
    }

https://go.dev/src/crypto/cipher/gcm.go?s=3702:3764

Here is link to my github repo
https://github.com/Rusted2361/media-file-server-go/tree/server-test

Upvotes: 0

Views: 305

Answers (0)

Related Questions