211 lines
5.7 KiB
Go
211 lines
5.7 KiB
Go
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
|
|
}
|