You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Golang中加密/解密动态SQL并生成固定长度加密结果?

可以实现,以下是具体方案和代码示例

你的需求核心是将任意长度的SQL语句加密为固定长度字符串,同时支持解密还原。之前的示例因直接对明文加密,导致密文长度随明文变化,要解决这个问题,需结合「无损压缩+固定长度填充+对称加密」的组合方案:

核心思路

  1. 压缩原始SQL:用无损压缩算法(如zlib)把可变长度的SQL压缩为更小的字节流,减少后续加密的基础长度。
  2. 固定长度填充:将压缩后的字节流填充到预先定义的固定长度(需结合数据库字段的字节限制计算,比如255字符的Base64字段对应约191字节原始数据)。若压缩后的长度超过阈值,直接抛出错误(无法在固定内容纳且保证无损)。
  3. 对称加密:使用带认证的对称加密算法(如AES-GCM)对填充后的固定长度数据加密,将随机生成的IV(初始化向量)和加密标签与密文拼接,最后转成Base64字符串,确保总长度固定。

Golang 代码实现

package main

import (
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"compress/zlib"
)

// 配置参数,根据数据库字段限制调整
const (
	maxPlaintextBytes = 163 // 预留IV(12字节)+标签(16字节)空间,总加密字节数163+28=191,Base64编码后约255字符
	keySize           = 32   // AES-256密钥长度
)

// EncryptSQL 输入SQL字符串,输出固定长度Base64加密串
func EncryptSQL(sql string, key []byte) (string, error) {
	if len(key) != keySize {
		return "", errors.New("密钥长度必须为32字节(AES-256)")
	}

	// 1. 压缩SQL
	var compressedBuf bytes.Buffer
	w := zlib.NewWriter(&compressedBuf)
	if _, err := w.Write([]byte(sql)); err != nil {
		return "", err
	}
	if err := w.Close(); err != nil {
		return "", err
	}
	compressedBytes := compressedBuf.Bytes()

	// 2. 检查压缩后长度是否超限
	if len(compressedBytes) > maxPlaintextBytes {
		return "", errors.New("SQL语句过长,压缩后仍超出固定长度限制")
	}

	// 3. 填充到固定长度(用0填充,也可使用PKCS#7填充)
	paddedBytes := make([]byte, maxPlaintextBytes)
	copy(paddedBytes, compressedBytes)

	// 4. AES-GCM加密
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	// 生成随机IV(GCM推荐12字节长度)
	iv := make([]byte, gcm.NonceSize())
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		return "", err
	}

	// 加密:最终密文 = IV + 加密数据 + 认证标签
	ciphertext := gcm.Seal(iv, iv, paddedBytes, nil)

	// 转Base64得到固定长度字符串
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// DecryptSQL 输入固定长度加密串,输出原始SQL
func DecryptSQL(encryptedStr string, key []byte) (string, error) {
	if len(key) != keySize {
		return "", errors.New("密钥长度必须为32字节(AES-256)")
	}

	// 解码Base64
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedStr)
	if err != nil {
		return "", err
	}

	// AES-GCM解密
	block, err := aes.NewCipher(key)
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonceSize := gcm.NonceSize()
	if len(ciphertext) < nonceSize {
		return "", errors.New("加密串格式错误")
	}

	iv, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
	paddedBytes, err := gcm.Open(nil, iv, ciphertext, nil)
	if err != nil {
		return "", err
	}

	// 去掉填充的0,还原压缩后的字节流
	compressedBytes := bytes.TrimRight(paddedBytes, "\x00")

	// 解压SQL
	var decompressedBuf bytes.Buffer
	r, err := zlib.NewReader(bytes.NewReader(compressedBytes))
	if err != nil {
		return "", err
	}
	if _, err := io.Copy(&decompressedBuf, r); err != nil {
		return "", err
	}
	if err := r.Close(); err != nil {
		return "", err
	}

	return decompressedBuf.String(), nil
}

func main() {
	// 示例密钥(实际使用需从安全配置读取,禁止硬编码)
	key := make([]byte, keySize)
	if _, err := io.ReadFull(rand.Reader, key); err != nil {
		panic(err)
	}

	// 测试SQL
	sql := "SELECT id FROM table WHERE column_a = value_a AND column_b IN (value_b, value_c)"
	encrypted, err := EncryptSQL(sql, key)
	if err != nil {
		fmt.Println("加密失败:", err)
		return
	}
	fmt.Printf("加密后(固定长度):%s,长度:%d\n", encrypted, len(encrypted))

	decrypted, err := DecryptSQL(encrypted, key)
	if err != nil {
		fmt.Println("解密失败:", err)
		return
	}
	fmt.Printf("解密后:%s\n", decrypted)
}

关键注意事项

  • 长度适配:需根据数据库字段的实际字符限制调整maxPlaintextBytes,Base64编码后长度公式为:ceil(总字节数 / 3) * 4,确保最终编码后的字符串长度不超过字段限制。
  • 安全规范:密钥必须妥善保管(如用密钥管理系统),禁止硬编码;IV每次加密都要随机生成,避免重复使用同一IV和密钥导致安全漏洞。
  • 超长处理:若SQL压缩后仍超过固定长度限制,需在业务层面处理(如拆分任务、扩大数据库字段),因为无损加密无法将更长的数据压缩到更小的固定空间。

内容的提问来源于stack exchange,提问作者fr4n03

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 13:10:59