Converting Javascript to Golang code discrepancy

I’m using CyberSource as a payment processor and I want to convert the JS hashing code to Golang. I attempted to do it but the output of the Go is not matching that of the JS. Where am I messing up? The Golang code runs and spits out a long string about the same length as the JS, but not the same.

Correct JS

"use strict";
import crypto from 'crypto';


export const CardTypes = {
  Visa: '001',
  MasterCard: '002',
  AmericanExpress: '003',
  Discover: '004',
  Diners: '005',
  JCB: '007',
  Maestro: '042',
  ChinaUnionPay: '062',
};


export const generateKey = (crypto) => crypto.subtle.generateKey({
  name: 'AES-GCM',
  length: 256
}, true, ['encrypt']);


export const _encrypt = async (crypto, payload, key, header, iv) => {
  const algorithm = {
      name: 'AES-GCM',
      iv,
      additionalData: stringToArrayBuffer(replace(JSON.stringify(header))),
      tagLength: 128
  };
  const buffer = await crypto.subtle.encrypt(algorithm, key, stringToArrayBuffer(JSON.stringify(payload)));
  return [buffer, key];
};


export const importKey = (crypto, jsonWebKey) => crypto.subtle.importKey('jwk', jsonWebKey, {
  name: 'RSA-OAEP',
  hash: {
      name: 'SHA-1'
  }
}, false, ['wrapKey']);


export const wrapKey = async (crypto, key, jsonWebKey) => {
  const wrappingKey = await importKey(crypto, jsonWebKey);
  const params = {
      name: 'RSA-OAEP',
      hash: {
          name: 'SHA-1'
      }
  };
  return crypto.subtle.wrapKey('raw', key, wrappingKey, params);
};


export const buildEncryptedData = async (crypto, buffer, key, iv, header, jsonWebKey) => {
  const u = buffer.byteLength - ((128 + 7) >> 3);
  const keyBuffer = await wrapKey(crypto, key, jsonWebKey);
  return [
      replace(JSON.stringify(header)),
      replace(arrayBufferToString(keyBuffer)),
      replace(arrayBufferToString(iv)),
      replace(arrayBufferToString(buffer.slice(0, u))),
      replace(arrayBufferToString(buffer.slice(u)))
  ].join('.');
};


export const arrayBufferToString = (buf) => String.fromCharCode.apply(null, new Uint8Array(buf));


export const stringToArrayBuffer = (str) => {
  const buffer = new ArrayBuffer(str.length);
  const array = new Uint8Array(buffer);
  const { length } = str;
  for (let r = 0; r < length; r += 1) {
      array[r] = str.charCodeAt(r);
  }
  return buffer;
};


// Replaced base64 encoding function
export const replace = (str) => btoa(str).replace(/+/g, '-').replace(///g, '_').replace(/=+$/, '');


// JWT decoding function
function decodeJwt(token) {
  try {
      const payload = token.split('.')[1]; // Extract the payload part of the JWT
      const decoded = Buffer.from(payload, 'base64').toString('utf-8'); // Decode base64-encoded string
      return JSON.parse(decoded); // Parse the JSON string
  } catch (error) {
      console.log(error);
      throw new Error('Invalid JWT token');
  }
}


// Encryption function
export const encrypt = async (data, context, index = 0) => {
  const keyId = decodeJwt(context);
  const header = {
      kid: keyId.flx.jwk.kid,
      alg: 'RSA-OAEP',
      enc: 'A256GCM'
  };
  const payload = {
      data,
      context,
      index
  };


  const iv = crypto.randomBytes(12); // Generate a random initialization vector (12 bytes for AES-GCM)


  return generateKey(crypto)
      .then((key) => _encrypt(crypto, payload, key, header, iv))
      .then((data) => {
          const [buffer, key] = data;
          return buildEncryptedData(crypto, buffer, key, iv, header, keyId.flx.jwk);
      });
};



// Example use

const context = "eyJraWQiOiJ3ZiIsImFsZyI6IlJTMjU2In0.eyJmbHgiOnsicGF0aCI6Ii9mbGV4L3YyL3Rva2VucyIsImRhdGEiOiIvUVJ1QjF1dGJVQXd3OXp0ekovZG1oQUFFTlB3VFVZR2dtVTh1eDBiZnNocWpFNmlEU09VNWxJd0dFZGNNWG1nSlp5WGlrWDA0RXVDNmlrOXJPaXNZZW5XRm1ZK0ZUSGw4dW9UYWkvOVhRNEZ6SDFPOE4rekxkaEkzblN3QllwQmsyWWYiLCJvcmlnaW4iOiJodHRwczovL2ZsZXguY3liZXJzb3VyY2UuY29tIiwiandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoiZW5jIiwibiI6InNfbHhBLUJXc1Bab1pRR0lKN0JwdjNfNkF5U0VYZXVtMloyR3RzM1NFY2preFM0aXFDTmltN1haTzZDanBMMy1laW1aZVZmNmo2YjFkbG5NUGViZDRCRHduQlotU3ZVQ1VhRjBhWGNxazhheGFBVVhNZlg0VEthR1BOcldmc2x4ZFhEdHk5empsY3dLbTBKUkhEdGpSVDBKQkVqZWQ3aXZDUGVnWV9ySldkSGtDVEpPN2t4dDl0YWFfQzZsclYwMGRHLWlmVGZRTE1rZmw0cHFKZzN0QzJiSGNrTFZIX2hET0loR3BkZGRQNkpiNUdLUW9GblcwX0VfcFk3WWF0WVFTdkZLOVZYUE82azJDbUZuaUNpYS1ERFhDMkFGdnlqdFA0bWdDOUdHbEVkWjczRDFyVExnZG9TTEVOOFBFS3VuZUFiaG5oWjB0QkM2RUpPcFIyRlB2USIsImtpZCI6IjAzd3pWcDgzRzFMV0l5ZXo4NTFNNzh1YWRzZmFnd1RmIn19LCJjdHgiOlt7ImRhdGEiOnsiY2xpZW50TGlicmFyeSI6Imh0dHBzOi8vZmxleC5jeWJlcnNvdXJjZS5jb20vbWljcm9mb3JtL2J1bmRsZS92MS9mbGV4LW1pY3JvZm9ybS5taW4uanMiLCJ0YXJnZXRPcmlnaW5zIjpbImh0dHBzOi8vd3d3LnBva2Vtb25jZW50ZXIuY29tIiwiaHR0cHM6Ly90ZXN0LnBva2Vtb25jZW50ZXIuY29tIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sIm1mT3JpZ2luIjoiaHR0cHM6Ly9mbGV4LmN5YmVyc291cmNlLmNvbSJ9LCJ0eXBlIjoibWYtMS4wLjAifV0sImlzcyI6IkZsZXggQVBJIiwiZXhwIjoxNzMxNzQzMjQ5LCJpYXQiOjE3MzE3NDIzNDksImp0aSI6IkloRmhQS0ZJYklodFJlSnUifQ.NXO01kzfvAVqs-iZY2r7dKL_0enRa3snbAe0kpwCs-rrLDbwITAYw--sfX1rC0CLBxeFubaT52y0hxCf88-MmcQetSchswCka9MBl7zLnmncsToES1rd7VaDbv_DlgaHBwREb1NR3l-6rmclcgOswK_kuAgFRWes-QOLWgtSqoBfhOC87w1ZInNMDwcJk__74o8vBgsVB7UDHfyjDaRMRrxOZRiX3TTmizOZtuN0TvmL7rwJHgTxlAdwcuxA4Ja2hBLA0zvqLgDg5cPZCNIpQvGYTdzJFSSc-nmhYlzf4eqZ3V-eD3qK9u1q44ZWAl8dS9x56XrsIkvYMo9VJ3namQ";


const data = {
number: "5102778094092647",
securityCode: "813",
expirationMonth: "01",
expirationYear: "2025",
type: "002",
};


const encrypted = await encrypt(data, context);

Golang

package Captcha

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha1"
    "encoding/base64"
    "encoding/json"
    "errors"
    "fmt"
    "io"
    "math/big"
    "strings"
)

var CardTypes = map[string]string{
    "Visa":            "001",
    "MasterCard":      "002",
    "AmericanExpress": "003",
    "Discover":        "004",
    "Diners":          "005",
    "JCB":             "007",
    "Maestro":         "042",
    "ChinaUnionPay":   "062",
}

func decodeJwt(token string) (map[string]interface{}, error) {
    parts := strings.Split(token, ".")
    if len(parts) < 2 {
        return nil, errors.New("invalid JWT token: missing segments")
    }

    payload := parts[1]

    decoded, err := base64.RawURLEncoding.DecodeString(padBase64(payload))
    if err != nil {
        decodedStd, errStd := base64.StdEncoding.DecodeString(padBase64(payload))
        if errStd != nil {
            return nil, fmt.Errorf("failed to decode base64 payload: %w", err)
        }
        decoded = decodedStd
    }

    var result map[string]interface{}
    if err := json.Unmarshal(decoded, &result); err != nil {
        return nil, fmt.Errorf("failed to unmarshal payload JSON: %w", err)
    }

    return result, nil
}

func padBase64(s string) string {
    switch len(s) % 4 {
    case 2:
        s += "=="
    case 3:
        s += "="
    }
    return s
}

func generateKey() ([]byte, error) {
    key := make([]byte, 32) // 256 bits
    _, err := rand.Read(key)
    if err != nil {
        return nil, err
    }
    return key, nil
}

func stringToBytes(str string) []byte {
    return []byte(str)
}

func bytesToString(buf []byte) string {
    return string(buf)
}

func replace(str string) string {
    return base64.RawURLEncoding.EncodeToString([]byte(str))
}

func _encrypt(payload interface{}, key []byte, header interface{}, iv []byte) ([]byte, []byte, error) {
    // Prepare the additionalData from the header
    headerJSON, err := json.Marshal(header)
    if err != nil {
        return nil, nil, err
    }
    additionalData := stringToBytes(replace(string(headerJSON)))

    // Convert payload to JSON
    payloadBytes, err := json.Marshal(payload)
    if err != nil {
        return nil, nil, err
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, nil, err
    }

    aesGCM, err := cipher.NewGCM(block)
    if err != nil {
        return nil, nil, err
    }

    // Encrypt
    // GCM output = ciphertext + tag appended
    cipherText := aesGCM.Seal(nil, iv, payloadBytes, additionalData)

    // Return the combined ciphertext+tag, along with key
    return cipherText, key, nil
}

type jwkRSA struct {
    Kty string `json:"kty"`
    N   string `json:"n"`   // base64url
    E   string `json:"e"`   // base64url
    Kid string `json:"kid"` // optional
    Use string `json:"use"` // e.g. "enc"
    Alg string `json:"alg"` // e.g. "RSA-OAEP"
}

func wrapKey(aesKey []byte, jwk map[string]interface{}) ([]byte, error) {
    // Convert the relevant JWK portion to a jwkRSA struct
    var keyData jwkRSA
    b, err := json.Marshal(jwk)
    if err != nil {
        return nil, err
    }
    if err := json.Unmarshal(b, &keyData); err != nil {
        return nil, err
    }

    // Decode base64 URL n and e
    nBytes, err := base64.RawURLEncoding.DecodeString(keyData.N)
    if err != nil {
        return nil, fmt.Errorf("failed to decode n: %w", err)
    }
    eBytes, err := base64.RawURLEncoding.DecodeString(keyData.E)
    if err != nil {
        return nil, fmt.Errorf("failed to decode e: %w", err)
    }

    // Convert big-endian bytes to big.Int
    n := new(big.Int).SetBytes(nBytes)
    e := new(big.Int).SetBytes(eBytes)

    // If e is small, interpret it properly
    // (Often e is 65537)
    pubKey := &rsa.PublicKey{
        N: n,
        E: int(e.Int64()),
    }

    // For RSA-OAEP with SHA1
    hash := sha1.New()

    // Encrypt (wrap) the AES key
    wrapped, err := rsa.EncryptOAEP(hash, rand.Reader, pubKey, aesKey, nil)
    if err != nil {
        return nil, err
    }
    return wrapped, nil
}

func buildEncryptedData(
    cipherText []byte,
    aesKey []byte,
    iv []byte,
    header interface{},
    jwk map[string]interface{},
) (string, error) {

    // GCM uses a 128-bit (16-byte) tag
    tagLength := 16

    // ciphertext length minus 16 bytes
    if len(cipherText) < tagLength {
        return "", errors.New("ciphertext too short for GCM tag")
    }
    u := len(cipherText) - tagLength

    // Wrap the AES key
    wrappedKey, err := wrapKey(aesKey, jwk)
    if err != nil {
        return "", err
    }

    // Convert everything to base64-URL strings
    headerJSON, _ := json.Marshal(header)
    partHeader := replace(string(headerJSON))
    partKey := replace(bytesToString(wrappedKey))
    partIV := replace(bytesToString(iv))
    partCipher := replace(bytesToString(cipherText[:u]))
    partTag := replace(bytesToString(cipherText[u:]))

    return strings.Join([]string{
        partHeader,
        partKey,
        partIV,
        partCipher,
        partTag,
    }, "."), nil
}

func Encrypt(data interface{}, context string, index int) (string, string, error) {
    // 1) Decode the JWT to get the kid + JWK
    decoded, err := decodeJwt(context)
    if err != nil {
        return "", "", err
    }

    flxRaw, ok := decoded["flx"]
    if !ok {
        return "", "", errors.New("flx field not found in JWT payload")
    }
    flxMap, ok := flxRaw.(map[string]interface{})
    if !ok {
        return "", "", errors.New("invalid flx field")
    }
    jwkRaw, ok := flxMap["jwk"]
    if !ok {
        return "", "", errors.New("jwk field not found in flx")
    }
    jwkMap, ok := jwkRaw.(map[string]interface{})
    if !ok {
        return "", "", errors.New("invalid jwk field")
    }

    kidRaw, ok := jwkMap["kid"]
    if !ok {
        return "", "", errors.New("kid field not found in jwk")
    }
    kid, _ := kidRaw.(string)
    fmt.Println("key id is: " + kid)

    // 2) Build the header
    header := map[string]string{
        "kid": kid,
        "alg": "RSA-OAEP",
        "enc": "A256GCM",
    }

    // 3) The actual payload
    payload := map[string]interface{}{
        "data":    data,
        "context": context,
        "index":   index,
    }

    // 4) Generate a random IV (12 bytes for GCM)
    iv := make([]byte, 12)
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return "", "", err
    }

    // 5) Generate the AES key
    aesKey, err := generateKey()
    if err != nil {
        return "", "", err
    }

    // 6) Encrypt with AES-GCM
    cipherText, realKey, err := _encrypt(payload, aesKey, header, iv)
    if err != nil {
        return "", "", err
    }

    // 7) Build final encrypted data string
    encrypted, err := buildEncryptedData(cipherText, realKey, iv, header, jwkMap)
    if err != nil {
        return "", "", err
    }

    return encrypted, kid, nil
}

// Example use
func main() {
    context := "eyJraWQiOiJ3ZiIsImFsZyI6IlJTMjU2In0.eyJmbHgiOnsicGF0aCI6Ii9mbGV4L3YyL3Rva2VucyIsImRhdGEiOiIvUVJ1QjF1dGJVQXd3OXp0ekovZG1oQUFFTlB3VFVZR2dtVTh1eDBiZnNocWpFNmlEU09VNWxJd0dFZGNNWG1nSlp5WGlrWDA0RXVDNmlrOXJPaXNZZW5XRm1ZK0ZUSGw4dW9UYWkvOVhRNEZ6SDFPOE4rekxkaEkzblN3QllwQmsyWWYiLCJvcmlnaW4iOiJodHRwczovL2ZsZXguY3liZXJzb3VyY2UuY29tIiwiandrIjp7Imt0eSI6IlJTQSIsImUiOiJBUUFCIiwidXNlIjoiZW5jIiwibiI6InNfbHhBLUJXc1Bab1pRR0lKN0JwdjNfNkF5U0VYZXVtMloyR3RzM1NFY2preFM0aXFDTmltN1haTzZDanBMMy1laW1aZVZmNmo2YjFkbG5NUGViZDRCRHduQlotU3ZVQ1VhRjBhWGNxazhheGFBVVhNZlg0VEthR1BOcldmc2x4ZFhEdHk5empsY3dLbTBKUkhEdGpSVDBKQkVqZWQ3aXZDUGVnWV9ySldkSGtDVEpPN2t4dDl0YWFfQzZsclYwMGRHLWlmVGZRTE1rZmw0cHFKZzN0QzJiSGNrTFZIX2hET0loR3BkZGRQNkpiNUdLUW9GblcwX0VfcFk3WWF0WVFTdkZLOVZYUE82azJDbUZuaUNpYS1ERFhDMkFGdnlqdFA0bWdDOUdHbEVkWjczRDFyVExnZG9TTEVOOFBFS3VuZUFiaG5oWjB0QkM2RUpPcFIyRlB2USIsImtpZCI6IjAzd3pWcDgzRzFMV0l5ZXo4NTFNNzh1YWRzZmFnd1RmIn19LCJjdHgiOlt7ImRhdGEiOnsiY2xpZW50TGlicmFyeSI6Imh0dHBzOi8vZmxleC5jeWJlcnNvdXJjZS5jb20vbWljcm9mb3JtL2J1bmRsZS92MS9mbGV4LW1pY3JvZm9ybS5taW4uanMiLCJ0YXJnZXRPcmlnaW5zIjpbImh0dHBzOi8vd3d3LnBva2Vtb25jZW50ZXIuY29tIiwiaHR0cHM6Ly90ZXN0LnBva2Vtb25jZW50ZXIuY29tIiwiaHR0cDovL2xvY2FsaG9zdDozMDAwIl0sIm1mT3JpZ2luIjoiaHR0cHM6Ly9mbGV4LmN5YmVyc291cmNlLmNvbSJ9LCJ0eXBlIjoibWYtMS4wLjAifV0sImlzcyI6IkZsZXggQVBJIiwiZXhwIjoxNzMxNzQzMjQ5LCJpYXQiOjE3MzE3NDIzNDksImp0aSI6IkloRmhQS0ZJYklodFJlSnUifQ.NXO01kzfvAVqs-iZY2r7dKL_0enRa3snbAe0kpwCs-rrLDbwITAYw--sfX1rC0CLBxeFubaT52y0hxCf88-MmcQetSchswCka9MBl7zLnmncsToES1rd7VaDbv_DlgaHBwREb1NR3l-6rmclcgOswK_kuAgFRWes-QOLWgtSqoBfhOC87w1ZInNMDwcJk__74o8vBgsVB7UDHfyjDaRMRrxOZRiX3TTmizOZtuN0TvmL7rwJHgTxlAdwcuxA4Ja2hBLA0zvqLgDg5cPZCNIpQvGYTdzJFSSc-nmhYlzf4eqZ3V-eD3qK9u1q44ZWAl8dS9x56XrsIkvYMo9VJ3namQ"

    data := map[string]string{
        "number":           "5102778094092647",
        "securityCode":     "813",
        "expirationMonth":  "01",
        "expirationYear":   "2025",
        "type":             "002",
    }

    encryptedData, kid, err := Captcha.Encrypt(data, context, 0)
}