package services import ( helper "api-service/internal/helpers/bpjs" "bytes" "compress/gzip" "crypto/aes" "crypto/cipher" "crypto/sha256" "encoding/base64" "errors" "io" "log" "unicode/utf16" lzstring "github.com/daku10/go-lz-string" ) func min(a, b int) int { if a < b { return a } return b } // ResponseVclaim decrypts the encrypted response from VClaim API func ResponseVclaim(encrypted string, key string) (string, error) { log.Println("ResponseVclaim: Starting decryption process") log.Printf("ResponseVclaim: Encrypted string length: %d", len(encrypted)) // Pad the base64 string if needed if len(encrypted)%4 != 0 { padding := (4 - len(encrypted)%4) % 4 for i := 0; i < padding; i++ { encrypted += "=" } } // Decode base64 cipherText, err := base64.StdEncoding.DecodeString(encrypted) if err != nil { log.Printf("ResponseVclaim: Failed to decode base64: %v", err) return "", err } if len(cipherText) < aes.BlockSize { return "", errors.New("cipherText too short") } // Create AES cipher hash := sha256.Sum256([]byte(key)) block, err := aes.NewCipher(hash[:]) if err != nil { return "", err } // Try both IV methods // Method 1: IV from hash (current approach) if result, err := tryDecryptWithHashIV(cipherText, block, hash[:aes.BlockSize]); err == nil { log.Printf("ResponseVclaim: Success with hash IV method") return result, nil } // Method 2: IV from cipherText (standard approach) if result, err := tryDecryptWithCipherIV(cipherText, block); err == nil { log.Printf("ResponseVclaim: Success with cipher IV method") return result, nil } return "", errors.New("all decryption methods failed") } func tryDecryptWithHashIV(cipherText []byte, block cipher.Block, iv []byte) (string, error) { if len(cipherText)%aes.BlockSize != 0 { return "", errors.New("cipherText is not a multiple of the block size") } mode := cipher.NewCBCDecrypter(block, iv) decrypted := make([]byte, len(cipherText)) mode.CryptBlocks(decrypted, cipherText) // Remove PKCS7 padding decrypted = helper.RemovePKCS7Padding(decrypted) log.Printf("tryDecryptWithHashIV: Decryption completed, length: %d", len(decrypted)) return tryAllDecompressionMethods(decrypted) } func tryDecryptWithCipherIV(cipherText []byte, block cipher.Block) (string, error) { if len(cipherText) < aes.BlockSize { return "", errors.New("cipherText too short for IV extraction") } // Extract IV from first block iv := cipherText[:aes.BlockSize] cipherData := cipherText[aes.BlockSize:] if len(cipherData)%aes.BlockSize != 0 { return "", errors.New("cipher data is not a multiple of the block size") } mode := cipher.NewCBCDecrypter(block, iv) decrypted := make([]byte, len(cipherData)) mode.CryptBlocks(decrypted, cipherData) // Remove PKCS7 padding decrypted = helper.RemovePKCS7Padding(decrypted) log.Printf("tryDecryptWithCipherIV: Decryption completed, length: %d", len(decrypted)) return tryAllDecompressionMethods(decrypted) } func tryAllDecompressionMethods(data []byte) (string, error) { log.Printf("tryAllDecompressionMethods: Attempting decompression, data length: %d", len(data)) // Method 1: Check if it's already valid JSON if isValidJSON(data) { log.Println("tryAllDecompressionMethods: Data is valid JSON, returning as-is") return string(data), nil } // Method 2: Try gzip decompression if result, err := tryGzipDecompression(data); err == nil && len(result) > 0 { log.Println("tryAllDecompressionMethods: Gzip decompression successful") return result, nil } // Method 3: Try LZ-string decompression methods if result, err := tryLZStringMethods(data); err == nil && len(result) > 0 { log.Println("tryAllDecompressionMethods: LZ-string decompression successful") return result, nil } // Method 4: Return as plain text result := string(data) if len(result) > 0 { log.Printf("tryAllDecompressionMethods: Using decrypted data as plain text, length: %d", len(result)) return result, nil } return "", errors.New("all decompression methods failed") } func isValidJSON(data []byte) bool { if len(data) == 0 { return false } firstChar := data[0] return firstChar == '{' || firstChar == '[' } func tryGzipDecompression(data []byte) (string, error) { reader, err := gzip.NewReader(bytes.NewReader(data)) if err != nil { return "", err } defer reader.Close() decompressed, err := io.ReadAll(reader) if err != nil { return "", err } return string(decompressed), nil } func tryLZStringMethods(data []byte) (string, error) { dataStr := string(data) // Method 1: DecompressFromEncodedURIComponent if result, err := lzstring.DecompressFromEncodedURIComponent(dataStr); err == nil && len(result) > 0 { return result, nil } // Method 2: DecompressFromBase64 if result, err := lzstring.DecompressFromBase64(dataStr); err == nil && len(result) > 0 { return result, nil } // Method 3: DecompressFromUTF16 (with proper conversion) if utf16Data, err := stringToUTF16(dataStr); err == nil { if result, err := lzstring.DecompressFromUTF16(utf16Data); err == nil && len(result) > 0 { return result, nil } } // Method 4: Try with base64 decoding first if decoded, err := base64.StdEncoding.DecodeString(dataStr); err == nil { if result, err := lzstring.DecompressFromEncodedURIComponent(string(decoded)); err == nil && len(result) > 0 { return result, nil } } return "", errors.New("all LZ-string methods failed") } // stringToUTF16 converts string to []uint16 for UTF16 decompression func stringToUTF16(s string) ([]uint16, error) { if len(s) == 0 { return nil, errors.New("empty string") } // Convert string to runes first runes := []rune(s) // Convert runes to UTF16 utf16Data := utf16.Encode(runes) return utf16Data, nil }