Files
fanqie-go/internal/fqapi/register_key.go
T
chenxin 5bcc8a962e ♻️ refactor(handlers): status 与 review 页面模板化
- HTML 提取至 templates/status.html、templates/review.html(go:embed 内嵌)
- status.go 1022→271 行、review.go 1281→438 行,动态数据全部经 html/template 按上下文自动转义
- 评论循环/作者徽章/贴纸/图片条件渲染翻译为模板 range/if;emojiMap 以 template.JS 注入
- 与旧版渲染输出实测对比:22 条评论结构完全一致;差异仅为 JS 上下文安全转义(& 等)与 html/template 剥除 CSS 注释,均无功能影响
- 其余文件为 gofmt 顺手格式化
2026-09-30 00:25:09 +08:00

249 lines
5.4 KiB
Go

package fqapi
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fanqie/internal/common"
"fmt"
"math/rand"
"regexp"
"sort"
"strings"
"time"
)
// KeyInfo 密钥信息
type KeyInfo struct {
Key string `json:"key"`
Ver int `json:"ver"`
}
var (
keyInfo *KeyInfo
keyLogger = common.NewLogger("register_key", "INFO", "")
)
// rHex 将数字转换为反转的16进制字符串
func rHex(num int64) string {
hexStr := fmt.Sprintf("%032x", num)
result := ""
for i := len(hexStr); i > 0; i -= 2 {
result += hexStr[i-2 : i]
}
return result
}
// createSymmetricCrypto 创建对称加密器
func createSymmetricCrypto(algorithm string, key, iv []byte) (func([]byte) ([]byte, error), func([]byte) ([]byte, error), error) {
if algorithm != "AES/CBC/PKCS5Padding" {
return nil, nil, fmt.Errorf("不支持的加密算法")
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, nil, err
}
encrypt := func(data []byte) ([]byte, error) {
// PKCS7填充
padding := aes.BlockSize - len(data)%aes.BlockSize
padText := make([]byte, padding)
for i := range padText {
padText[i] = byte(padding)
}
paddedData := append(data, padText...)
ciphertext := make([]byte, len(paddedData))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, paddedData)
return ciphertext, nil
}
decrypt := func(data []byte) ([]byte, error) {
mode := cipher.NewCBCDecrypter(block, iv)
decrypted := make([]byte, len(data))
mode.CryptBlocks(decrypted, data)
// 移除PKCS7填充
padding := int(decrypted[len(decrypted)-1])
if padding > len(decrypted) || padding > aes.BlockSize {
return nil, fmt.Errorf("无效的填充")
}
return decrypted[:len(decrypted)-padding], nil
}
return encrypt, decrypt, nil
}
// En AES加密
func En(data, key, iv []byte) ([]byte, error) {
encrypt, _, err := createSymmetricCrypto("AES/CBC/PKCS5Padding", key, iv)
if err != nil {
return nil, err
}
return encrypt(data)
}
// De AES解密
func De(data, key, iv []byte) ([]byte, error) {
_, decrypt, err := createSymmetricCrypto("AES/CBC/PKCS5Padding", key, iv)
if err != nil {
return nil, err
}
return decrypt(data)
}
// randomStringKey 生成随机字符串(密钥用)
func randomStringKey(length int) string {
const charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
// GetKey 获取密钥
func GetKey(header string) (*KeyInfo, error) {
if header == "" {
keyLogger.Error("请先注册设备")
return nil, fmt.Errorf("请先注册设备")
}
key, _ := base64.StdEncoding.DecodeString("rCXGfd2POMGzeiNIgo4iLg==")
iv := []byte(randomStringKey(16))
// 提取device_id
re := regexp.MustCompile(`device_id=(\d+)`)
matches := re.FindStringSubmatch(header)
if len(matches) < 2 {
return nil, fmt.Errorf("无法从header中提取device_id")
}
var deviceIDInt int64
fmt.Sscanf(matches[1], "%d", &deviceIDInt)
// 转换device_id
hexStr := rHex(deviceIDInt)
data, _ := hex.DecodeString(hexStr)
keyLogger.Debug(fmt.Sprintf("rKey: %v", iv))
// 加密
encrypted, err := En(data, key, iv)
if err != nil {
return nil, err
}
// 组合iv和加密数据
newArr := append(iv, encrypted...)
content := base64.StdEncoding.EncodeToString(newArr)
keyLogger.Info("正在获取密钥...")
// 构建参数
params := []string{
"aid=1967",
"app_name=novelapp",
"channel=0",
"device_platform=android",
"os_version=0",
"version_code=5.8.9.32",
header,
}
allParams := strings.Join(params, "&")
paramList := strings.Split(allParams, "&")
sort.Strings(paramList)
sortedParams := strings.Join(paramList, "&")
// 准备请求体
requestBody := map[string]string{
"content": content,
}
bodyJSON, _ := json.Marshal(requestBody)
// gzip压缩
var gzipBuf bytes.Buffer
gzipWriter := gzip.NewWriter(&gzipBuf)
gzipWriter.Write(bodyJSON)
gzipWriter.Close()
reqURL := "https://reading.snssdk.com/reading/crypt/registerkey?" + sortedParams
resp, err := GorgonRequest(reqURL, map[string]interface{}{
"method": "POST",
"body": gzipBuf.Bytes(),
"headers": map[string]string{
"User-Agent": "com.dragon.read",
"Content-Encoding": "gzip",
},
})
if err != nil {
keyLogger.Error("获取密钥失败: " + err.Error())
return nil, err
}
if resp.Status == 200 {
var respData map[string]interface{}
json.Unmarshal(resp.Content, &respData)
dataObj, ok := respData["data"].(map[string]interface{})
if !ok {
return nil, fmt.Errorf("响应格式错误")
}
keyStr, ok := dataObj["key"].(string)
if !ok {
return nil, fmt.Errorf("密钥字段不存在")
}
// 解码返回的密钥
res, _ := base64.StdEncoding.DecodeString(keyStr)
if len(res) < 32 {
return nil, fmt.Errorf("密钥长度不足")
}
data := res[16:]
ivResp := res[0:16]
// 解密密钥
decryptedKey, err := De(data, key, ivResp)
if err != nil {
return nil, err
}
finalKey := base64.StdEncoding.EncodeToString(decryptedKey)
keyVer, _ := dataObj["keyver"].(float64)
keyInfo = &KeyInfo{
Key: finalKey,
Ver: int(keyVer),
}
keyLogger.Info(fmt.Sprintf("密钥获取成功!"))
return keyInfo, nil
}
return nil, fmt.Errorf("获取密钥失败")
}
// GetKeyInfo 获取当前密钥信息
func GetKeyInfo() *KeyInfo {
return keyInfo
}
func init() {
rand.Seed(time.Now().UnixNano())
}