参数化查询是防范SQL注入的唯一可靠方式,必须用?(MySQL/SQLite)或$1(PostgreSQL)占位符传参,禁用字符串拼接;动态表名、列名等标识符须白名单校验,不可参数化。

用 database/sql 参数化查询防 SQL 注入,不是“加不加密”的问题
SQL 注入根本不是靠“加密”解决的——它是输入控制失效导致的执行逻辑污染。Golang 标准库的 database/sql 本身不提供“SQL 加密”,但通过正确使用 Query、Exec 的参数化接口,能从源头杜绝注入。
- 错误写法:
db.Query("SELECT * FROM users WHERE name = '" + name + "'")—— 字符串拼接直接暴露漏洞 - 正确写法:
db.Query("SELECT * FROM users WHERE name = ?", name)(MySQL/SQLite)或$1(PostgreSQL),由驱动自动转义并绑定为独立参数 - ORM 如
gorm或sqlc同样依赖底层参数化机制,但需确认没开启AllowGlobalUpdate或手写原始 SQL 时绕过绑定 - 注意:
IN子句、表名、列名无法参数化,必须白名单校验或映射转换,不能靠“加密字段名”糊弄
cipher.AES + cipher.NewGCM 是文件/字段加密的事实标准
对敏感字段(如身份证、手机号)或整个文件做加密,Go 标准库的 AES-GCM 组合是目前最稳妥的选择——它同时提供机密性与完整性校验(AEAD),比自己拼 CBC + HMAC 少踩十倍坑。
- 密钥必须是 32 字节(AES-256),且绝不能硬编码;推荐用
scrypt.Key()从密码派生,或从 Vault/KMS 获取 - nonce 必须每次唯一,GCM 最佳长度是 12 字节,用
rand.Read(nonce)生成,不要复用 - 加密后数据结构建议为:
[salt(16B)][nonce(12B)][ciphertext+tag],解密时严格按偏移读取,且必须检查err != nil,不能只看返回长度 - 别对 JSON/gob 序列化后的字节流再加密——序列化输出不稳定(尤其 gob 含类型头),会导致相同数据加密结果不同,破坏确定性验证
密码存储必须用 bcrypt 或 scrypt,不是 AES
用户密码不是“要解密回来”的数据,而是单向验证场景。用 AES 加密密码等于埋雷:一旦密钥泄露,所有密码瞬间可逆。
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 正确做法:用
golang.org/x/crypto/bcrypt的GenerateFromPassword生成哈希,用CompareHashAndPassword校验 - bcrypt 自带 salt 和 cost 参数(推荐
cost=12),无需手动管理 salt 存储;scrypt更抗 GPU 暴力,但内存开销大,适合高安全等级场景 - 绝对避免:
md5、sha1、sha256(password+salt)这类快速哈希——现代 GPU 一秒可试数亿次 - 如果业务强制要求“找回密码”,说明设计有误;应改用“重置密码”流程,而非解密明文
TLS 和字段级加密不是二选一,而是分层覆盖
传输中加密(TLS)和静态数据加密(字段/AES)解决的是不同环节的风险,漏掉任一层都可能被绕过。
立即学习“go语言免费学习笔记(深入)”;
- TLS 只保护链路,数据库磁盘被拖库后,没加密的字段直接裸奔;字段加密后没 TLS,中间人仍可截获 API 请求里的密文(虽无法解密,但可能重放或篡改)
- PostgreSQL 支持透明数据加密(TDE),但 Go 应用层仍需对超敏字段(如银行卡 CVV)做二次加密——TDE 不覆盖内存和日志,而应用层加密可控粒度
- 注意:加密字段后,
LIKE、ORDER BY、索引等数据库能力基本失效,得用 tokenization 或 search-as-you-type 的替代方案,这点常被忽略
实际最难的不是选算法,而是密钥生命周期管理——生成、分发、轮换、销毁。用硬编码密钥、把密钥写进 config、或在日志里打密文+密钥组合,比选错 AES 模式危险得多。

















