引言

在Web应用中,数据安全是至关重要的考量因素。非对称加密算法因其安全性高、使用灵活,被广泛应用于数据传输加密、数字签名等场景。本文将详细介绍如何在Python后端和Vue前端中实现非对称加解密功能。

RSA加密原理简介

RSA是一种非对称加密算法,它使用一对密钥:公钥和私钥。公钥用于加密数据,私钥用于解密数据。这种特性使得RSA非常适合以下场景:

  • 敏感数据加密传输

  • 数字签名和身份验证

  • 安全密钥交换

环境准备

后端依赖安装

pip install cryptography

cryptography是Python中一个功能强大且易用的加密库,提供了各种加密算法的实现。

前端依赖安装

pnpm add jsencrypt
# 或使用npm
npm install jsencrypt
# 或使用yarn
yarn add jsencrypt

jsencrypt是一个纯JavaScript实现的RSA加密库,专门为Web应用设计。

后端加解密核心代码

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
import base64
import os
​
PRIVATE_KEY_FILE = "private_key.pem"
PUBLIC_KEY_FILE = "public_key.pem"
​
​
def initialize_keys():
    """初始化密钥:加载现有密钥或生成新密钥对"""
    global private_key, public_key
​
    if os.path.exists(PRIVATE_KEY_FILE) and os.path.exists(PUBLIC_KEY_FILE):
        # 加载已存在的密钥
        with open(PRIVATE_KEY_FILE, "rb") as f:
            private_key = serialization.load_pem_private_key(f.read(), password=None)
        with open(PUBLIC_KEY_FILE, "rb") as f:
            public_key = serialization.load_pem_public_key(f.read())
    else:
        # 生成新的密钥对并保存
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
        )
        public_key = private_key.public_key()
​
        # 保存私钥(生产环境应加密存储)
        with open(PRIVATE_KEY_FILE, "wb") as f:
            f.write(private_key.private_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PrivateFormat.PKCS8,
                encryption_algorithm=serialization.NoEncryption()
            ))
​
        # 保存公钥
        with open(PUBLIC_KEY_FILE, "wb") as f:
            f.write(public_key.public_bytes(
                encoding=serialization.Encoding.PEM,
                format=serialization.PublicFormat.SubjectPublicKeyInfo
            ))
​
​
def get_public_key_pem() -> str:
    """获取PEM格式的公钥,供前端使用"""
    pem = public_key.public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo
    )
    return pem.decode()
​
​
def get_private_key_pem() -> str:
    """获取PEM格式的私钥,供前端使用"""
    pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption()
    )
    return pem.decode()
​
​
def encrypt_data_with_public_key(data: str, public_key_pem: str) -> str:
    """使用公钥加密数据(模拟前端加密过程)"""
    public_key = serialization.load_pem_public_key(public_key_pem.encode())
    encrypted_data = public_key.encrypt(
        data.encode(),
        padding.PKCS1v15()
    )
    return base64.b64encode(encrypted_data).decode()
​
​
def decrypt_data(encrypted_data: str) -> str:
    """使用私钥解密数据(后端解密)"""
    encrypted_bytes = base64.b64decode(encrypted_data.encode())
    decrypted_data = private_key.decrypt(
        encrypted_bytes,
        padding.PKCS1v15()
    )
    return decrypted_data.decode()
​
​
# 生成公钥私钥
initialize_keys()

详解:

1、initialize_keys()

实现了密钥的智能管理:

  • 首先检查是否已存在密钥文件

  • 如果存在,直接加载现有密钥

  • 如果不存在,生成新的RSA密钥对并保存到文件

安全提醒:生产环境中,私钥应该加密存储,并使用安全的密码保护。

2、加密过程说明:

encrypt_data_with_public_key():

  1. 将PEM格式的公钥字符串加载为公钥对象

  2. 使用PKCS1v15填充方案对数据进行加密

  3. 将加密后的二进制数据转换为Base64字符串,便于网络传输

3、解密过程说明:

decrypt_data():

  1. 将Base64编码的加密数据解码为二进制

  2. 使用私钥和相同的填充方案进行解密

  3. 将解密后的二进制数据转换为字符串

4、后端接口获取公私秘钥:

用于前端请求数据前加密,回显后解密,这里以fastapi为例:

...
@app.get("/get_public_key", summary="获取公钥", tags=["公私秘钥"])
async def get_public_key(auth: Auth = Depends(AllUserAuth())):
    if not auth:
        return ErrorResponse("请先登录")
    return SuccessResponse(data=get_public_key_pem())
​
​
@app.get("/get_private_key", summary="获取私钥", tags=["公私秘钥"])
async def get_private_key(auth: Auth = Depends(AllUserAuth())):
    if not auth:
        return ErrorResponse("请先登录")
    return SuccessResponse(data=get_private_key_pem())
...

接口设计要点:

  • 公钥私钥接口需要身份验证后才能获取

  • 返回PEM格式的公钥字符串,便于前端直接使用

前端加解密核心代码

<script setup lang="tsx">
...
import { addObjApi, getObjApi, getObjListApi, delObjListApi, putObjApi, getPublicKeyApi, getPrivateKeyApi } from './api'
...    
    
// 导入加密工具
import { JSEncrypt } from 'jsencrypt'
// 加密实例
const encryptor = ref(new JSEncrypt())
const publicKey = ref('')
const privateKey = ref('')
​
// 获取公钥
const fetchPublicKey = async () => {
  try {
    const res = await getPublicKeyApi()
    if (res.data) {
      publicKey.value = res.data
      encryptor.value.setPublicKey(publicKey.value)
    }
  } catch (error) {
    console.error('获取公钥失败:', error)
  }
}
​
// 加密数据
const encryptData = (data: string): string => {
  if (!publicKey.value) {
    ElMessage.warning('加密公钥未就绪')
    return data
  }
​
  try {
    const encrypted = encryptor.value.encrypt(data)
    return encrypted || data
  } catch (error) {
    console.error('加密失败:', error)
    ElMessage.error('数据加密失败')
    return data
  }
}
​
// 获取私钥
const fetchPrivateKey = async () => {
  try {
    const res = await getPrivateKeyApi()
    if (res.data) {
      privateKey.value = res.data
      encryptor.value.setPrivateKey(privateKey.value)
    }
  } catch (error) {
    console.error('获取私钥失败:', error)
  }
}
​
// 解密数据
const decryptData = (encryptedData: string): string => {
  if (!privateKey.value) {
    ElMessage.warning('解密私钥未就绪')
    return encryptedData
  }
​
  try {
    const decrypted = encryptor.value.decrypt(encryptedData)
    return decrypted || encryptedData
  } catch (error) {
    console.error('解密失败:', error)
    ElMessage.error('数据解密失败')
    return encryptedData
  }
}
​
const { tableRegister, tableState, tableMethods } = useTable({
  fetchDataApi: async () => {
    const { pageSize, currentPage } = tableState
    const res = await getObjListApi({
      page: unref(currentPage),
      limit: unref(pageSize)
    })
    // 对加密的数据进行解密,回显到前端展示
    const decryptedList = (res.data || []).map(item => {
      if (item.is_encrypt && item.var_value) {
        return {
          ...item,
          var_value: decryptData(item.var_value)
        }
      }
      return item
    })
​
    return {
      list: decryptedList,
      total: res.count || 0
    }
  },
  fetchDelApi: async (value) => {
    const res = await delObjListApi(value)
    return res.code === 200
  }
})
​
...
​
// 组件挂载时获取公私钥
onMounted(() => {
  fetchPublicKey()
  fetchPrivateKey()
})
</script>

详解:

1、数据加密(encryptData):

加密注意事项:

  • 检查公钥是否已准备就绪

  • 实现降级策略,加密失败时返回原始数据

  • 提供用户友好的错误提示

2、数据解密(decryptData):

解密功能特点:

  • 同样实现降级处理,解密失败返回加密数据

  • 适用于处理从后端获取的加密数据

总结

本文详细介绍了在Python+Vue项目中实现非对称加解密的完整方案。通过前后端分离的方式实现了:

  • 后端的密钥生成、管理和加解密功能

  • 前端的密钥获取、数据加密和解密功能

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐