用JS+GO实现一个RSA非对称加解密流程

当今虽有HTTPS,但一些重要数据如果需要存在客户端,还是要进行额外加密,同时更能确保传输过程安全。对于少量数据的加解密,非对称加密RSA还是非常好用的,如果是大量数据要考虑使用AES+RSA混合加密。

下方示例中前端纯HTML+JS,后端使用Go(Gin框架)。

HTML

使用公钥数据加密并将加密后的数据发送POST请求到后端。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <title>RSA加密示例</title>
</head>

<body>
    <input type="text" id="username" name="username"><br>
    <input type="text" id="password" name="password"><br>
    <button onclick="sendData()">发送</button>
    <script>
        // RSA公钥,自行生成,需要和私钥是一对的
        const SERVER_PUBLIC_KEY = `XXXXXXXXXXX`;
        // 加密数据
        async function encrypt(text, publicKey) {
            const encodedMessage = new TextEncoder().encode(text);
            const encrypted = await window.crypto.subtle.encrypt(
                {
                    name: "RSA-OAEP"
                },
                publicKey,
                encodedMessage
            );
            return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
        }

        // 导入公钥
        async function importPublicKey(pem) {
            const pemHeader = "-----BEGIN PUBLIC KEY-----";
            const pemFooter = "-----END PUBLIC KEY-----";
            const pemContents = pem.substring(pemHeader.length, pem.length - pemFooter.length);
            const binaryDerString = window.atob(pemContents);
            const binaryDer = str2ab(binaryDerString);
            return await window.crypto.subtle.importKey(
                "spki",
                binaryDer,
                {
                    name: "RSA-OAEP",
                    hash: "SHA-256",
                },
                true,
                ["encrypt"]
            );
        }
        // 字符串转ArrayBuffer
        function str2ab(str) {
            const buf = new ArrayBuffer(str.length);
            const bufView = new Uint8Array(buf);
            for (let i = 0, strLen = str.length; i < strLen; i++) {
                bufView[i] = str.charCodeAt(i);
            }
            return buf;
        }

        // 发送数据
        async function sendData() {
            const data = {
                username: document.getElementById('username').value,
                password: document.getElementById('password').value,
            };
            const json = JSON.stringify(data);
            const serverPublicKey = `-----BEGIN PUBLIC KEY-----\n${SERVER_PUBLIC_KEY}\n-----END PUBLIC KEY-----`;
            const publicKey = await importPublicKey(serverPublicKey);
            const encryptedData = await encrypt(json, publicKey);
            fetch('http://localhost:4567/submit', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ data: encryptedData })
            })
                .then(response => response.text())
                .then(decryptedData => {
                    // 显示解密后的数据
                    alert('服务端解密后的数据:' + decryptedData);
                })
                .catch(error => {
                    // 显示错误信息
                    alert('发送数据失败:' + error.message);
                });
        }

    </script>
</body>

</html>

GO(Gin)

接收前端POST过来的加密数据,使用私钥解密并响应。

package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"io/ioutil"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

// 使用私钥和OAEP解密数据
func decrypt(encryptedData string, privateKey *rsa.PrivateKey) (string, error) {
	decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return "", err
	}
	decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, decodedData, nil)
	if err != nil {
		return "", err
	}
	return string(decryptedData), nil
}

// 跨域中间件
func CORSMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusOK)
		} else {
			c.Next()
		}
	}
}

func main() {
	router := gin.Default()
	router.Use(CORSMiddleware())
	// 从本地读取私钥文件
	privateKeyBytes, err := ioutil.ReadFile("private.pem")
	if err != nil {
		log.Fatal("读取私钥文件失败:", err)
	}
	block, _ := pem.Decode(privateKeyBytes)
	if block == nil {
		log.Fatal("PEM解码失败")
	}
	privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		log.Fatal("解析私钥失败:", err)
	}
	rsaPrivateKey, ok := privateKey.(*rsa.PrivateKey)
	if !ok {
		log.Fatal("私钥不是RSA格式")
	}
	router.POST("/submit", func(c *gin.Context) {
		var jsond struct {
			Data string `json:"data"`
		}
		if err := c.ShouldBindJSON(&jsond); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Error binding JSON: " + err.Error()})
			return
		}
		decryptedData, err := decrypt(jsond.Data, rsaPrivateKey)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Decryption failed: " + err.Error()})
			return
		}
		var decryptedJSON map[string]interface{}
		if err := json.Unmarshal([]byte(decryptedData), &decryptedJSON); err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid JSON format after decryption: " + err.Error()})
			return
		}
		// 将解析后的JSON作为响应发送
		c.JSON(http.StatusOK, decryptedJSON)
	})
	router.Run(":4567")
}

进阶示例

使用Cookie、对于本地的加密数据批量发送给后端处理

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>RSA加密示例</title>
    <script src="https://testingcf.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
  </head>

  <body>
    <input type="text" id="username" name="username" /><br />
    <input type="text" id="password" name="password" /><br />
    <button onclick="sendData()">发送</button>
    <button onclick="saveLocalData()">保存到Cookie</button>
    <button onclick="clearLocalCookie()">清除本地数据</button>
    <button onclick="sendLocalData()" id="sendAllEncryptedDataButton">
      发送本地数据
    </button>
    <script>
      const SERVER_URL = "http://localhost:4646";
      const SERVER_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
替换为完整公钥,主意好换行
-----END PUBLIC KEY-----
`;
      async function encrypt(text, publicKey) {
        const encodedMessage = new TextEncoder().encode(text);
        const encrypted = await window.crypto.subtle.encrypt(
          {
            name: "RSA-OAEP",
          },
          publicKey,
          encodedMessage
        );
        return btoa(String.fromCharCode(...new Uint8Array(encrypted)));
      }

      //  导入公钥,移除PEM格式的头部和尾部,以及所有换行符
      function importPublicKey(pem) {
        const base64String = pem
          .replace(/-----(BEGIN|END) PUBLIC KEY-----/g, "")
          .replace(/\n/g, "");
        const binaryDerString = window.atob(base64String);
        const binaryDer = str2ab(binaryDerString);
        return window.crypto.subtle.importKey(
          "spki",
          binaryDer,
          {
            name: "RSA-OAEP",
            hash: "SHA-256",
          },
          true,
          ["encrypt"]
        );
      }

      // 格式转换
      function str2ab(str) {
        const buf = new ArrayBuffer(str.length);
        const bufView = new Uint8Array(buf);
        for (let i = 0, strLen = str.length; i < strLen; i++) {
          bufView[i] = str.charCodeAt(i);
        }
        return buf;
      }

      // 存入Cookie
      function setCookie(name, value, days) {
        var expires = "";
        if (days) {
          var date = new Date();
          date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
          expires = "; expires=" + date.toUTCString();
        }
        document.cookie = name + "=" + (value || "") + expires + "; path=/";
      }

      // 存到Cookie
      async function saveLocalData() {
        const data = {
          username: document.getElementById("username").value,
          password: document.getElementById("password").value,
        };
        const json = JSON.stringify(data);
        const publicKey = await importPublicKey(SERVER_PUBLIC_KEY);
        const encryptedData = await encrypt(json, publicKey);
        const cookieName = `KZ_DB_${Date.now()}`;
        setCookie(cookieName, encryptedData, 1);
        console.log("将数据存入Cookie:", cookieName);
      }

      // 获取全部Cookie
      function getAllEncryptedDataCookies() {
        const ca = document.cookie.split(";");
        const encryptedDataCookies = {};
        ca.forEach((cookie) => {
          cookie = cookie.trim();
          const [name, value] = cookie.split("=");
          if (name.startsWith("KZ_DB_")) {
            encryptedDataCookies[name] = value;
          }
        });
        return encryptedDataCookies;
      }

      // 清除Cookie
      function clearLocalCookie() {
        const encryptedDataCookies = getAllEncryptedDataCookies();
        Object.keys(encryptedDataCookies).forEach((cookieName) => {
          setCookie(cookieName, "", -1);
        });
        console.log("本地Cookie已清空");
      }

      // 使用axios发送所输入的数据,不存储数据到本地
      async function sendData() {
        const data = {
          username: document.getElementById("username").value,
          password: document.getElementById("password").value,
        };
        const json = JSON.stringify(data);
        const serverPublicKey = await importPublicKey(SERVER_PUBLIC_KEY);
        const encryptedData = await encrypt(json, serverPublicKey);
        try {
          const response = await axios.post(`${SERVER_URL}/submit`, {
            data: encryptedData,
          });
          console.log("客户端发送:", encryptedData);
          console.log("服务端解密:", response.data);
        } catch (error) {
          console.error("发送错误:", error);
        }
      }

      // 使用axios发送cookie中的全部加密数据到后端
      async function sendLocalData() {
        const encryptedDataCookies = getAllEncryptedDataCookies();
        const allEncryptedData = Object.values(encryptedDataCookies).map(
          (data) => {
            // 检查Base64字符串的长度,并在必要时添加等号
            while (data.length % 4) {
              data += "=";
            }
            return data;
          }
        );
        const response = await axios.post(`${SERVER_URL}/batch`, {
          data: allEncryptedData,
        });
        console.log("客户端发送:", allEncryptedData);
        console.log("服务端解密:", response.data);
      }
    </script>
  </body>
</html>
package main

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"log"
	"os"
	"sync"

	"github.com/gin-gonic/gin"
)

// 跨域中间件
func CORSMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(200)
		} else {
			c.Next()
		}
	}
}

// 读取私钥pem文件,支持PKCS#1和PKCS#8格式
func parsePrivateKeyFromPEM(filePath string) (*rsa.PrivateKey, error) {
	pemData, err := os.ReadFile(filePath)
	if err != nil {
		return nil, err
	}
	block, _ := pem.Decode(pemData)
	if block == nil {
		return nil, err
	}
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err == nil {
		return privateKey, nil
	}
	pkcs8Key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, err
	}
	rsaKey, ok := pkcs8Key.(*rsa.PrivateKey)
	if !ok {
		return nil, err
	}
	return rsaKey, nil
}

// RSA解密函数,使用私钥和OAEP
func decrypt(encryptedData string, privateKey *rsa.PrivateKey) (string, error) {
	decodedData, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return "", err
	}
	decryptedData, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, decodedData, nil)
	if err != nil {
		return "", err
	}
	return string(decryptedData), nil
}

// 解密并解析传输过来的JSON
func decryptAndParseJSON(encryptedData string, privateKey *rsa.PrivateKey) (map[string]interface{}, error) {
	decryptedData, err := decrypt(encryptedData, privateKey)
	if err != nil {
		return nil, err
	}
	var decryptedJSON map[string]interface{}
	if err := json.Unmarshal([]byte(decryptedData), &decryptedJSON); err != nil {
		return nil, err
	}
	return decryptedJSON, nil
}

// 直接处理接收到的独立数据
func handleSubmit(c *gin.Context, privateKey *rsa.PrivateKey) {
	var jsond struct {
		Data string `json:"data"`
	}
	if err := c.ShouldBindJSON(&jsond); err != nil {
		c.JSON(400, gin.H{"code": 400, "message": "绑定JSON出错: " + err.Error()})
		return
	}
	decryptedJSON, err := decryptAndParseJSON(jsond.Data, privateKey)
	if err != nil {
		c.JSON(500, gin.H{"code": 500, "message": "解密失败: " + err.Error()})
		return
	}
	c.JSON(200, decryptedJSON)
}

// 并发处理接收到的组合数据
func handleBatch(c *gin.Context, privateKey *rsa.PrivateKey) {
	var jsond struct {
		Data []string `json:"data"`
	}
	if err := c.ShouldBindJSON(&jsond); err != nil {
		c.JSON(400, gin.H{"code": 400, "message": "绑定JSON出错: " + err.Error()})
		return
	}
	var wg sync.WaitGroup
	decryptedData := make([]map[string]interface{}, len(jsond.Data))
	errors := make([]error, len(jsond.Data))
	for i, encryptedDatum := range jsond.Data {
		wg.Add(1)
		go func(index int, encrypted string) {
			defer wg.Done()
			decryptedDatum, err := decryptAndParseJSON(encrypted, privateKey)
			if err != nil {
				errors[index] = err
				return
			}
			decryptedData[index] = decryptedDatum
		}(i, encryptedDatum)
	}
	wg.Wait()
	for _, err := range errors {
		if err != nil {
			c.JSON(500, gin.H{"code": 500, "message": "解密失败: " + err.Error()})
			return
		}
	}
	c.JSON(200, decryptedData)
}

// 主程序
func main() {
	router := gin.Default()
	router.Use(CORSMiddleware())
	gin.SetMode(gin.DebugMode)
	rsaPrivateKey, err := parsePrivateKeyFromPEM("private.pem")
	if err != nil {
		log.Fatal("解析私钥失败:", err)
	}
	router.POST("/submit", func(c *gin.Context) {
		handleSubmit(c, rsaPrivateKey)
	})
	router.POST("/batch", func(c *gin.Context) {
		handleBatch(c, rsaPrivateKey)
	})
	port := "4646"
	fmt.Printf("\n>>> 启动👇\n>>> http://localhost:%s\n\n", port)
	router.Run(":" + port)
}
暂无评论

发送评论 编辑评论


				
上一篇
下一篇