go-zero 自带一个 API 签名机制[1] 但语焉不详。本文就来说说其细节。
如何配置
签名当然要用到非对称加密,需要准备好公私钥一对。生成的命令[2]为
# 生成私钥,保存为 private.pem 文件
openssl genrsa -traditional -out private.pem 2048
# 生成公钥,保存为 public.pem 文件
openssl rsa -in private.pem -outform PEM -pubout -out public.pem
加 -traditional
的原因[3]是咱们需要生成旧格式的私钥[4]给 x509.ParsePKCS1PrivateKey
使用。
为需要签名的 API 打开开关,参见官方文档不再赘述,下面以打开了签名开关的 API /user/modify
为例。
在配置文件里加上签名相关的配置
Signature:
Strict: true
PrivateKeys:
- Fingerprint: 6PZ+6+kq8CaH4q0k/9zqjg==
KeyFile: etc/private.pem
-
Strict: true
启用严格模式,此时签名不正确的请求,会返回 403 Forbidden -
可以配置多套公私钥,对应于发给不同的客户端 -
Fingerprint
其实是私钥的ID,go-zero 中推荐把它设置为公钥的 MD5 base64
如何请求
请求需要设置 X-Content-Security
Header,示例内容为
(图一)
其中
-
key
为 fingerprint 让服务端知道用哪个私钥解密 -
secret
为秘文 -
signature
为签名
原理分析
(图二)
上图说明如何计算 secret
和 signature
,其中
-
Time
为当前时间戳 -
Key
为 32 字节随机数据 -
用公钥以 RSA 算法加密了 Time
和Key
-
公钥无需在网络上传输
go-zero 在 contentsecurityhandler.go
中校验签名的算法
-
根据 fingerprint 找到服务端的私钥 -
使用私钥解密 secret
-
验证 secret
里的Time
与服务器时间的误差在一定范围内。这一步防止了重放攻击。 -
验证签名:也就是重新计算图中的绿色部分并用 secret
里的Key
算出签名;与signature
比较,必须相等。这一步防止了篡改攻击。 -
解密消息体 (可选步骤,由 secret
里的Type
决定):用secret
里的Key
以 AES 算法解密消息体,进入业务逻辑处理,完成后再加密响应消息体,发回客户端。这一步防止了抓包分析。
如果客户端设置了消息体加密,则中间人抓包是这样的
(图三)
图三与图一对比,很容易看出消息体加密后的效果。
总结
go-zero 的签名开关,可以用在系统为第三方开放接口的场景。此时用颁发给第三方的公钥来确定请求方的身份,信道传输上能防重放、防篡改、防抓包。
弊端是请求方需要按照 go-zero 的要求构造 X-Content-Security
Header,有点复杂,系统应该提供 SDK 或者代码示例。Go 语言的示例可以参考 contentsecurityhandler_test.go
,如果你的客户端是 Android,可以关注本公众号留言获取没有任何外部依赖的纯 Java8 的请求示例代码。
API 签名机制: https://go-zero.dev/docs/tutorials/api/signature
[2]生成的命令: https://auth0.com/docs/secure/application-credentials/generate-rsa-key-pair
[3]原因: https://stackoverflow.com/questions/2957742/how-to-convert-pkcs8-formatted-pem-private-key-to-the-traditional-format
[4]旧格式的私钥: https://stackoverflow.com/questions/20065304/differences-between-begin-rsa-private-key-and-begin-private-key
原文始发于微信公众号(内存泄漏):详解 go-zero 的签名机制
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论