From a180cd1abde41a32fdee49c06b42030176130c22 Mon Sep 17 00:00:00 2001 From: mrzhou Date: Thu, 12 Feb 2026 12:30:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=B2=E6=B5=8B=E8=AF=95=E8=BF=87=EF=BC=8C?= =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes go.mod | 5 + go.sum | 2 + main.go | 454 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 461 insertions(+) create mode 100644 .DS_Store create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 0 { + padding := make([]byte, paddingLen) + rand.Read(padding) + padded = append(padded, padding...) + } + + // Create Blowfish ciphers + cipher1, err := blowfish.NewCipher(sc.key1) + if err != nil { + panic(err) + } + cipher2, err := blowfish.NewCipher(sc.key2) + if err != nil { + panic(err) + } + + // CBC mode + cbc1 := cipher.NewCBCEncrypter(cipher1, sc.iv) + cbc2 := cipher.NewCBCEncrypter(cipher2, sc.iv) + + // Encrypt with cipher2 first + encrypted2 := make([]byte, len(padded)) + cbc2.CryptBlocks(encrypted2, padded) + + // Add random prefix and suffix + randomPrefix := make([]byte, 4) + rand.Read(randomPrefix) + randomSuffix := make([]byte, 4) + rand.Read(randomSuffix) + combined := append(randomPrefix, encrypted2...) + combined = append(combined, randomSuffix...) + + // Encrypt with cipher1 + encrypted1 := make([]byte, len(combined)) + cbc1.CryptBlocks(encrypted1, combined) + + return hex.EncodeToString(encrypted1) +} + +func (sc *SecureCRTCrypto) Decrypt(ciphertext string) string { + ciphertextBytes, err := hex.DecodeString(ciphertext) + if err != nil { + panic(err) + } + if len(ciphertextBytes) <= 8 { + panic("Bad ciphertext: too short!") + } + + // Create Blowfish ciphers + cipher1, err := blowfish.NewCipher(sc.key1) + if err != nil { + panic(err) + } + cipher2, err := blowfish.NewCipher(sc.key2) + if err != nil { + panic(err) + } + + // CBC mode + cbc1 := cipher.NewCBCDecrypter(cipher1, sc.iv) + cbc2 := cipher.NewCBCDecrypter(cipher2, sc.iv) + + // Decrypt with cipher1 first + decrypted1 := make([]byte, len(ciphertextBytes)) + cbc1.CryptBlocks(decrypted1, ciphertextBytes) + + // Remove random prefix and suffix + decrypted1 = decrypted1[4 : len(decrypted1)-4] + + // Decrypt with cipher2 + decrypted2 := make([]byte, len(decrypted1)) + cbc2.CryptBlocks(decrypted2, decrypted1) + + // Find null terminator + nullIndex := -1 + for i := 0; i < len(decrypted2)-1; i += 2 { + if decrypted2[i] == 0 && decrypted2[i+1] == 0 { + nullIndex = i + break + } + } + if nullIndex < 0 { + panic("Bad ciphertext: null terminator not found") + } + + // Check padding + paddingLen := len(decrypted2) - (nullIndex + 2) + expectedPadding := blowfish.BlockSize - (nullIndex+2)%blowfish.BlockSize + if paddingLen != expectedPadding { + panic("Bad ciphertext: incorrect padding") + } + + // Convert from UTF-16 LE to string + plaintextBytes := decrypted2[:nullIndex] + plaintext := make([]byte, 0, len(plaintextBytes)/2) + for i := 0; i < len(plaintextBytes); i += 2 { + plaintext = append(plaintext, plaintextBytes[i]) + } + + return string(plaintext) +} + +type SecureCRTCryptoV2 struct { + configPassphrase []byte +} + +func NewSecureCRTCryptoV2(configPassphrase string) *SecureCRTCryptoV2 { + return &SecureCRTCryptoV2{ + configPassphrase: []byte(configPassphrase), + } +} + +func (sc *SecureCRTCryptoV2) Encrypt(plaintext, prefix string) string { + plaintextBytes := []byte(plaintext) + if len(plaintextBytes) > 0xffffffff { + panic("Bad plaintext: too long!") + } + + var block cipher.Block + var iv []byte + var salt []byte + + if prefix == "02" { + // Use SHA256 of passphrase as key + hash := sha256.Sum256(sc.configPassphrase) + var err error + block, err = aes.NewCipher(hash[:]) + if err != nil { + panic(err) + } + iv = make([]byte, aes.BlockSize) + // All zeros IV + } else if prefix == "03" { + // Use bcrypt_pbkdf2 to derive key and IV + salt = make([]byte, 16) + rand.Read(salt) + kdfBytes := bcryptPBKDF2(sc.configPassphrase, salt, 32+aes.BlockSize, 16) + var err error + block, err = aes.NewCipher(kdfBytes[:32]) + if err != nil { + panic(err) + } + iv = kdfBytes[32:] + } else { + panic(fmt.Sprintf("Unknown prefix: %s", prefix)) + } + + // Create CBC encrypter + cbc := cipher.NewCBCEncrypter(block, iv) + + // Create lvc bytes: length + value + checksum + length := uint32(len(plaintextBytes)) + lvc := make([]byte, 4) + lvc[0] = byte(length) + lvc[1] = byte(length >> 8) + lvc[2] = byte(length >> 16) + lvc[3] = byte(length >> 24) + lvc = append(lvc, plaintextBytes...) + hash := sha256.Sum256(plaintextBytes) + lvc = append(lvc, hash[:]...) + + // Calculate padding + paddingLen := aes.BlockSize - len(lvc)%aes.BlockSize + if paddingLen < aes.BlockSize/2 { + paddingLen += aes.BlockSize + } + + // Add padding + padded := make([]byte, len(lvc)) + copy(padded, lvc) + padding := make([]byte, paddingLen) + rand.Read(padding) + padded = append(padded, padding...) + + // Encrypt + encrypted := make([]byte, len(padded)) + cbc.CryptBlocks(encrypted, padded) + + // For prefix 03, prepend salt + if prefix == "03" { + encrypted = append(salt, encrypted...) + } + + return hex.EncodeToString(encrypted) +} + +func (sc *SecureCRTCryptoV2) Decrypt(ciphertext, prefix string) string { + ciphertextBytes, err := hex.DecodeString(ciphertext) + if err != nil { + panic(err) + } + + var block cipher.Block + var iv []byte + var encrypted []byte + + if prefix == "02" { + // Use SHA256 of passphrase as key + hash := sha256.Sum256(sc.configPassphrase) + var err error + block, err = aes.NewCipher(hash[:]) + if err != nil { + panic(err) + } + iv = make([]byte, aes.BlockSize) + // All zeros IV + encrypted = ciphertextBytes + } else if prefix == "03" { + // For prefix 03, ALWAYS extract salt and use bcrypt_pbkdf2 + // This matches Python code exactly + if len(ciphertextBytes) < 16 { + panic("Bad ciphertext: too short!") + } + salt := ciphertextBytes[:16] + encrypted = ciphertextBytes[16:] + + // Use bcrypt_pbkdf2 to derive key and IV for ALL cases, including empty passphrase + kdfBytes := bcryptPBKDF2(sc.configPassphrase, salt, 32+aes.BlockSize, 16) + var err error + block, err = aes.NewCipher(kdfBytes[:32]) + if err != nil { + panic(err) + } + iv = kdfBytes[32:] + } else { + panic(fmt.Sprintf("Unknown prefix: %s", prefix)) + } + + // Check if encrypted data is at least one block size + if len(encrypted) < aes.BlockSize { + panic("Bad ciphertext: encrypted data too short") + } + + // Create CBC decrypter + cbc := cipher.NewCBCDecrypter(block, iv) + + // Decrypt + decrypted := make([]byte, len(encrypted)) + cbc.CryptBlocks(decrypted, encrypted) + + // Check if decrypted data has at least lvc header + if len(decrypted) < 4+sha256.Size { + panic("Bad ciphertext: decrypted data too short") + } + + // Parse lvc bytes - little endian (matches Python's struct.pack(' maxValidLength || length < 0 { + panic(fmt.Sprintf("Bad ciphertext: invalid length %d, must be between 0 and %d", length, maxValidLength)) + } + + // Extract plaintext, checksum, and padding + plaintextBytes := decrypted[4 : 4+length] + checksum := decrypted[4+length : 4+length+sha256.Size] + paddingBytes := decrypted[4+length+sha256.Size:] + + // Verify checksum + actualHash := sha256.Sum256(plaintextBytes) + if !bytes.Equal(actualHash[:], checksum) { + panic("Bad ciphertext: incorrect sha256 checksum") + } + + // Verify padding + expectedPaddingLen := aes.BlockSize - (4+int(length)+sha256.Size)%aes.BlockSize + if expectedPaddingLen < aes.BlockSize/2 { + expectedPaddingLen += aes.BlockSize + } + if len(paddingBytes) != expectedPaddingLen { + panic("Bad ciphertext: incorrect padding") + } + + return string(plaintextBytes) +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("Usage: go run main.go [options] ") + os.Exit(1) + } + + operation := os.Args[1] + if operation != "enc" && operation != "dec" { + fmt.Println("Invalid operation. Use 'enc' or 'dec'") + os.Exit(1) + } + + v2 := false + prefix := "03" + passphrase := "" + passwordIndex := 2 + + // Parse arguments + for i := 2; i < len(os.Args); i++ { + arg := os.Args[i] + if arg == "-2" || arg == "--v2" { + v2 = true + passwordIndex++ + } else if arg == "--prefix" { + if i+1 >= len(os.Args) { + fmt.Println("Error: --prefix requires a value") + os.Exit(1) + } + prefix = os.Args[i+1] + if prefix != "02" && prefix != "03" { + fmt.Println("Error: prefix must be '02' or '03'") + os.Exit(1) + } + i++ + passwordIndex += 2 + } else if arg == "-p" || arg == "--passphrase" { + if i+1 >= len(os.Args) { + fmt.Println("Error: --passphrase requires a value") + os.Exit(1) + } + passphrase = os.Args[i+1] + i++ + passwordIndex += 2 + } + } + + if passwordIndex >= len(os.Args) { + fmt.Println("Error: password is required") + os.Exit(1) + } + password := os.Args[passwordIndex] + + if v2 { + crypto := NewSecureCRTCryptoV2(passphrase) + if operation == "enc" { + result := crypto.Encrypt(password, prefix) + fmt.Println(result) + } else { + result := crypto.Decrypt(password, prefix) + fmt.Println(result) + } + } else { + crypto := NewSecureCRTCrypto() + if operation == "enc" { + result := crypto.Encrypt(password) + fmt.Println(result) + } else { + result := crypto.Decrypt(password) + fmt.Println(result) + } + } +}