- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
导读
本指南对应《Go Web 编程》(build-web-application-with-golang)第 9.6 节「加密和解密資料」,聚焦于 Web 应用中最常见的对称加密需求:当我们需要把敏感资料(如支付凭证、接口令牌、密钥备份)加密后持久化存储,并在未来某个时刻按需解密还原时,应选用对称加密算法。读完本文,你将掌握 Go 语言中encoding/base64的简易加解密写法、crypto/aes与crypto/des高级对称加密套件的完整使用流程、cipher.Block接口的底层原理,以及基于仓库英文版演进出的 AES-GCM 现代实践建议,可直接落地到自己的 Web 服务中。
base64 加解密:最简单的资料变换方案
如果 Web 应用足够简单,对资料安全性没有严格要求,可以采用一种实现成本最低的加解密方式——base64。它本质上是编码(encoding)而非加密(encryption),并不具备密钥,任何人都能直接解码还原原文,因此只适合用于「防肉眼直读」而非「防攻击者解密」的场景。
Go 语言的encoding/base64标准包已经完整支持这一能力。下面是仓库中 zh-tw/09.6.md 提供的完整示例:
package main import ( "encoding/base64" "fmt" ) func base64Encode(src []byte) []byte { return []byte(base64.StdEncoding.EncodeToString(src)) } func base64Decode(src []byte) ([]byte, error) { return base64.StdEncoding.DecodeString(string(src)) } func main() { // encode hello := "你好,世界! hello world" debyte := base64Encode([]byte(hello)) fmt.Println(debyte) // decode enbyte, err := base64Decode(debyte) if err != nil { fmt.Println(err.Error()) } if hello != string(enbyte) { fmt.Println("hello is not equal to enbyte") } fmt.Println(string(enbyte)) }运行该程序,会先打印 base64 编码后的字符串,随后打印还原后的原文。代码的关键点在于:
base64.StdEncoding.EncodeToString(src)将[]byte编码为标准 base64 字符串;base64.StdEncoding.DecodeString(string(src))将 base64 字符串解码回[]byte,解码可能失败(输入不是合法 base64 时返回错误),所以函数签名中必须携带error返回值;- 编码与解码互为逆运算,程序中用
hello != string(enbyte)的比对来验证还原正确性。
如果你需要把编码结果放进 URL 查询参数、Cookie 或文件名中,标准库还提供了更适合的变体:base64.URLEncoding(使用-与_代替+与/,避免 URL 转义问题)、base64.RawStdEncoding与base64.RawURLEncoding(去除末尾的=填充符)。它们的使用方式与StdEncoding完全一致,只需替换对应的编码器常量。
高级加解密:Go crypto 家族的对称加密套件
当业务对安全性的要求上升,需要真正的密钥保护时,应改用对称加密算法。Go 语言的crypto标准库中提供了两类主流的对称加密套件:
crypto/aes套件:AES(Advanced Encryption Standard,高级加密标准),又称 Rijndael 加密法,是美国联邦政府采用的区块加密标准,也是当今事实上的对称加密主流选择;crypto/des套件:DES(Data Encryption Standard,资料加密标准),是一种对称加密标准,曾是保护金融资料安全中使用最广泛的密钥系统,也是美国联邦政府的旧加密标准,但如今已被 AES 取代。
由于这两种算法的使用方法高度相似(都通过NewCipher创建分组密码、再配合分组模式进行流式加解密),本节以crypto/aes为例完整演示。
AES-CFB 完整示例:加密与解密「我的名字是 Astaxie」
仓库 zh-tw/09.6.md 给出的 AES 示例支持从命令行参数传入待加密资料与密钥,便于直接运行验证:
package main import ( "crypto/aes" "crypto/cipher" "fmt" "os" ) var commonIV = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f} func main() { //需要去加密的字串 plaintext := []byte("My name is Astaxie") //如果傳入加密串的話,plaint 就是傳入的字串 if len(os.Args) > 1 { plaintext = []byte(os.Args[1]) } //aes 的加密字串 key_text := "astaxie12798akljzmknm.ahkjkljl;k" if len(os.Args) > 2 { key_text = os.Args[2] } fmt.Println(len(key_text)) // 建立加密演算法 aes c, err := aes.NewCipher([]byte(key_text)) if err != nil { fmt.Printf("Error: NewCipher(%d bytes) = %s", len(key_text), err) os.Exit(-1) } //加密字串 cfb := cipher.NewCFBEncrypter(c, commonIV) ciphertext := make([]byte, len(plaintext)) cfb.XORKeyStream(ciphertext, plaintext) fmt.Printf("%s=>%x\n", plaintext, ciphertext) // 解密字串 cfbdec := cipher.NewCFBDecrypter(c, commonIV) plaintextCopy := make([]byte, len(plaintext)) cfbdec.XORKeyStream(plaintextCopy, ciphertext) fmt.Printf("%x=>%s\n", ciphertext, plaintextCopy) }这段代码的完整执行链路可以拆解为四步:
- 确定明文与密钥:明文默认是
"My name is Astaxie",密钥默认是"astaxie12798akljzmknm.ahkjkljl;k",均可用命令行参数覆盖(go run main.go、go run main.go "你的明文"、go run main.go "你的明文" "你的密钥"); - 创建分组密码:调用
aes.NewCipher([]byte(key_text))校验密钥长度并生成cipher.Block;密钥必须为 16、24 或 32 字节的[]byte,分别对应AES-128、AES-192 或 AES-256三种算法强度,长度不符会直接返回错误并被os.Exit(-1)终止; - 加密:用
cipher.NewCFBEncrypter(c, commonIV)创建 CFB 模式的加密器,再调用XORKeyStream将明文逐块异或为密文。注意 CFB 模式需要初始化向量commonIV(示例中为 16 字节的全 0~F 序列),加密与解密必须使用同一个 IV才能正确还原; - 解密:用
cipher.NewCFBDecrypter(c, commonIV)创建 CFB 模式解密器,对同一段密文再次调用XORKeyStream,得到还原后的明文。
程序依次输出密钥长度、明文=>密文(十六进制)与密文(十六进制)=>明文,可作为对称加密「加密—解密」闭环的最小可运行参考。
cipher.Block 接口:三个方法撑起加解密核心
上面的示例中,aes.NewCipher返回的是一个cipher.Block接口,它定义了分组密码的基础能力。仓库文档完整给出了该接口的声明:
type Block interface { // BlockSize returns the cipher's block size. BlockSize() int // Encrypt encrypts the first block in src into dst. // Dst and src may point at the same memory. Encrypt(dst, src []byte) // Decrypt decrypts the first block in src into dst. // Dst and src may point at the same memory. Decrypt(dst, src []byte) }这三个方法的分工如下:
BlockSize() int:返回分组的字节数,AES 固定为 16 字节(128 bit),DES 为 8 字节,这是选择初始化向量长度、判断密文对齐的依据;Encrypt(dst, src []byte):加密第一个分组,将src写入dst,dst与src允许指向同一块内存(原地操作);Decrypt(dst, src []byte):解密第一个分组,规则同上。
需要注意的是,Block接口本身只处理「单个分组」的加解密,无法直接处理任意长度的连续资料流。因此实际项目中还要借助crypto/cipher包的分组模式——示例中的NewCFBEncrypter/NewCFBDecrypter(CFB 密码反馈模式)就是通过XORKeyStream把Block包装成可连续处理的流式密码。常用的同类包装还有 CBC(NewCBCEncrypter)、CTR(NewCTREncrypter)与 GCM(NewGCM)等模式,可按吞吐量、是否需认证等需求选择。
进阶建议:优先采用 AES-GCM 认证加密
仓库英文版文档 en/09.6.md 在演进中给出了更贴合现代安全实践的结论:如果不确定自己在做什么,除 GCM 模式(Galois/Counter Mode,伽罗瓦计数器模式)的 AES 外不要使用其他任何方案。这一建议的缘由是:CFB/CBC/CTR 等传统模式只提供机密性,不提供完整性保护,密文在传输或存储中被篡改时无法被察觉;而 GCM 是一种认证加密(AEAD)模式,在同一套运算中同时完成加密与消息认证,密文被改动会被立即识别。
仓库英文版配套的 AES-GCM 示例(见 en/09.6.md 正文)展示了推荐写法——使用随机 nonce 而非固定 IV:
func encrypt(plaintext []byte, key []byte) ([]byte, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(c) if err != nil { return nil, err } nonce := make([]byte, gcm.NonceSize()) if _, err = io.ReadFull(rand.Reader, nonce); err != nil { return nil, err } return gcm.Seal(nonce, nonce, plaintext, nil), nil } func decrypt(ciphertext []byte, key []byte) ([]byte, error) { c, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(c) if err != nil { return nil, err } nonceSize := gcm.NonceSize() if len(ciphertext) < nonceSize { return nil, errors.New("ciphertext too short") } nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] return gcm.Open(nil, nonce, ciphertext, nil) }与 CFB 示例相比,AES-GCM 写法有三个关键差异,值得在实战中沿用:
- nonce 随机生成:nonce(等价于 IV)由
rand.Reader每次随机产生,不再使用硬编码的固定值,避免相同密钥与相同 IV 重复使用导致的安全性退化; - nonce 与密文一起保存:
gcm.Seal(nonce, nonce, plaintext, nil)把 nonce 直接作为密文的前缀输出,解密时用ciphertext[:nonceSize]切分取出,因此无需额外管理 IV 的同步问题; - 内置完整性校验:
gcm.Open在解密的同时验证认证标签,密文被篡改或 nonce 不对应时会返回错误,而decrypt中的errors.New("ciphertext too short")则先防御性地拦截过短输入。
总结
本小节围绕 Web 应用中「敏感资料加密存储、按需解密」的需求,介绍了三种层次的加解密方案,可以按业务对安全性的要求灵活选用:
- base64:实现最简单,适合对安全性要求不高、仅需防直读的场景,属于编码而非加密,不具备密钥与保密性;
- AES / DES 对称加密:通过
aes.NewCipher(密钥必须为 16/24/32 字节,对应 AES-128/192/256)返回cipher.Block,再配合 CFB 等分组模式完成任意长度资料的加密与解密,适合常规敏感资料的落库保护; - AES-GCM 认证加密:现代实践的推荐方案,同时提供机密性与完整性校验,nonce 随机生成并随密文存储,可直接作为生产级实现的起点。
需要说明的是,本节的对称加密解决的是「加密后存储、按需解密」的问题;若目标是「只验证、不解密」的密码存储,则应采用上一节 9.5 存储密码 中介绍的加盐杂凑与 scrypt 方案。两种手段各司其职,共同构成 Web 应用资料安全的基础防线。
延伸阅读
- 目录
- 上一节:存储密码
- 下一节:小结
- 英文版: 9.6 Encrypting and decrypting data(含 AES-GCM 完整示例)
- 简体中文版:9.6 加密和解密数据
- 仓库配套示例代码目录:en/code/src(各章节可运行示例,运行前请参考 en/code/readme.md 设置
GOPATH)
- 文档
- 教程
【免费下载链接】build-web-application-with-golang
A golang ebook intro how to build a web with golang
相关推荐
Go Web 应用中的数据加解密实战:从 base64 编码到 AES/DES 对称加密(build-web-application-with-golang 第 9.6 节)
Go Web 应用中的数据加解密实战:从 base64 编码到 AES/DES 对称加密(build web application with golang 第
文档教程Go Web 应用对称加密实战:用 crypto/aes 与 crypto/des 实现数据加解密(build-web-application-with-golang 9.6)
Go Web 应用对称加密实战:用 crypto/aes 与 crypto/des 实现数据加解密(build web application with gol
文档教程Go Web 编程中的数据加解密:使用 crypto/aes 实现 AES-GCM 对称加密实战
Go Web 编程中的数据加解密:使用 crypto/aes 实现 AES GCM 对称加密实战 本文对应 build web application with
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考