2e2c161770
- 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
241 lines
5.7 KiB
Go
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
|
|
}
|