feat(channels/weixin_ilink): 微信 iLink 渠道插件(扫码登录+长轮询+游标+context_token+AES-128-ECB 媒体+24h 主动推送窗口)
This commit is contained in:
1 parent
7831b0ea1b
commit
88dc12466d
13 files changed
+2627
No files matched your search
@@ -0,0 +1,79 @@
|
||||
package weixin_ilink
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrBadCiphertext is returned when ciphertext is not a whole number of AES
|
||||
// blocks or its padding is malformed.
|
||||
var ErrBadCiphertext = errors.New("weixin_ilink: bad ciphertext or padding")
|
||||
|
||||
// pkcs7Pad appends PKCS#7 padding to src so its length becomes a multiple of
|
||||
// blockSize. Every appended byte holds the pad length, so it self-describes
|
||||
// and Unpad can strip it unambiguously.
|
||||
func pkcs7Pad(src []byte, blockSize int) []byte {
|
||||
n := blockSize - len(src)%blockSize
|
||||
out := make([]byte, len(src)+n)
|
||||
copy(out, src)
|
||||
for i := len(src); i < len(out); i++ {
|
||||
out[i] = byte(n)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// pkcs7Unpad strips PKCS#7 padding from src. It returns ErrBadCiphertext when
|
||||
// the length is not a block multiple or the final pad byte is inconsistent.
|
||||
func pkcs7Unpad(src []byte, blockSize int) ([]byte, error) {
|
||||
if len(src) == 0 || len(src)%blockSize != 0 {
|
||||
return nil, ErrBadCiphertext
|
||||
}
|
||||
n := int(src[len(src)-1])
|
||||
if n < 1 || n > blockSize {
|
||||
return nil, ErrBadCiphertext
|
||||
}
|
||||
for _, b := range src[len(src)-n:] {
|
||||
if int(b) != n {
|
||||
return nil, ErrBadCiphertext
|
||||
}
|
||||
}
|
||||
return src[:len(src)-n], nil
|
||||
}
|
||||
|
||||
// aesECBEncrypt encrypts src under AES with the supplied key using ECB mode.
|
||||
// Go's crypto/cipher package does not ship ECB, so the mode is implemented
|
||||
// here: src is padded to a block multiple and every block is encrypted
|
||||
// independently with the same key. ECB is deterministic and not semantically
|
||||
// secure; it is required here only because the iLink CDN protocol mandates it
|
||||
// for media payloads.
|
||||
func aesECBEncrypt(key, src []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weixin_ilink: new aes cipher: %w", err)
|
||||
}
|
||||
padded := pkcs7Pad(src, block.BlockSize())
|
||||
out := make([]byte, len(padded))
|
||||
for i := 0; i < len(padded); i += block.BlockSize() {
|
||||
block.Encrypt(out[i:i+block.BlockSize()], padded[i:i+block.BlockSize()])
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// aesECBDecrypt decrypts src under AES with the supplied key and strips the
|
||||
// PKCS#7 padding. ECB has no IV, so the first block decrypts directly with the
|
||||
// key. It returns ErrBadCiphertext for malformed input.
|
||||
func aesECBDecrypt(key, src []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("weixin_ilink: new aes cipher: %w", err)
|
||||
}
|
||||
if len(src) == 0 || len(src)%block.BlockSize() != 0 {
|
||||
return nil, ErrBadCiphertext
|
||||
}
|
||||
out := make([]byte, len(src))
|
||||
for i := 0; i < len(src); i += block.BlockSize() {
|
||||
block.Decrypt(out[i:i+block.BlockSize()], src[i:i+block.BlockSize()])
|
||||
}
|
||||
return pkcs7Unpad(out, block.BlockSize())
|
||||
}
|
||||
Reference in new issue
Block a user