场景
把公钥硬编码在前端代码文件里,被公司安全检测到了要整改,遇是整理几种常见的前端密钥存储方案。
1. 设置环境变量再读取
在打包或部署前端应用时,可以将密钥配置为环境变量,在应用运行时通过环境变量读取密钥。这样可以将密钥从源代码中分离出来,避免意外泄露。
a. 前端设置环境变量
将密钥作为前端应用的环境变量进行配置。但是直接使用环境变量存储密钥也是很危险的,前端代码可以被任何人查看和修改,这意味着敏感信息可能会被泄露。
步骤:
-
在 .env 文件中设置环境变量
首先,在项目根目录下的 .env 文件中设置环境变量.
当然,你也可以设置在特定环境的 .env 文件,如 .env.production、.env.test等,这样可以根据不同的环境设置不同的变量值。
VITE_API_KEY = 'your_api_key_here'
-
在 Vue 组件中读取环境变量
console.log(import.meta.env.VITE_API_KEY) // 打印 API 密钥
但是使用这种方法打包后,秘钥仍能在源码中找到。
b. 后端设置环境变量
将密钥存储在后端环境变量中再由前端读取可以提高安全性,这样密钥不会在客户端暴露。
-
后端密钥生成与存储:在后端应用中,生成和存储密钥。可以使用安全的密钥生成算法来生成密钥,并将其存储在后端的安全存储中,如数据库或密钥管理服务。
-
创建API接口:创建一个受保护的API接口来向前端提供密钥。为了提高安全性,可设置此接口需要身份验证,以确保只有经过授权的用户或应用程序可以访问后端的密钥服务。
-
前端请求密钥:前端通过已认证的请求来获取密钥。
对于包含敏感信息的环境变量,应该避免将其暴露给前端。如果确实需要在前端使用某些敏感信息,考虑使用更安全的机制,比如客户端证书或令牌。
2. 配置文件存储
将密钥存储在前端应用的配置文件中。在构建和部署应用时,可以将密钥配置为独立的配置文件,并在应用启动时读取配置文件中的密钥。
但是,在前端应用中使用配置文件来存储密钥也不是一个好的做法,因为前端代码(包括配置文件)最终会被发送到用户的浏览器,这意味着任何有意图的攻击者都可以查看、修改甚至篡改这些配置。
3. 使用加密库加密存储
将密钥进行加密,并将加密后的密钥存储在前端应用中,应用在运行时解密密钥并使用。
这种方法可以提供更高的安全性,防止明文密钥泄露。
常见的做法是使用对称加密算法,将密钥与应用内部的固定值进行加密存储,并在需要使用时进行解密。
-
选择加密算法:选择一个适合的对称加密算法,例如 AES(高级加密标准)。AES 是一种常用的对称加密算法,提供了高强度的加密和解密功能。
-
生成加密密钥:使用选择的算法生成加密密钥。
-
加密密钥:将生成的加密密钥与应用内部的某固定值进行加密,固定值可以是应用的特定字符串或其他数据。将加密后的密钥存储在前端应用中。
-
解密密钥:在需要使用密钥的时候,通过解密算法对加密的密钥进行解密,获取原始的密钥值。解密过程需要使用相同的密钥和算法进行解密操作。
以下是一个示例,展示如何使用 JavaScript 中的 CryptoJS 库进行加密和解密:
// 导入 CryptoJS 库
const CryptoJS = require('crypto-js');
// 固定值,用于加密密钥
const fixedValue = 'your_fixed_value';
// 原始密钥
const originalKey = 'your_secret_key_value';
// 加密密钥
const encryptedKey = CryptoJS.AES.encrypt(originalKey, fixedValue).toString();
// 解密密钥
const decryptedKey = CryptoJS.AES.decrypt(encryptedKey, fixedValue).toString(CryptoJS.enc.Utf8);
console.log(decryptedKey); // 输出原始密钥
加密存储并不是绝对安全的,它只是增加了密钥泄露的难度。对于高安全性要求的应用,建议将敏感操作放在服务器端进行,避免将加密密钥暴露给前端应用的客户端。
4. 混淆技术
混淆是防止JavaScript代码被轻易阅读和理解的有效方法,它通过一系列自动化的工具转换代码结构,但不改变其功能。混淆虽然提高了代码的保密性,但依然不能防止专业人员通过耐心分析来理解代码。因此,它更多是增加攻击者的分析成本,而不是绝对的保护措施。
关于混淆,详细请看这篇:前端JavaScript代码混淆加密原理
5. 安全存储服务
将密钥存储在专门的密钥管理服务中,如密钥管理系统(Key Management System,KMS)。前端应用在运行时通过安全的协议和认证机制与密钥管理服务通信,获取需要的密钥。这样可以将密钥的管理和保护责任交给专门的服务,提供更高级别的安全性。
这也是一种将敏感数据安全地存储在后端服务器上的方法,以确保数据的保密性和完整性。
------------------------------------------------------------------------------------------------------------------------------
综合来看,比较安全的是方法1b和5,其他都多少存在风险。
由于我只是为解决公司安全检测,故只选择了1a方法来存储密钥,其他方法可以考虑作为后续优化方向。
// ------------env.d.ts------------
declare const _MY_GLOBAL_KEY_: string
// ------------vite.config.ts------------
// base64加密后的密钥(注意,此处存储的密钥已经使用base64加密过了)
const PRIVATE_KEY = "'xxxxxxxxxxxx'"
export default defineConfig(({ command, mode }) => {
return {
// ......
// 定义全局常量,用于环境变量注入或其他编译时替换
define: {
_MY_GLOBAL_KEY_: PRIVATE_KEY
}
}
})
// ------------crypto.js------------
import CryptoJS from 'crypto-js'
import JSEncrypt from 'jsencrypt'
const decryptKEY = CryptoJS.enc.Base64.parse(_MY_GLOBAL_KEY_).toString(CryptoJS.enc.Utf8) // Base64解密
// rsa解密
export function rsaDecrypt(decryStr) {
const decryptor = new JSEncrypt()
decryptor.setPrivateKey(decryptKEY)
const decrypted = decryptor.decrypt(decryStr)
return decrypted
}
// 发送请求获取token
getTokenApi()
.then(res => {
const { resultData } = res
let decryptData = null
try {
decryptData = JSON.parse(CryptoJS.enc.Base64.parse(resultData).toString(CryptoJS.enc.Utf8))
} catch (error) {
console.log('error: ', error)
}
s3Data.value = decryptData
s3Data.value.ak = rsaDecrypt(decryptData.ak) //RSA解密
s3Data.value.sk = rsaDecrypt(decryptData.sk) //RSA解密
formData.bucketName = s3Data.value.bucketName
s3.value = new window.AWS.S3({
accessKeyId: s3Data.value.ak,
secretAccessKey: s3Data.value.sk,
endpoint: s3Data.value.endpoint,
sessionToken: s3Data.value.token
})
})
.catch(err => {
proxy.$errorHandle(err)
})