前后端数据加密传输(附go语言实现)
创始人
2024-06-02 08:42:03
0

前言

一般的对外服务都是需要服务器支持 https 传输的,那既然有了 https ,数据已经加密了,为什么还要做这个事情呢?

现在大多数应用服务都是使用的前后端分离的方式来开发的,以后端提供的接口来进行业务数据交互。相信有过 web 开发经验的都打开过浏览器的调试控制台,在 Network 中能够看到当前页面发过哪些请求,而且能够看到请求的参数和返回值。

这里就是希望我们的传输/返回数据不被别人看到,直接给参数和返回值加密,主要不是为了防止数据传输过程中被第三方拦截而做的,而是为了防止不法分子调试获取到数据,对服务器进行恶意攻击。

加解密

简单说一下加解密相关的知识,本篇我们只需要了解下 对称加密非对称加密 即可:

  • 对称加密:只有一个密钥,使用同一个密钥对数据加解密,常见的有 DESAES 等。
  • 非对称加密:两个密钥,一个公钥,一个私钥,公钥用于加密数据,私钥解密数据。常常将公钥发给前端,私钥保存在后端(一定不能泄露),例如 RSADSA 等。

思路

这里提供两种思路供大家参考,相应的变种很多,可以自己根据需求实现。

第一版

客户端服务端使用对称加密传输数据,客户端服务端定好传输密钥,直接写死在代码里。
客户端加密 urlquery 参数(即问号后面那串 id=xxx&name=xxx)或者传输的 body 内容(序列化后加密传输),服务端进行相应的解密操作。

在这里插入图片描述

模拟一下客户端服务器的代码,对称加密算法使用 AES,密钥为 4335dfgeredhfdsd

服务端使用 gin 框架中添加中间件 middlewareDecryptReq,用来解析客户端加密后的数据,并使用统一的数据返回入口 EncryptWriter 函数来做数据加密返回。

示例代码 如下:

package mainimport ("bytes""encoding/base64""fmt""github.com/duke-git/lancet/v2/cryptor""github.com/gin-gonic/gin""io""io/ioutil""net/http"
)var (aesKey = []byte("4335dfgeredhfdsd")
)func main() {engine := gin.Default()engine.Use(middlewareDecryptReq())engine.GET("/g", func(c *gin.Context) {EncryptWriter(c, []byte(c.Request.URL.RawQuery))})engine.POST("/p", func(c *gin.Context) {buf, err := c.GetRawData()if err != nil {c.String(http.StatusInternalServerError, err.Error())return}EncryptWriter(c, buf)})engine.Run(":4780")
}func middlewareDecryptReq() func(c *gin.Context) {return func(c *gin.Context) {if c.Request.URL.RawQuery != "" {res, err := AesCbcDecryptBase64([]byte(c.Request.URL.RawQuery), aesKey)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}c.Request.URL.RawQuery = string(res)}data, err := ioutil.ReadAll(c.Request.Body)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}defer c.Request.Body.Close()if len(data) == 0 {c.Next()return}plainBuf, err := AesCbcDecryptBase64(data, aesKey)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}r := bytes.NewBuffer(plainBuf)rd := io.NopCloser(r)c.Request.Body = rd}
}func EncryptWriter(c *gin.Context, data []byte) {cipherBuf := AesCbcEncryptBase64(data, aesKey)c.String(http.StatusOK, string(cipherBuf))
}func AesCbcEncrypt(plainText, secretKey []byte) []byte {return cryptor.AesCbcEncrypt(plainText, secretKey)
}func AesCbcDecrypt(cipherText, key []byte) []byte {return cryptor.AesCbcDecrypt(cipherText, key)
}func AesCbcEncryptBase64(plainText, secretKey []byte) (cipherTextBase64 []byte) {encryBytes := AesCbcEncrypt(plainText, secretKey)cipherTextBase64 = make([]byte, base64.StdEncoding.EncodedLen(len(encryBytes)))base64.StdEncoding.Encode(cipherTextBase64, encryBytes)return
}func AesCbcDecryptBase64(cipherTextBase64, key []byte) (res []byte, err error) {plainTextBytes := make([]byte, base64.StdEncoding.DecodedLen(len(cipherTextBase64)))n, err := base64.StdEncoding.Decode(plainTextBytes, cipherTextBase64)if err != nil {return}res = AesCbcDecrypt(plainTextBytes[:n], key)return
}func tServerGet() {queryData := []byte("id=xxx&name=xxx")cipherBuf := AesCbcEncryptBase64(queryData, aesKey)resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:4780/g?%s", string(cipherBuf)))if err != nil {fmt.Println(err)return}fmt.Println(resp.StatusCode)buf, err := io.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println("原始数据:", string(buf))plainBuf, err := AesCbcDecryptBase64(buf, aesKey)if err != nil {fmt.Println(err)return}fmt.Println("解密成功:", string(plainBuf))
}func tServerPost() {data := []byte(`{"id":"xxx","name":"法外狂徒"}`)cipherBuf := AesCbcEncryptBase64(data, aesKey)resp, err := http.Post("http://127.0.0.1:4780/p", "application/json", bytes.NewReader(cipherBuf))if err != nil {fmt.Println(err)return}fmt.Println(resp.StatusCode)buf, err := io.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println("原始数据:", string(buf))plainBuf, err := AesCbcDecryptBase64(buf, aesKey)if err != nil {fmt.Println(err)return}fmt.Println("解密成功:", string(plainBuf))
}

缺点:

  1. 客户端密钥容易找,拿到了密钥,那也就能对数据进行加解密操作,可以篡改数据内容。
  2. 密钥写死在代码里,如果要改个密钥比较麻烦。

第二版

在第一版的基础上,添加了一些复杂性。借鉴了 https 的实现原理,同时使用 非对称加密对称加密

客户端先向服务器要 非对称加密公钥,然后根据时间戳或者其他算法生成 对称加密密钥,然后将这个 对称加密密钥 使用 非对称加密公钥 加密后传给服务器。
服务器解密后,再将返回数据使用 对称加密密钥 加密后返回给客户端,客户端再解密获取数据。

在这里插入图片描述

示例代码 如下:

package mainimport ("bytes""crypto/rand""crypto/rsa""crypto/x509""encoding/base64""encoding/pem""errors""fmt""github.com/duke-git/lancet/v2/cryptor""github.com/gin-gonic/gin""io""io/ioutil""net/http""sync"
)var (onceGenerateKey sync.OncersaPubKey       []bytersaPrivateKey   []byte
)const ginAesKey = "ginAesKey"func main() {engine := gin.Default()engine.GET("/rsa_key", func(c *gin.Context) {onceGenerateKey.Do(func() {private, pub, er := GenRsaKey()if er != nil {fmt.Println(er)return}rsaPubKey = pubrsaPrivateKey = private})c.String(http.StatusOK, string(rsaPubKey))})cryptRouter := engine.Group("", middlewareDecryptReq())cryptRouter.GET("/g", func(c *gin.Context) {EncryptWriter(c, []byte(c.Request.URL.RawQuery))})cryptRouter.POST("/p", func(c *gin.Context) {buf, err := c.GetRawData()if err != nil {c.String(http.StatusInternalServerError, err.Error())return}EncryptWriter(c, buf)})engine.Run(":4780")
}func middlewareDecryptReq() func(c *gin.Context) {return func(c *gin.Context) {// aes 密钥放在 query 参数里,没有值直接报错if c.Request.URL.RawQuery == "" {c.String(http.StatusBadRequest, "参数错误")c.Abort()return}res, err := RsaDecryptBase64([]byte(c.Request.URL.RawQuery), rsaPrivateKey)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}c.Request.URL.RawQuery = string(res)ak := c.Query("aesKey")if ak == "" {c.String(http.StatusBadRequest, "参数错误")c.Abort()return}c.Set(ginAesKey, ak)data, err := ioutil.ReadAll(c.Request.Body)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}defer c.Request.Body.Close()if len(data) == 0 {c.Next()return}plainBuf, err := RsaDecryptBase64(data, rsaPrivateKey)if err != nil {c.String(http.StatusBadRequest, err.Error())c.Abort()return}rd := io.NopCloser(bytes.NewBuffer(plainBuf))c.Request.Body = rd}
}func EncryptWriter(c *gin.Context, data []byte) {cipherBuf := AesCbcEncryptBase64(data, []byte(c.GetString(ginAesKey)))c.String(http.StatusOK, string(cipherBuf))
}func Base64Encrypt(data []byte) []byte {res := make([]byte, base64.StdEncoding.EncodedLen(len(data)))base64.StdEncoding.Encode(res, data)return res
}func Base64Decrypt(data []byte) ([]byte, error) {res := make([]byte, base64.StdEncoding.DecodedLen(len(data)))n, err := base64.StdEncoding.Decode(res, data)return res[:n], err
}func AesCbcEncrypt(plainText, secretKey []byte) []byte {return cryptor.AesCbcEncrypt(plainText, secretKey)
}func AesCbcDecrypt(cipherText, key []byte) []byte {return cryptor.AesCbcDecrypt(cipherText, key)
}func AesCbcEncryptBase64(plainText, secretKey []byte) (cipherTextBase64 []byte) {encryptBytes := AesCbcEncrypt(plainText, secretKey)return Base64Encrypt(encryptBytes)
}func AesCbcDecryptBase64(cipherTextBase64, key []byte) (res []byte, err error) {plainTextBytes, err := Base64Decrypt(cipherTextBase64)if err != nil {return}res = AesCbcDecrypt(plainTextBytes, key)return
}func GenRsaKey() (prvkey, pubkey []byte, err error) {// 生成私钥文件privateKey, err := rsa.GenerateKey(rand.Reader, 1024)if err != nil {return}derStream := x509.MarshalPKCS1PrivateKey(privateKey)block := &pem.Block{Type:  "RSA PRIVATE KEY",Bytes: derStream,}prvkey = pem.EncodeToMemory(block)publicKey := &privateKey.PublicKeyderPkix, err := x509.MarshalPKIXPublicKey(publicKey)if err != nil {return}block = &pem.Block{Type:  "RSA PUBLIC KEY",Bytes: derPkix,}pubkey = pem.EncodeToMemory(block)return
}// 公钥加密
func RsaEncrypt(data, keyBytes []byte) ([]byte, error) {//解密pem格式的公钥block, _ := pem.Decode(keyBytes)if block == nil {return nil, errors.New("public key error")}// 解析公钥pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)if err != nil {return nil, err}// 类型断言pub := pubInterface.(*rsa.PublicKey)//加密ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data)if err != nil {return nil, err}return ciphertext, nil
}
func RsaEncryptBase64(data, keyBytes []byte) ([]byte, error) {encryptBuf, err := RsaEncrypt(data, keyBytes)if err != nil {return nil, err}res := Base64Encrypt(encryptBuf)return res, nil
}// 私钥解密
func RsaDecrypt(ciphertext, keyBytes []byte) ([]byte, error) {//获取私钥block, _ := pem.Decode(keyBytes)if block == nil {return nil, errors.New("private key error!")}//解析PKCS1格式的私钥priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)if err != nil {return nil, err}// 解密data, err := rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)if err != nil {return nil, err}return data, nil
}
func RsaDecryptBase64(ciphertext, keyBytes []byte) ([]byte, error) {buf, err := Base64Decrypt(ciphertext)if err != nil {return nil, err}return RsaDecrypt(buf, keyBytes)
}func serverGetRsaKey() (pubKey []byte, err error) {resp, err := http.Get("http://127.0.0.1:4780/rsa_key")if err != nil {return}return io.ReadAll(resp.Body)
}func tServerGet() {pubKey, err := serverGetRsaKey()if err != nil {fmt.Println(err)return}//随机生成 aes keyaes_key := "4335dfgeresdheud"queryData := []byte("id=xxx&name=xxx&aesKey=" + aes_key)cipherBuf, err := RsaEncryptBase64(queryData, pubKey)resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:4780/g?%s", string(cipherBuf)))if err != nil {fmt.Println(err)return}fmt.Println(resp.StatusCode)buf, err := io.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println("原始数据:", string(buf))plainBuf, err := AesCbcDecryptBase64(buf, []byte(aes_key))if err != nil {fmt.Println(err)return}fmt.Println("解密成功:", string(plainBuf))
}func tServerPost() {pubKey, err := serverGetRsaKey()if err != nil {fmt.Println(err)return}//随机生成 aes keyaes_key := "4335dfgeresdheud"queryCipher, err := RsaEncryptBase64([]byte("aesKey="+aes_key), pubKey)if err != nil {fmt.Println(err)return}data := []byte(`{"id":"xxx","name":"法外狂徒"}`)cipherBuf, err := RsaEncryptBase64(data, pubKey)if err != nil {fmt.Println(err)return}resp, err := http.Post("http://127.0.0.1:4780/p?"+string(queryCipher), "application/json", bytes.NewReader(cipherBuf))if err != nil {fmt.Println(err)return}fmt.Println(resp.StatusCode)buf, err := io.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println("原始数据:", string(buf))plainBuf, err := AesCbcDecryptBase64(buf, []byte(aes_key))if err != nil {fmt.Println(err)return}fmt.Println("解密成功:", string(plainBuf))
}

这样,就算攻击者获取到密钥了,也不能解密数据,但是还是防止不了反编译代码,打断点调试获取数据。

攻击者也可以自己在中间模拟生成 非对称加密密钥,当客户端发起请求获取服务器 公钥 时,可以将自己模拟的 公钥 发给客户端,保存下服务器传回的 公钥。然后在客户端发起请求时,可以先用自己生成的 私钥 解密数据,然后再篡改数据,使用刚刚保存的服务器 公钥 加密后传给客户端。

前端

前端 js 可以使用 jsencrypt 库,本篇不做详细介绍了。相关的客户端加密解密代码都用 go 实现了,贴在上面的 示例代码 的末尾。

总结

这里做的加密传输,仅仅只能添加破解的复杂性,不能真的保证数据不泄露

所以一般的服务在做数据交互时也并不会刻意去做这种前后端的数据加密,一般性的是做服务器的 session 或者客户端的 cookie 校验,来保证数据不泄露,不被篡改

但你要是说我做 session 校验的 token 在前端泄露了,那这种也是用户自己的问题(进入黑客网站或其他做了鉴权),服务防不了这种,顶多是增加校验复杂性来让其更加繁琐。所以说在类似付款的操作的时候,都会再进行一次校验(输入密码/验证指纹等),来再做一次权限校验。

本篇前后端做数据加密,主要的一个场景就是:我有一个平台服务,这时候我又做了一个配套的单机应用,单机应用是可以随意给其他人使用的,为了保证我的软件不被别人轻易的破解获取数据而做的一层防护。

相关内容

热门资讯

ios系统与安卓互通吗 你有没有想过,为什么你的iPhone手机上的游戏,你的安卓手机上的好友也能玩?或者,你用安卓手机下载...
中国法律app安卓系统,便捷法... 你有没有发现,现在手机上各种各样的应用层出不穷,而在中国,法律相关的APP更是让人眼花缭乱。今天,就...
安卓系统漫画神器怎么用 你有没有发现,手机里装了那么多应用,有时候就是缺那么一个能让你轻松看漫画的神器?别急,今天就来给你揭...
哪个公司推出的安卓系统,引领移... 你有没有想过,我们每天离不开的安卓系统,究竟是由哪个公司推出的呢?没错,就是那个家喻户晓的谷歌(Go...
xp系统的电脑刷个安卓系统,电... 你有没有想过,你的老XP系统电脑,是不是也能来个华丽变身,摇身一变成为安卓系统的忠实粉丝呢?没错,今...
特斯拉安卓系统怎么样,创新科技... 特斯拉安卓系统怎么样?揭秘这款独特系统的魅力与挑战想象一辆汽车,它不仅是一台交通工具,更是一个移动的...
电视安卓系统区别相机,解析电视... 你有没有发现,家里的电视和手机,还有相机,它们都离不开一个神奇的东西——系统!今天,咱们就来聊聊这三...
安卓电脑系统哪个系统最好用,探... 你有没有想过,如果你的电脑也能像手机一样灵活多变,那该多好啊!想象你可以在电脑上玩手游,看视频,还能...
安卓系统高低影响价格吗,系统高... 你有没有想过,同样是手机,为什么有的便宜有的贵呢?这其中,安卓系统的版本可是起着不小的作用哦!今天,...
冠道支持安卓系统么,冠道安卓系... 你有没有想过,你的爱车冠道是不是也能跟上时代的潮流,支持安卓系统呢?这可是个让人心动的话题哦!今天,...
安卓和系统不兼容,揭秘不兼容问... 你有没有遇到过这种情况?手机里下载了一个超级酷炫的应用,结果一打开,哎呀妈呀,系统不兼容,直接闪退了...
安卓平板刷linux系统版本,... 你有没有想过给你的安卓平板来个“变身”大法?没错,就是刷上Linux系统版本!想象原本流畅的安卓系统...
安卓系统不支持网络,探索网络限... 你知道吗?最近我在网上看到一个让人哭笑不得的新闻,说的是安卓系统竟然不支持网络!这可真是让人大跌眼镜...
红魔系统属于安卓系统吗,安卓游... 你有没有听说过红魔系统?是不是好奇它到底是不是安卓系统的一员呢?今天,就让我带你一探究竟,揭开红魔系...
xos系统是安卓吗,揭秘其与安... 你有没有听说过XOS系统?是不是好奇它是不是安卓系统呢?今天,就让我带你一探究竟,揭开这个神秘面纱!...
whatsup安卓系统下载,便... Hey,亲爱的手机控们,是不是又到了你心痒痒想要升级手机系统的时候了?今天,咱们就来聊聊那让人心动的...
安卓点餐系统自己做,重塑餐饮服... 你有没有想过,自己动手打造一个安卓点餐系统呢?想象点餐时不再需要繁琐的操作,一键就能完成,是不是很酷...
安卓安装系统验证失败,原因排查... 最近是不是你也遇到了安卓安装系统验证失败的问题?别急,让我来给你详细说说这个让人头疼的小麻烦,让你一...
三方开源安卓系统,基于三方开源... 你知道吗?在安卓的世界里,除了谷歌的官方系统,还有许多其他有趣的版本在悄悄崛起。今天,就让我带你一起...
安卓系统最老版本下载 你有没有想过,手机里的安卓系统其实也有它的“老古董”呢?没错,就是那些最古老的安卓版本,它们就像时间...