当今虽有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)
}