Files
xk-hy-transit-go/internal/xkapi/crypto.go
2026-05-25 12:53:48 +08:00

123 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package xkapi
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"encoding/base64"
"fmt"
)
// DeriveTransitAESKey 从 HY_TRANSIT_API_TOKEN 派生 AES-128 密钥(与 PHP HyTransitCryptoService 一致)。
func DeriveTransitAESKey(token string) []byte {
sum := sha256.Sum256([]byte(token))
return sum[:16]
}
// EncryptBody AES-128-ECB + PKCS7 加密为 Base64与 PHP HyTransitCryptoService 一致)。
func EncryptBody(plain, token string) (string, error) {
return encryptTransitBody(plain, token)
}
func encryptTransitBody(plain, token string) (string, error) {
key := DeriveTransitAESKey(token)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
data := pkcs7Pad([]byte(plain), block.BlockSize())
out := make([]byte, len(data))
ecb := newECBEncrypter(block)
ecb.CryptBlocks(out, data)
return base64.StdEncoding.EncodeToString(out), nil
}
func pkcs7Pad(data []byte, blockSize int) []byte {
pad := blockSize - (len(data) % blockSize)
padding := make([]byte, pad)
for i := range padding {
padding[i] = byte(pad)
}
return append(data, padding...)
}
type ecbEncrypter struct {
b cipher.Block
blockSize int
}
func newECBEncrypter(b cipher.Block) *ecbEncrypter {
return &ecbEncrypter{b: b, blockSize: b.BlockSize()}
}
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("invalid padding")
}
for len(src) > 0 {
x.b.Encrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}
// DecryptBody AES-128-ECB + PKCS7 解密 Base64 密文(与 PHP AesUtilService / hy.EncryptBody 对称)。
func DecryptBody(ciphertext, token string) (string, error) {
raw, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", fmt.Errorf("base64 decode: %w", err)
}
key := DeriveTransitAESKey(token)
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
if len(raw)%block.BlockSize() != 0 {
return "", fmt.Errorf("ciphertext length not multiple of block size")
}
out := make([]byte, len(raw))
ecb := newECBDecrypter(block)
ecb.CryptBlocks(out, raw)
plain, err := pkcs7Unpad(out, block.BlockSize())
if err != nil {
return "", err
}
return string(plain), nil
}
func pkcs7Unpad(data []byte, blockSize int) ([]byte, error) {
if len(data) == 0 || len(data)%blockSize != 0 {
return nil, fmt.Errorf("invalid padding size")
}
pad := int(data[len(data)-1])
if pad == 0 || pad > blockSize || pad > len(data) {
return nil, fmt.Errorf("invalid padding")
}
for i := 0; i < pad; i++ {
if data[len(data)-1-i] != byte(pad) {
return nil, fmt.Errorf("invalid padding bytes")
}
}
return data[:len(data)-pad], nil
}
type ecbDecrypter struct {
b cipher.Block
blockSize int
}
func newECBDecrypter(b cipher.Block) *ecbDecrypter {
return &ecbDecrypter{b: b, blockSize: b.BlockSize()}
}
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("invalid padding")
}
for len(src) > 0 {
x.b.Decrypt(dst, src[:x.blockSize])
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
}