package main import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/md5" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "errors" "fmt" "io" "net/http" "os" "time" ) func sha256Sum(b []byte) []byte { sum := sha256.Sum256(b) return sum[:] } func md5Hex(s string) string { h := md5.Sum([]byte(s)) return hex.EncodeToString(h[:]) } func deriveKey(secret string) []byte { return sha256Sum([]byte(secret)) // 32 bytes for AES-256 } func deriveIV(secret, nonce string) []byte { ivFull := sha256Sum([]byte(md5Hex(nonce) + secret)) // 32 bytes return ivFull[:aes.BlockSize] // 16 bytes IV } func pkcs7Pad(data []byte, blockSize int) []byte { pad := blockSize - len(data)%blockSize padding := bytes.Repeat([]byte{byte(pad)}, pad) return append(data, padding...) } func pkcs7Unpad(data []byte) ([]byte, error) { if len(data) == 0 { return nil, errors.New("invalid data length") } pad := int(data[len(data)-1]) if pad <= 0 || pad > aes.BlockSize || pad > len(data) { return nil, errors.New("invalid padding") } for i := 0; i < pad; i++ { if data[len(data)-1-i] != byte(pad) { return nil, errors.New("invalid padding content") } } return data[:len(data)-pad], nil } func genNonce() string { return fmt.Sprintf("%x", time.Now().UnixNano()) } func encryptPayload(plain map[string]interface{}, secret string) ([]byte, string, error) { nonce := genNonce() key := deriveKey(secret) iv := deriveIV(secret, nonce) b, err := json.Marshal(plain) if err != nil { return nil, "", err } block, err := aes.NewCipher(key) if err != nil { return nil, "", err } mode := cipher.NewCBCEncrypter(block, iv) padded := pkcs7Pad(b, aes.BlockSize) cipherText := make([]byte, len(padded)) mode.CryptBlocks(cipherText, padded) wrapper := map[string]string{ "data": base64.StdEncoding.EncodeToString(cipherText), "time": nonce, } out, err := json.Marshal(wrapper) return out, nonce, err } func decryptResponseBody(respBody []byte, secret string) (map[string]interface{}, error) { var top map[string]interface{} if err := json.Unmarshal(respBody, &top); err != nil { return nil, err } // 响应格式可能是: // { "code": 0, "msg": "ok", "data": { "data": "...", "time": "..." } } // 或者直接就是 { "data": "...", "time": "..." } var wrapper map[string]interface{} if v, ok := top["data"].(map[string]interface{}); ok && v["data"] != nil && v["time"] != nil { wrapper = v } else { wrapper = top } cipherB64, _ := wrapper["data"].(string) nonce, _ := wrapper["time"].(string) if cipherB64 == "" || nonce == "" { return nil, errors.New("response missing data/time fields") } key := deriveKey(secret) iv := deriveIV(secret, nonce) cipherBytes, err := base64.StdEncoding.DecodeString(cipherB64) if err != nil { return nil, err } block, err := aes.NewCipher(key) if err != nil { return nil, err } mode := cipher.NewCBCDecrypter(block, iv) plainPadded := make([]byte, len(cipherBytes)) mode.CryptBlocks(plainPadded, cipherBytes) plain, err := pkcs7Unpad(plainPadded) if err != nil { return nil, err } var out map[string]interface{} if err := json.Unmarshal(plain, &out); err != nil { return nil, err } return out, nil } func bindEmailWithPassword(serverURL, secret, token, email, password, userAgent string) error { plain := map[string]interface{}{ "email": email, "password": password, } body, _, err := encryptPayload(plain, secret) if err != nil { return err } req, err := http.NewRequest("POST", serverURL+"/v1/public/user/bind_email_with_password", bytes.NewReader(body)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", token) req.Header.Set("Login-Type", "device") req.Header.Set("User-Agent", userAgent) resp, err := http.DefaultClient.Do(req) if err != nil { return err } defer resp.Body.Close() respBytes, _ := io.ReadAll(resp.Body) fmt.Println("[绑定邮箱响应]", resp.StatusCode, string(respBytes)) if resp.StatusCode != http.StatusOK { return fmt.Errorf("bind_email_with_password failed: %s", string(respBytes)) } return nil } func main() { secret := "c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx" serverURL := "http://localhost:8080" identifier := "AP4A.241205.014" userAgent := "ppanel-go-test/1.0" plain := map[string]interface{}{ "identifier": identifier, "user_agent": userAgent, } body, _, err := encryptPayload(plain, secret) if err != nil { fmt.Println("加密失败:", err) os.Exit(2) } req, err := http.NewRequest("POST", serverURL+"/v1/auth/login/device", bytes.NewReader(body)) if err != nil { fmt.Println("请求创建失败:", err) os.Exit(3) } req.Header.Set("Login-Type", "device") req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", userAgent) req.Header.Set("X-Original-Forwarded-For", "127.0.0.1") resp, err := http.DefaultClient.Do(req) if err != nil { fmt.Println("请求失败:", err) os.Exit(4) } defer resp.Body.Close() respBytes, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("读取响应失败:", err) os.Exit(5) } fmt.Println("[加密响应原文]", string(respBytes)) decrypted, err := decryptResponseBody(respBytes, secret) if err != nil { fmt.Println("解密失败:", err) os.Exit(6) } fmt.Println("[解密响应明文]", decrypted) var token string if t, ok := decrypted["token"].(string); ok && t != "" { token = t fmt.Println("✅ 登录成功,token =", token) } else { fmt.Println("⚠️ 未获取到 token") } // 新增:根据邮箱密码绑定设备号(需提供 EMAIL 和 PASSWORD 环境变量) email := "client@qq.com" password := "123456" if token != "" && email != "" && password != "" { if err := bindEmailWithPassword(serverURL, secret, token, email, password, userAgent); err != nil { fmt.Println("绑定邮箱失败:", err) } else { fmt.Println("✅ 绑定邮箱成功") } } else { fmt.Println("跳过绑定:缺少 token、EMAIL 或 PASSWORD") } }