mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 21:08:01 +02:00
155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package c2
|
||
|
||
import (
|
||
"crypto/aes"
|
||
"crypto/cipher"
|
||
"crypto/rand"
|
||
"encoding/base64"
|
||
"errors"
|
||
"io"
|
||
)
|
||
|
||
// AES-256-GCM 信封:每个 Listener 独立 32 字节密钥 + 每条消息独立 12 字节 nonce。
|
||
// 协议格式(base64 文本,便于 HTTP body / SSE 直接传):
|
||
// base64( nonce(12) || ciphertext+tag )
|
||
// 设计要点:
|
||
// - GCM 自带 16 字节 AEAD tag,完整性 + 机密性一次性搞定,无需额外 HMAC;
|
||
// - nonce 由 crypto/rand 生成,96bit 在密钥不变期内重复概率极低(< 2^-32 / 4B 次);
|
||
// - 密钥不出服务端:listener 创建时随机生成 32 字节,编译 beacon 时硬编码进去。
|
||
|
||
// GenerateAESKey 生成随机 32 字节 AES-256 密钥并 base64 输出
|
||
func GenerateAESKey() (string, error) {
|
||
key := make([]byte, 32)
|
||
if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
||
return "", err
|
||
}
|
||
return base64.StdEncoding.EncodeToString(key), nil
|
||
}
|
||
|
||
// GenerateImplantToken 生成 32 字节 token,base64 编码(implant 携带在 HTTP header 鉴权用)
|
||
func GenerateImplantToken() (string, error) {
|
||
t := make([]byte, 32)
|
||
if _, err := io.ReadFull(rand.Reader, t); err != nil {
|
||
return "", err
|
||
}
|
||
return base64.RawURLEncoding.EncodeToString(t), nil
|
||
}
|
||
|
||
// EncryptAESGCM 加密任意明文,返回 base64(nonce||ct)
|
||
func EncryptAESGCM(keyB64 string, plaintext []byte) (string, error) {
|
||
key, err := decodeKey(keyB64)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
nonce := make([]byte, gcm.NonceSize())
|
||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||
return "", err
|
||
}
|
||
ct := gcm.Seal(nil, nonce, plaintext, nil)
|
||
out := append(nonce, ct...)
|
||
return base64.StdEncoding.EncodeToString(out), nil
|
||
}
|
||
|
||
// DecryptAESGCM 解密 base64(nonce||ct),返回明文
|
||
func DecryptAESGCM(keyB64, encB64 string) ([]byte, error) {
|
||
key, err := decodeKey(keyB64)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
raw, err := base64.StdEncoding.DecodeString(encB64)
|
||
if err != nil {
|
||
return nil, errors.New("ciphertext base64 invalid")
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
nonceSize := gcm.NonceSize()
|
||
if len(raw) < nonceSize+16 { // 至少 nonce + tag
|
||
return nil, errors.New("ciphertext too short")
|
||
}
|
||
nonce, ct := raw[:nonceSize], raw[nonceSize:]
|
||
pt, err := gcm.Open(nil, nonce, ct, nil)
|
||
if err != nil {
|
||
return nil, errors.New("aead open failed (key mismatch or tampered)")
|
||
}
|
||
return pt, nil
|
||
}
|
||
|
||
// EncryptAESGCMWithAAD encrypts with additional authenticated data bound to context (e.g. session_id).
|
||
// Prevents cross-session replay: ciphertext from session A cannot be fed to session B.
|
||
func EncryptAESGCMWithAAD(keyB64 string, plaintext []byte, aad []byte) (string, error) {
|
||
key, err := decodeKey(keyB64)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
nonce := make([]byte, gcm.NonceSize())
|
||
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
||
return "", err
|
||
}
|
||
ct := gcm.Seal(nil, nonce, plaintext, aad)
|
||
out := append(nonce, ct...)
|
||
return base64.StdEncoding.EncodeToString(out), nil
|
||
}
|
||
|
||
// DecryptAESGCMWithAAD decrypts with AAD verification.
|
||
func DecryptAESGCMWithAAD(keyB64, encB64 string, aad []byte) ([]byte, error) {
|
||
key, err := decodeKey(keyB64)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
raw, err := base64.StdEncoding.DecodeString(encB64)
|
||
if err != nil {
|
||
return nil, errors.New("ciphertext base64 invalid")
|
||
}
|
||
block, err := aes.NewCipher(key)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
gcm, err := cipher.NewGCM(block)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
nonceSize := gcm.NonceSize()
|
||
if len(raw) < nonceSize+16 {
|
||
return nil, errors.New("ciphertext too short")
|
||
}
|
||
nonce, ct := raw[:nonceSize], raw[nonceSize:]
|
||
pt, err := gcm.Open(nil, nonce, ct, aad)
|
||
if err != nil {
|
||
return nil, errors.New("aead open failed (key mismatch, tampered, or AAD mismatch)")
|
||
}
|
||
return pt, nil
|
||
}
|
||
|
||
func decodeKey(keyB64 string) ([]byte, error) {
|
||
key, err := base64.StdEncoding.DecodeString(keyB64)
|
||
if err != nil {
|
||
return nil, errors.New("key base64 invalid")
|
||
}
|
||
if len(key) != 32 {
|
||
return nil, errors.New("key must be 32 bytes (AES-256)")
|
||
}
|
||
return key, nil
|
||
}
|