package services import ( helper "api-service/internal/helpers/bpjs" "bytes" "compress/gzip" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "io" "log" "strings" "unicode" "unicode/utf16" "unicode/utf8" lzstring "github.com/daku10/go-lz-string" ) func min(a, b int) int { if a < b { return a } return b } // GenerateBPJSKey - Generate key sesuai standar BPJS VClaim func GenerateBPJSKey(consumerID, timestamp, userKey string) string { // Format: consumerID + "&" + timestamp + "&" + userKey keyString := fmt.Sprintf("%s&%s&%s", consumerID, timestamp, userKey) // BPJS biasanya menggunakan key dengan length 32 bytes if len(keyString) > 32 { return keyString[:32] } // Pad dengan spasi jika kurang dari 32 for len(keyString) < 32 { keyString += " " } log.Printf("GenerateBPJSKey: Generated key length: %d", len(keyString)) return keyString } // 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") } // Try multiple key generation and decryption methods keyMethods := []func(string) ([]byte, error){ // Method 1: SHA256 hash of key func(k string) ([]byte, error) { hash := sha256.Sum256([]byte(k)) return hash[:], nil }, // Method 2: Key as-is (padded/truncated to 32 bytes) func(k string) ([]byte, error) { keyBytes := []byte(k) if len(keyBytes) < 32 { // Pad with zeros padded := make([]byte, 32) copy(padded, keyBytes) return padded, nil } return keyBytes[:32], nil }, // Method 3: MD5 hash repeated to make 32 bytes func(k string) ([]byte, error) { hash := md5.Sum([]byte(k)) key32 := make([]byte, 32) copy(key32[:16], hash[:]) copy(key32[16:], hash[:]) return key32, nil }, } for keyIdx, keyMethod := range keyMethods { keyBytes, err := keyMethod(key) if err != nil { continue } block, err := aes.NewCipher(keyBytes) if err != nil { continue } // Try different IV methods for each key method ivMethods := []func([]byte, []byte) (string, error){ // IV from key hash func(ct, kb []byte) (string, error) { hash := sha256.Sum256(kb) return tryDecryptWithCustomIV(ct, block, hash[:aes.BlockSize]) }, // IV from ciphertext func(ct, kb []byte) (string, error) { return tryDecryptWithCipherIV(ct, block) }, // Zero IV func(ct, kb []byte) (string, error) { iv := make([]byte, aes.BlockSize) return tryDecryptWithCustomIV(ct, block, iv) }, // IV from key directly func(ct, kb []byte) (string, error) { iv := make([]byte, aes.BlockSize) copy(iv, kb[:aes.BlockSize]) return tryDecryptWithCustomIV(ct, block, iv) }, } for ivIdx, ivMethod := range ivMethods { if result, err := ivMethod(cipherText, keyBytes); err == nil { log.Printf("ResponseVclaim: Success with key method %d, IV method %d", keyIdx+1, ivIdx+1) log.Printf("ResponseVclaim result preview: %s", result[:min(200, len(result))]) return result, nil } else { log.Printf("ResponseVclaim: Key method %d, IV method %d failed: %v", keyIdx+1, ivIdx+1, err) } } } return "", errors.New("all decryption methods failed") } // 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 multiple decryption methods // methods := []func([]byte, cipher.Block, []byte) (string, error){ // // Method 1: IV from hash (current approach) // func(ct []byte, b cipher.Block, h []byte) (string, error) { // return tryDecryptWithHashIV(ct, b, h[:aes.BlockSize]) // }, // // Method 2: IV from cipherText (standard approach) // func(ct []byte, b cipher.Block, h []byte) (string, error) { // return tryDecryptWithCipherIV(ct, b) // }, // // Method 3: Try without padding removal // func(ct []byte, b cipher.Block, h []byte) (string, error) { // return tryDecryptWithoutPaddingRemoval(ct, b, h[:aes.BlockSize]) // }, // } // for i, method := range methods { // if result, err := method(cipherText, block, hash[:]); err == nil { // log.Printf("ResponseVclaim: Success with method %d", i+1) // log.Printf("ResponseVclaim result: %s", result[:min(100, len(result))]) // return result, nil // } else { // log.Printf("ResponseVclaim: Method %d failed: %v", i+1, err) // } // } // return "", errors.New("all decryption methods failed") // } // 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") // log.Println("ResponseVclaim: ", result) // 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 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) // Log raw decrypted data before padding removal log.Printf("tryDecryptWithHashIV: Raw decrypted length: %d", len(decrypted)) // Remove PKCS7 padding decrypted = helper.RemovePKCS7Padding(decrypted) log.Printf("tryDecryptWithHashIV: After padding removal, length: %d", len(decrypted)) // Log first 50 bytes untuk debugging logLen := min(50, len(decrypted)) log.Printf("tryDecryptWithHashIV: First %d bytes: %q", logLen, string(decrypted[:logLen])) 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 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) // Log raw decrypted data before padding removal log.Printf("tryDecryptWithCipherIV: Raw decrypted length: %d", len(decrypted)) // Remove PKCS7 padding decrypted = helper.RemovePKCS7Padding(decrypted) log.Printf("tryDecryptWithCipherIV: After padding removal, length: %d", len(decrypted)) // Log first 50 bytes untuk debugging logLen := min(50, len(decrypted)) log.Printf("tryDecryptWithCipherIV: First %d bytes: %q", logLen, string(decrypted[:logLen])) return tryAllDecompressionMethods(decrypted) } func tryAllDecompressionMethods(data []byte) (string, error) { log.Printf("tryAllDecompressionMethods: Attempting decompression, data length: %d", len(data)) // Log hex dump for better debugging hexDump := make([]string, min(32, len(data))) for i := 0; i < len(hexDump); i++ { hexDump[i] = fmt.Sprintf("%02x", data[i]) } log.Printf("tryAllDecompressionMethods: Hex dump (first 32 bytes): %s", strings.Join(hexDump, " ")) // Method 1: Try LZ-string first (most common for BPJS) if result, err := tryLZStringMethods(data); err == nil { log.Println("tryAllDecompressionMethods: LZ-string decompression successful") return result, nil } // Method 2: Try gzip if result, err := tryGzipDecompression(data); err == nil && isValidDecompressedResult(result) { log.Println("tryAllDecompressionMethods: Gzip decompression successful") return result, nil } // Method 3: Try as plain text if isValidUTF8AndPrintable(string(data)) { result := string(data) if isValidDecompressedResult(result) { log.Println("tryAllDecompressionMethods: Data is already valid text") return result, nil } } // Method 4: Try base64 decode then decompress if result, err := tryBase64ThenDecompress(data); err == nil { log.Println("tryAllDecompressionMethods: Base64 then decompress successful") return result, nil } log.Printf("tryAllDecompressionMethods: All methods failed") return "", errors.New("all decompression methods failed") } // func tryAllDecompressionMethods(data []byte) (string, error) { // log.Printf("tryAllDecompressionMethods: Attempting decompression, data length: %d", len(data)) // // Method 1: Try LZ-string decompression FIRST (paling umum untuk BPJS API) // if result, err := tryLZStringMethods(data); err == nil { // log.Println("tryAllDecompressionMethods: LZ-string decompression successful") // return result, nil // } // // Method 2: Try gzip decompression // if result, err := tryGzipDecompression(data); err == nil && isValidDecompressedResult(result) { // log.Println("tryAllDecompressionMethods: Gzip decompression successful") // return result, nil // } // // Method 3: Check if it's already valid JSON/text (SETELAH mencoba decompression) // if isValidUTF8AndPrintable(string(data)) { // result := string(data) // if isValidDecompressedResult(result) { // log.Println("tryAllDecompressionMethods: Data is already valid, returning as-is") // return result, nil // } // } // // Method 4: Log the raw data for debugging // log.Printf("tryAllDecompressionMethods: All methods failed. Raw data (first 100 bytes): %q", string(data[:min(100, len(data))])) // return "", errors.New("all decompression methods failed") // } // func tryLZStringMethods(data []byte) (string, error) { // dataStr := string(data) // log.Printf("tryLZStringMethods: Attempting LZ-string decompression on data: %s", dataStr[:min(50, len(dataStr))]) // // Method 1: DecompressFromEncodedURIComponent (paling umum) // if result, err := lzstring.DecompressFromEncodedURIComponent(dataStr); err == nil && len(result) > 0 { // log.Printf("LZ-string DecompressFromEncodedURIComponent attempted, result length: %d", len(result)) // if isValidDecompressedResult(result) { // log.Printf("LZ-string DecompressFromEncodedURIComponent successful and valid") // return result, nil // } else { // log.Printf("LZ-string DecompressFromEncodedURIComponent result not valid: %s", result[:min(100, len(result))]) // } // } // // Method 2: DecompressFromBase64 // if result, err := lzstring.DecompressFromBase64(dataStr); err == nil && len(result) > 0 { // log.Printf("LZ-string DecompressFromBase64 attempted, result length: %d", len(result)) // if isValidDecompressedResult(result) { // log.Printf("LZ-string DecompressFromBase64 successful and valid") // return result, nil // } else { // log.Printf("LZ-string DecompressFromBase64 result not valid: %s", result[:min(100, len(result))]) // } // } // // Method 3: Try base64 decode first, then decompress // if decoded, err := base64.StdEncoding.DecodeString(dataStr); err == nil { // if result, err := lzstring.DecompressFromEncodedURIComponent(string(decoded)); err == nil && len(result) > 0 { // if isValidDecompressedResult(result) { // log.Printf("LZ-string with base64 decode first successful and valid") // return result, nil // } // } // } // // Method 4: Try as raw bytes (convert each byte to uint16) // if result, err := tryRawBytesToLZString(data); err == nil && len(result) > 0 { // if isValidDecompressedResult(result) { // log.Printf("LZ-string from raw bytes successful and valid") // return result, nil // } // } // log.Printf("All LZ-string methods failed or returned invalid results") // return "", errors.New("all LZ-string methods failed") // } func tryLZStringMethods(data []byte) (string, error) { dataStr := string(data) log.Printf("tryLZStringMethods: Raw data length: %d", len(dataStr)) // Method 1: Bersihkan prefix corrupt dan cari pattern LZ-string cleanedData := extractCleanLZString(dataStr) if cleanedData != "" { log.Printf("tryLZStringMethods: Found clean LZ-string: %s", cleanedData[:min(50, len(cleanedData))]) // Dekompresi sesuai standar BPJS if result, err := lzstring.DecompressFromEncodedURIComponent(cleanedData); err == nil && len(result) > 0 { if isValidDecompressedResult(result) { log.Printf("LZ-string decompression successful, length: %d", len(result)) return result, nil } } } // Method 2: Fallback direct decompression if result, err := lzstring.DecompressFromEncodedURIComponent(dataStr); err == nil && len(result) > 0 { if isValidDecompressedResult(result) { return result, nil } } return "", errors.New("LZ-string decompression failed") } func extractCleanLZString(data string) string { // Pattern LZ-string umum dari dokumentasi BPJS patterns := []string{"EAuUA", "N4Ig", "BwIw", "CwIw", "DwIw", "EwIw", "FwIw", "GwIw", "HwIw"} for _, pattern := range patterns { if idx := strings.Index(data, pattern); idx >= 0 { // Ekstrak dari pattern hingga akhir candidate := data[idx:] log.Printf("extractCleanLZString: Found pattern '%s' at position %d", pattern, idx) // Bersihkan hanya karakter base64 valid cleaned := extractBase64Only(candidate) if len(cleaned) > 100 { // Minimal length untuk data valid return cleaned } } } return "" } // Fungsi untuk mengekstrak hanya karakter base64 valid func extractBase64Only(s string) string { base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" var result strings.Builder for _, char := range s { if strings.ContainsRune(base64Chars, char) { result.WriteRune(char) } else { // Stop di karakter non-base64 jika sudah cukup panjang if result.Len() > 100 { break } } } return result.String() } // Fungsi untuk mencari dan mengekstrak LZ-string dari data yang mungkin corrupt func findAndExtractLZString(data string) string { // Pattern LZ-string umum - prioritaskan yang paling umum patterns := []string{ "EAuUA", "N4Ig", "BwIw", "CwIw", "DwIw", "EwIw", "FwIw", "GwIw", "HwIw", "BAuUA", "CAuUA", "DAuUA", "AAuUA", } // Cari pattern yang paling awal dalam string earliestIdx := -1 bestPattern := "" for _, pattern := range patterns { if idx := strings.Index(data, pattern); idx >= 0 { if earliestIdx == -1 || idx < earliestIdx { earliestIdx = idx bestPattern = pattern } } } if earliestIdx >= 0 { // Ekstrak dari posisi pattern hingga akhir candidate := data[earliestIdx:] log.Printf("findAndExtractLZString: Found pattern '%s' at position %d", bestPattern, earliestIdx) log.Printf("findAndExtractLZString: Extracted data: %s", candidate[:min(100, len(candidate))]) // Bersihkan dari karakter non-base64 di akhir cleaned := cleanBase64String(candidate) if len(cleaned) > 50 { log.Printf("findAndExtractLZString: Cleaned data length: %d", len(cleaned)) return cleaned } } // Fallback: cari sequence base64 terpanjang return extractLongestBase64Sequence(data) } // Fungsi untuk membersihkan string hingga hanya karakter base64 valid func cleanBase64String(s string) string { base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" var result strings.Builder for _, char := range s { if strings.ContainsRune(base64Chars, char) { result.WriteRune(char) } else { // Jika bertemu karakter non-base64 dan sudah cukup panjang, stop if result.Len() > 100 { break } // Skip karakter non-base64 di awal } } cleaned := result.String() log.Printf("cleanBase64String: Original length: %d, Cleaned length: %d", len(s), len(cleaned)) return cleaned } // // Fungsi untuk membersihkan string hingga hanya karakter base64 valid // func cleanBase64String(s string) string { // base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" // var result strings.Builder // for _, char := range s { // if strings.ContainsRune(base64Chars, char) { // result.WriteRune(char) // } else if result.Len() > 50 { // // Stop jika sudah cukup panjang dan bertemu karakter non-base64 // break // } // } // return result.String() // } // Fungsi untuk membersihkan karakter non-printable func cleanNonPrintableChars(s string) string { var result strings.Builder for _, r := range s { if r >= 32 && r <= 126 || r == '\n' || r == '\r' || r == '\t' { result.WriteRune(r) } } return result.String() } // Fungsi baru untuk mengekstrak data LZ-string yang bersih dari data corrupt func extractLZStringFromCorruptData(data string) string { // Cari pattern LZ-string yang umum patterns := []string{"N4Ig", "BwIw", "CwIw", "DwIw", "EwIw", "FwIw", "GwIw", "HwIw", "EAuUA"} for _, pattern := range patterns { if idx := strings.Index(data, pattern); idx > 0 { cleanData := data[idx:] log.Printf("extractLZStringFromCorruptData: Found pattern '%s' at position %d", pattern, idx) // Validasi bahwa data setelah pattern adalah base64-like characters if isBase64Like(cleanData) { return cleanData } } } // Jika tidak ada pattern yang ditemukan, coba cari sequences panjang dari base64 characters return extractLongestBase64Sequence(data) } // Fungsi untuk mengecek apakah string berisi karakter base64 func isBase64Like(s string) bool { if len(s) < 20 { return false } base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" validCount := 0 for i, char := range s[:min(100, len(s))] { if strings.ContainsRune(base64Chars, char) { validCount++ } if i > 20 && float64(validCount)/float64(i+1) < 0.8 { return false } } return float64(validCount)/float64(min(100, len(s))) >= 0.8 } // Fungsi untuk mengekstrak sequence base64 terpanjang func extractLongestBase64Sequence(data string) string { base64Chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" bestStart := -1 bestLength := 0 currentStart := -1 currentLength := 0 for i, char := range data { if strings.ContainsRune(base64Chars, char) { if currentStart == -1 { currentStart = i currentLength = 1 } else { currentLength++ } } else { if currentLength > bestLength && currentLength > 50 { bestStart = currentStart bestLength = currentLength } currentStart = -1 currentLength = 0 } } // Check final sequence if currentLength > bestLength && currentLength > 50 { bestStart = currentStart bestLength = currentLength } if bestStart >= 0 && bestLength > 50 { result := data[bestStart : bestStart+bestLength] log.Printf("extractLongestBase64Sequence: Found sequence at pos %d, length %d", bestStart, bestLength) return result } return "" } // Fungsi untuk validasi hasil dekompresi func isValidDecompressedResult(result string) bool { if len(result) == 0 { return false } // Trim whitespace dan cek UTF-8 trimmed := strings.TrimSpace(result) if !utf8.ValidString(trimmed) { return false } // Harus dimulai dengan { atau [ untuk JSON if len(trimmed) > 0 && (trimmed[0] == '{' || trimmed[0] == '[') { // Validasi sebagai JSON var js json.RawMessage if json.Unmarshal([]byte(result), &js) == nil { log.Printf("Decompressed result is valid JSON, length: %d", len(result)) return true } } // Jika bukan JSON, tolak log.Printf("Decompressed result is not valid JSON") return false } // func isValidDecompressedResult(result string) bool { // if len(result) == 0 { // return false // } // // Check if result contains only printable ASCII and valid UTF-8 // if !isValidUTF8AndPrintable(result) { // log.Printf("Decompressed result contains invalid characters") // return false // } // // Check if it looks like JSON (starts with { or [) // trimmed := strings.TrimSpace(result) // if len(trimmed) > 0 && (trimmed[0] == '{' || trimmed[0] == '[') { // // Try to validate as JSON // var js json.RawMessage // if json.Unmarshal([]byte(result), &js) == nil { // log.Printf("Decompressed result is valid JSON") // return true // } // } // // PERBAIKAN: Jangan anggap data yang dimulai dengan karakter tertentu sebagai valid text // // Data LZ-string biasanya dimulai dengan karakter seperti N4Ig, BwIw, dll // if detectLZStringPattern(result) { // log.Printf("Data appears to be LZ-string compressed, needs decompression") // return false // } // // If not JSON, check if it's reasonable text content // if len(result) > 10 && isReasonableTextContent(result) { // log.Printf("Decompressed result appears to be valid text content") // return true // } // return false // } // Fungsi baru untuk mendeteksi pola LZ-string func detectLZStringPattern(s string) bool { if len(s) < 10 { return false } // Pattern umum LZ-string compressed data commonLZPatterns := []string{ "N4Ig", "BwIw", "CwIw", "DwIw", "EwIw", "FwIw", "GwIw", "HwIw", "IwIw", "JwIw", "KwIw", "LwIw", "MwIw", "NwIw", "OwIw", "PwIw", } for _, pattern := range commonLZPatterns { if strings.HasPrefix(s, pattern) { return true } } // Cek apakah string hanya berisi karakter base64 tanpa spasi atau newline base64Pattern := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" if len(s) > 50 { // Only check long strings invalidChars := 0 for _, char := range s { if !strings.ContainsRune(base64Pattern, char) { invalidChars++ } } // Jika kurang dari 5% karakter invalid, kemungkinan ini LZ-string if float64(invalidChars)/float64(len(s)) < 0.05 { return true } } return false } // func isValidUTF8AndPrintable(s string) bool { // if !utf8.ValidString(s) { // return false // } // // Allow common characters: letters, numbers, spaces, and common JSON characters // for _, r := range s { // if r < 32 && r != '\n' && r != '\r' && r != '\t' { // return false // Control characters (except newline, carriage return, tab) // } // if r > 126 && r < 160 { // return false // Extended ASCII control characters // } // // Allow Unicode characters above 160 // } // return true // } func isValidUTF8AndPrintable(s string) bool { if !utf8.ValidString(s) { log.Printf("isValidUTF8AndPrintable: String is not valid UTF-8") return false } // Hitung karakter yang valid validChars := 0 totalChars := 0 for _, r := range s { totalChars++ if r >= 32 && r <= 126 { // Printable ASCII validChars++ } else if r == '\n' || r == '\r' || r == '\t' { // Allowed control chars validChars++ } else if r >= 160 { // Unicode characters validChars++ } } validRatio := float64(validChars) / float64(totalChars) log.Printf("isValidUTF8AndPrintable: Valid chars ratio: %.2f (%d/%d)", validRatio, validChars, totalChars) // At least 70% should be valid characters return validRatio >= 0.7 } func isReasonableTextContent(s string) bool { // Count printable characters printableCount := 0 for _, r := range s { if unicode.IsPrint(r) || unicode.IsSpace(r) { printableCount++ } } // At least 80% should be printable return float64(printableCount)/float64(len([]rune(s))) >= 0.8 } // Perbaikan untuk konversi raw bytes ke LZ-string func tryRawBytesToLZString(data []byte) (string, error) { // Convert bytes to uint16 array (for UTF-16 decompression) if len(data)%2 != 0 { // Pad with zero if odd length data = append(data, 0) } utf16Data := make([]uint16, len(data)/2) for i := 0; i < len(data); i += 2 { utf16Data[i/2] = uint16(data[i]) | (uint16(data[i+1]) << 8) } return lzstring.DecompressFromUTF16(utf16Data) } 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 } // 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 } // Method dekripsi tanpa padding removal func tryDecryptWithoutPaddingRemoval(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) log.Printf("tryDecryptWithoutPaddingRemoval: Decrypted length: %d", len(decrypted)) // Coba tanpa remove padding dulu return tryAllDecompressionMethods(decrypted) } func tryDecryptWithCustomIV(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) log.Printf("tryDecryptWithCustomIV: Raw decrypted length: %d", len(decrypted)) log.Printf("tryDecryptWithCustomIV: Raw first 50 bytes: %q", string(decrypted[:min(50, len(decrypted))])) // Try multiple padding removal strategies paddingStrategies := []func([]byte) []byte{ helper.RemovePKCS7Padding, removePaddingManual, func(data []byte) []byte { return data }, // No padding removal } for i, strategy := range paddingStrategies { processed := strategy(decrypted) log.Printf("tryDecryptWithCustomIV: Strategy %d, processed length: %d", i+1, len(processed)) if result, err := tryAllDecompressionMethods(processed); err == nil { log.Printf("tryDecryptWithCustomIV: Success with padding strategy %d", i+1) return result, nil } } return "", errors.New("all padding strategies failed") } // Manual padding removal yang lebih agresif func removePaddingManual(data []byte) []byte { if len(data) == 0 { return data } // Coba berbagai kemungkinan padding for padLen := 1; padLen <= min(16, len(data)); padLen++ { if data[len(data)-1] == byte(padLen) { // Check if all padding bytes match valid := true start := len(data) - padLen for i := start; i < len(data); i++ { if data[i] != byte(padLen) { valid = false break } } if valid { log.Printf("removePaddingManual: Found valid padding of length %d", padLen) return data[:start] } } } // Jika tidak ada padding yang valid, coba buang beberapa byte terakhir for i := 1; i <= min(16, len(data)); i++ { trimmed := data[:len(data)-i] if isLikelyValidData(trimmed) { log.Printf("removePaddingManual: Trimmed %d bytes, result seems valid", i) return trimmed } } return data } // Fungsi untuk mengecek apakah data kemungkinan valid func isLikelyValidData(data []byte) bool { if len(data) < 10 { return false } // Check for common patterns in compressed data or JSON str := string(data) // LZ-string patterns if strings.HasPrefix(str, "N4Ig") || strings.HasPrefix(str, "BwIw") { return true } // JSON patterns if strings.HasPrefix(str, "{") || strings.HasPrefix(str, "[") { return true } // Gzip magic number if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b { return true } return false } func tryBase64ThenDecompress(data []byte) (string, error) { decoded, err := base64.StdEncoding.DecodeString(string(data)) if err != nil { return "", err } return tryLZStringMethods(decoded) } // Fungsi alternatif untuk menghapus prefix corrupt dari data func removeCorruptPrefix(data string) string { // Cari pattern LZ-string yang dikenal patterns := []string{"EAuUA", "N4Ig", "BwIw", "BAuUA"} for _, pattern := range patterns { if idx := strings.Index(data, pattern); idx >= 0 { cleaned := data[idx:] log.Printf("removeCorruptPrefix: Removed %d corrupt bytes, found pattern: %s", idx, pattern) return cleaned } } // Jika tidak ada pattern ditemukan, coba hapus karakter non-printable di awal var result strings.Builder started := false for _, r := range data { if !started { // Mulai mengumpulkan karakter setelah bertemu karakter valid if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '+' || r == '/' || r == '=' { started = true result.WriteRune(r) } } else { result.WriteRune(r) } } return result.String() }