Files
cpdaily_xiaozhushou/backed/internal/service/cpdaily/crypto.go
T
becomer 2e2c161770 feat: init cpdaily-xiaozhushou with blue-green deploy
- backed/: Go Fiber backend with cpdaily integration
- frondend/: Vue 3 + Vite frontend
- Dockerfile: multi-stage build (Node → Go → Alpine)
- deploy/docker-compose.yml: production compose file
- .woodpecker/build.yml: CI/CD with blue-green zero-downtime deploy
- /health endpoint for deployment health checks
- Port 8884/8885 blue-green swap via 1Panel OpenResty
2026-07-03 15:37:55 +08:00

241 lines
5.7 KiB
Go

package cpdaily
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/des"
"crypto/rand"
"encoding/base64"
"encoding/hex"
"fmt"
"math/big"
"strings"
)
// ========== AES-128-CBC-PKCS7 ==========
var (
AesKey = []byte("fafUY9659kyengTR")
AesKeyForCat = []byte("hru89rwr87Nthifa")
AesIv = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7}
)
func AesEncrypt(data []byte) []byte {
return aesEncrypt(data, AesKey, AesIv)
}
func AesDecrypt(data []byte) []byte {
return aesDecrypt(data, AesKey, AesIv)
}
func AesEncryptForCat(data []byte) []byte {
return aesEncrypt(data, AesKeyForCat, AesIv)
}
func AesDecryptForCat(data []byte) []byte {
return aesDecrypt(data, AesKeyForCat, AesIv)
}
func aesEncrypt(data, key, iv []byte) []byte {
block, _ := aes.NewCipher(key)
padded := pkcs7Pad(data, block.BlockSize())
ciphertext := make([]byte, len(padded))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, padded)
return ciphertext
}
func aesDecrypt(data, key, iv []byte) []byte {
block, _ := aes.NewCipher(key)
plaintext := make([]byte, len(data))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(plaintext, data)
return pkcs7Unpad(plaintext)
}
func pkcs7Pad(data []byte, blockSize int) []byte {
padding := blockSize - len(data)%blockSize
padText := bytes.Repeat([]byte{byte(padding)}, padding)
return append(data, padText...)
}
func pkcs7Unpad(data []byte) []byte {
if len(data) == 0 {
return data
}
padLen := int(data[len(data)-1])
if padLen > len(data) || padLen == 0 {
return data
}
return data[:len(data)-padLen]
}
// ========== CAS 登录密码加密 ==========
// EncryptCasPassword AES-128-CBC 加密密码
// key=salt(从页面提取), iv=16位随机, 明文=64位随机字符串+密码, 输出base64
func EncryptCasPassword(password, salt string) string {
const randStrLen = 64
const randIvLen = 16
randomIv := randomString(randIvLen)
randomStr := randomString(randStrLen)
plaintext := []byte(randomStr + password)
key := []byte(salt)
iv := []byte(randomIv)
encrypted := aesEncrypt(plaintext, key, iv)
return base64.StdEncoding.EncodeToString(encrypted)
}
// ========== RSA (IAP 登录用) ==========
// 硬编码 IAP RSA 公钥
const IapRsaPubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCpbRy8ZoyQvRPpUDIXycglTwVDcYrLcv7P9HI4E7/TAPZiI3GN0ckTUVVWRvoo/re3uCOOJK+F+Ufh19XiRIdoPYCULsSyEjcLLiyAS09mVOzhAQco84E/lM24T7rRTVID7LIgWPewyN8Nd5vzHET4Q3qTthoL8n82BwC1iXuVQIDAQAB"
type RSAKeyPair struct {
n *big.Int
e *big.Int
}
func NewRSAKeyPairFromPublicKey(base64Key string) (*RSAKeyPair, error) {
der, err := base64.StdEncoding.DecodeString(base64Key)
if err != nil {
return nil, fmt.Errorf("base64 decode error: %w", err)
}
// 简单的 DER 解析 (PKCS#1 Public Key)
// 跳过 ASN.1 头部,提取 n 和 e
return parsePKCS1PublicKey(der)
}
func parsePKCS1PublicKey(der []byte) (*RSAKeyPair, error) {
// 简化的 PKCS#1 解析
// 实际上应该用 crypto/x509,但为了轻量级,手动解析
// 格式: SEQUENCE { SEQUENCE { OID, NULL }, BIT STRING { SEQUENCE { INTEGER n, INTEGER e } } }
// 或者直接 PKCS#1: SEQUENCE { INTEGER n, INTEGER e }
// 找最后的两个 INTEGER
data := string(der)
nParts := strings.Split(data, "\x02")
if len(nParts) < 3 {
return nil, fmt.Errorf("invalid RSA key format")
}
// 提取 n (倒数第二个 INTEGER)
nBytes := extractIntegerBytes(nParts[len(nParts)-2])
// 提取 e (最后一个 INTEGER)
eBytes := extractIntegerBytes(nParts[len(nParts)-1])
r := &RSAKeyPair{
n: new(big.Int).SetBytes(nBytes),
e: new(big.Int).SetBytes(eBytes),
}
return r, nil
}
func extractIntegerBytes(s string) []byte {
if len(s) == 0 {
return nil
}
// 第一个字节是长度
data := []byte(s)
if len(data) < 2 {
return data
}
length := int(data[0])
if length > 128 {
// 长格式
numLenBytes := length & 0x7F
length = 0
for i := 1; i <= numLenBytes; i++ {
length = length*256 + int(data[i])
}
data = data[1+numLenBytes:]
} else {
data = data[1:]
}
if length > len(data) {
length = len(data)
}
return data[:length]
}
func (r *RSAKeyPair) Encrypt(plaintext string) (string, error) {
// PKCS#1 v1.5 加密
k := (r.n.BitLen() + 7) / 8
if k < 12 {
return "", fmt.Errorf("key too short")
}
// 填充: 0x00 || 0x02 || PS || 0x00 || M
// PS 是随机非零字节
plainBytes := []byte(plaintext)
if len(plainBytes) > k-11 {
return "", fmt.Errorf("plaintext too long")
}
padded := make([]byte, k)
padded[0] = 0x00
padded[1] = 0x02
// PS: 随机非零字节
for i := 2; i < k-len(plainBytes)-1; i++ {
bt := byte(0)
for bt == 0 {
b := make([]byte, 1)
rand.Read(b)
bt = b[0]
}
padded[i] = bt
}
padded[k-len(plainBytes)-1] = 0x00
copy(padded[k-len(plainBytes):], plainBytes)
// c = m^e mod n
m := new(big.Int).SetBytes(padded)
c := new(big.Int).Exp(m, r.e, r.n)
result := c.Text(16)
if len(result)%2 != 0 {
result = "0" + result
}
return result, nil
}
// ========== 工具函数 ==========
func randomString(length int) string {
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
result := make([]byte, length)
for i := 0; i < length; i++ {
n, _ := rand.Read(make([]byte, 1))
result[i] = chars[int(n)%len(chars)]
}
return string(result)
}
func randomStringV2(length int) string {
b := make([]byte, length)
rand.Read(b)
return hex.EncodeToString(b)[:length]
}
// ========== DES-CBC-PKCS7 (用于 Cpdaily-Extension) ==========
var (
DesKeyForCat = []byte("b3L26XNL")
DesIv = []byte{1, 2, 3, 4, 5, 6, 7, 8}
)
func DesEncryptForCat(data []byte) []byte {
block, _ := des.NewCipher(DesKeyForCat)
padded := pkcs7Pad(data, block.BlockSize())
ciphertext := make([]byte, len(padded))
mode := cipher.NewCBCEncrypter(block, DesIv)
mode.CryptBlocks(ciphertext, padded)
return ciphertext
}