Front-end and back-end data encrypted transmission (with go language implementation)

Preface

General external services require the server to support httpstransmission. So since it is available and httpsthe data has been encrypted, why do we need to do this?

Most application services now are developed using a front-end and back-end separation method, and the interface provided by the back-end is used for business data interaction. I believe that anyone who has webdevelopment experience has opened the browser's debugging console. In , Networkyou can see which requests have been sent by the current page, and you can also see the parameters and return values ​​of the request.

Here we hope that our transmission/return data will not be seen by others, and we directly encrypt the parameters and return values. This is not mainly to prevent third parties from intercepting the data during the transmission process, but to prevent criminals from debugging and obtaining the data. Conduct malicious attacks on servers.

Encryption and decryption

Let’s briefly talk about the knowledge related to encryption and decryption. In this article, we only need to understand symmetric encryption and asymmetric encryption :

  • Symmetric encryption: There is only one key, and the same key is used to encrypt and decrypt data. Common ones include DES, AESetc.
  • Asymmetric encryption: two keys, a public key and a private key, the public key is used to encrypt data, and the private key decrypts data. The public key is often sent to the front end, and the private key is stored in the back end (must not be leaked), for example RSA, DSAetc.

Ideas

Here are two ideas for your reference. There are many corresponding variants and you can implement them according to your needs.

first edition

The client and server use symmetric encryption to transmit data. The client and server determine the transmission key and write it directly into the code.
The client encrypts urlthe queryparameters (that is, the string after the question mark id=xxx&name=xxx) or the transmitted bodycontent (encrypted transmission after serialization), and the server performs corresponding decryption operations.

Insert image description here

Simulate the client server code, use the symmetric encryption algorithm AES, and the key is 4335dfgeredhfdsd.

The server uses ginmiddleware to add to the framework middlewareDecryptReqto parse the encrypted data from the client, and uses a unified data return entry EncryptWriterfunction to return encrypted data.

The sample code is as follows:

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))
}

shortcoming:

  1. The client key is easy to find. Once you get the key, you can encrypt and decrypt the data and tamper with the data content.
  2. The key is hard-coded in the code, so it would be troublesome to change the key.

second edition

Based on the first version, some complexity has been added. Drawing on httpsthe implementation principle of , using both asymmetric encryption and symmetric encryption .

The client first asks the server for an asymmetric encryption public key , then generates a symmetric encryption key based on a timestamp or other algorithm , and then encrypts the symmetric encryption key using the asymmetric encryption public key and sends it to the server.
After the server decrypts the data, it encrypts the returned data using a symmetric encryption key and returns it to the client. The client then decrypts and obtains the data.

Insert image description here

The sample code is as follows:

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))
}

In this way, even if the attacker obtains the key, he cannot decrypt the data, but he still cannot prevent decompiling the code and obtaining data through break-point debugging.

The attacker can also simulate and generate an asymmetric encryption key in the middle . When the client initiates a request to obtain the server's public key , he can send his simulated public key to the client and save the public key returned by the server . Then when the client initiates a request, it can first decrypt the data with the private key it generated , then tamper with the data, encrypt it with the server public key just saved and pass it to the client.

front end

The front end jscan use the jsencrypt library, which will not be introduced in detail in this article. The relevant client-side encryption and decryption codes are all goimplemented and are posted at the end of the sample code above.

Summarize

The encrypted transmission done here can only increase the complexity of cracking, but cannot really guarantee that the data will not be leaked .

Therefore, general services do not deliberately do this kind of front-end and back-end data encryption when doing data interaction. They generally do server or sessionclient cookieverification to ensure that the data is not leaked or tampered with .

But if you say that sessionthe verification tokenI did was leaked on the front end, then this is also the user's own problem (entering a hacker website or doing other authentication). The service cannot prevent this. At most, it can increase the complexity of verification. It's more cumbersome. Therefore, during operations such as payment, verification (entering password/verifying fingerprint, etc.) will be performed again to verify permissions again.

The main scenario for doing data encryption on the front and back ends of this article is: I have a platform service, and at this time I made a supporting stand-alone application. The stand-alone application can be used by others at will. In order to ensure that my software is not It is a layer of protection that can be easily cracked by others to obtain data.

Guess you like

Origin blog.csdn.net/DisMisPres/article/details/129458902
Recommended