前后端数据加密传输(附go语言实现)

这篇具有很好参考价值的文章主要介绍了前后端数据加密传输(附go语言实现)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

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

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

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

加解密

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

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

思路

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

第一版

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

前后端数据加密传输(附go语言实现)

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

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

示例代码 如下:

package main

import (
	"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 的实现原理,同时使用 非对称加密对称加密

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

前后端数据加密传输(附go语言实现)

示例代码 如下:

package main

import (
	"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.Once
	rsaPubKey       []byte
	rsaPrivateKey   []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 = pub
			rsaPrivateKey = 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.PublicKey
	derPkix, 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 key
	aes_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 key
	aes_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 在前端泄露了,那这种也是用户自己的问题(进入黑客网站或其他做了鉴权),服务防不了这种,顶多是增加校验复杂性来让其更加繁琐。所以说在类似付款的操作的时候,都会再进行一次校验(输入密码/验证指纹等),来再做一次权限校验。

本篇前后端做数据加密,主要的一个场景就是:我有一个平台服务,这时候我又做了一个配套的单机应用,单机应用是可以随意给其他人使用的,为了保证我的软件不被别人轻易的破解获取数据而做的一层防护。文章来源地址https://www.toymoban.com/news/detail-472451.html

到了这里,关于前后端数据加密传输(附go语言实现)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 源码分享-go语言实现的祖冲之ZUC加密算法

    源码路径:free5gc/nas/security/zuc zuc.go zuc_test.go

    2024年02月16日
    浏览(24)
  • 适用于前后端公用的SM2国密加密传输, JAVA + VUE

    由于等保和多个系统间的数据传输加密, 写了一个共有的前端与后端, 后端与后端,的通用算法SM2简单加密,  不需要验签, 几行代码搞定.  引包, 测试好几遍, 这个包适合jdk1.8使用 引包, 没有意外就应该直接能用下面代码了     publicKey:04aa909915f87880507e3de515220cc8f82b1c5693f56a0475b3

    2024年02月16日
    浏览(32)
  • 源码分享-go语言实现的snow3g加密算法

    源码路径:free5gc/nas/security/snow3g snow3g.go snow3g_test.go

    2024年02月16日
    浏览(25)
  • springboot + vue 前后端加密传输 RSA互相加解密、加签验签、密钥对生成

    参考 由于Java非对称加解密、加验签都是采用PKCS#8格式的密钥,PKCS#1格式的密钥跑不通,这里先简单介绍一下两者的区别。 PKCS#1和PKCS#8是两个不同的数字证书标准。 PKCS#1是一个公钥加密标准,它定义了使用RSA算法进行加密和签名的格式。主要用于对数字签名、加密以及数字签

    2024年04月27日
    浏览(30)
  • 基于netty框架不使用SSL证书,实现websocket数据加密传输

    1、简介 2、实现方式 3、服务端主要代码 4、客户端主要代码 5、调用方式 为什么不使用SSL证书? 1、服务器运行在专网环境,不能访问互联网。证书有有效期,CA机构规定,证书有效期最多2年。在客户的专网环境里更新和维护证书就会增加运营成本。 实现逻辑? 参照SSL的实

    2024年02月04日
    浏览(42)
  • 若依ruoyi前端vue使用jsencrypt.js加密后端java进行RSA解密(前后端交互RSA加解密)

    目录 1、前后端RSA加解密实现思路 2、前端 3、后端 按照约定来说公钥一般用来加密,大家都可以获取得到,私钥用来解密,当然你也可以混着用,以下示例是前端通过加密,后端解密.  -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ81AMIIBCgKCAQEA1+05vAf7m5NcLNLkRtsm gp+QdzcW6MVdayGTGBJG0v

    2024年02月06日
    浏览(43)
  • RSA加密解密(无数据大小限制,php、go、java互通实现)

    RSA加解密中必须考虑到的**长度、明文长度和密文长度问题。明文长度需要小于**长度,而密文长度则等于**长度。因此当加密内容长度大于**长度时,有效的RSA加解密就需要对内容进行分段。 这是因为,RSA算法本身要求加密内容也就是明文长度m必须0m**长度n。如果小于这个长

    2024年02月15日
    浏览(41)
  • Go语言项目后端使用gin框架接收前端发送的三种格式数据(form-data,json,Params)

    使用gin框架的BindJSON方法,将前端的json格式数据将后端的结构体相绑定,从而获取到前端所发送的数据,并返回给前端 1.将前端发送过来的数据全部返回 2.将前端发送过来的json格式数据选择性返回   使用gin框架的PostForm方法,从而获取到前端form格式的参数 使用gin框架中的

    2024年02月01日
    浏览(32)
  • C语言数据结构(0)——前言

    欢迎来到博主的新专栏——C语言与数据结构 博主id:代码小豪 在前两个专栏当中,博主已经大致的讲过了C语言中的大部分使用方法。大家都知道,学习英语时,首先掌握的是单词,随后学习语法,如此才能融会贯通的学习英语。如果学英文只会单词,那么阅读虽然不成问题

    2024年01月17日
    浏览(27)
  • go语言使用AES加密解密

    Go语言提供了标准库中的crypto/aes包来支持AES加密和解密。下面是使用AES-128-CBC模式加密和解密的示例代码:

    2024年02月06日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包