123 lines
3.0 KiB
Go
123 lines
3.0 KiB
Go
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:]
|
||
}
|
||
}
|