鸿蒙PC文件加密工具实战:AES-256-GCM 加密
/ 加密进度// 文件记录id: string安全的加密算法:使用 AES-256-GCM + PBKDF2,提供军事级别的安全保障本地加密处理:文件在浏览器本地完成加密,不会上传到任何服务器批量文件处理:支持同时加密/解密多个文件,提高工作效率密码强度检测:实时检测密码强度,支持一键生成强密码操作记录管理:记录每次加密/解密操作,方便追溯。
Vue3 + Web Crypto API 文件加密工具实战:AES-256-GCM 加密
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
项目 Git 仓库:
https://atomgit.com/liboqian/harmonyOs_tool
1. 项目背景与需求分析



1.1 为什么需要文件加密工具
在数字化时代,我们的电脑和手机中存储了大量敏感文件,包括个人照片、财务文档、商业合同等。这些文件一旦泄露,可能造成不可挽回的损失。虽然市面上已有许多加密软件,但大多数存在以下问题:
- 依赖安装:需要下载和安装第三方软件,占用磁盘空间
- 隐私顾虑:部分在线加密工具会将文件上传到服务器,存在泄露风险
- 跨平台限制:不同操作系统需要使用不同的加密工具
- 操作复杂:命令行工具对普通用户不友好
本项目基于浏览器的 Web Crypto API 开发了一个纯前端的文件加密/解密工具,具有以下核心优势:
- 无需安装:打开浏览器即可使用,无需下载任何软件
- 本地加密:文件在浏览器本地完成加密,不会上传到任何服务器
- 跨平台:支持所有现代浏览器,Windows、macOS、Linux 通用
- 安全可靠:使用 AES-256-GCM 加密算法,军事级别的安全保障
1.2 系统功能概览
| 功能模块 | 核心功能 | 技术要点 |
|---|---|---|
| 文件加密 | 拖拽上传、批量加密、进度显示 | Web Crypto API、ArrayBuffer |
| 文件解密 | 自动识别加密文件、密码验证 | AES-GCM 解密、元数据解析 |
| 密码管理 | 密码强度检测、强密码生成 | 密码学随机数生成 |
| 操作记录 | 历史记录、文件信息展示 | localStorage 持久化 |
1.3 技术选型
本项目采用以下技术栈进行开发:
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue | 3.4+ | 前端框架,使用组合式 API |
| TypeScript | 5.3+ | 类型安全,提升代码质量 |
| Vite | 5.0+ | 构建工具,快速热更新 |
| vue-router | 4.6+ | 路由管理 |
| Web Crypto API | 原生 | 核心加密算法实现 |
1.4 加密算法选择
本项目采用 AES-256-GCM 作为核心加密算法,其选择原因如下:
| 算法特性 | 说明 |
|---|---|
| 对称加密 | 加密和解密使用相同密钥,效率高 |
| 256位密钥 | 密钥长度 256 位,暴力破解需 2^256 次尝试 |
| GCM 模式 | Galois/Counter Mode,提供机密性和完整性验证 |
| 硬件加速 | 现代 CPU 普遍支持 AES-NI 指令集,加密速度快 |
| 标准化 | NIST 标准算法,被广泛应用于 TLS、SSH 等协议 |
2. 系统架构设计
2.1 整体目录结构
vue-app/
├── src/
│ ├── components/
│ │ └── CryptoTool.vue # 主加密/解密组件
│ ├── views/
│ │ └── CryptoView.vue # 视图页面
│ ├── services/
│ │ └── CryptoService.ts # 加密服务层
│ ├── types/
│ │ └── crypto.ts # 类型定义
│ ├── router/
│ │ └── index.ts # 路由配置
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
└── package.json
2.2 核心数据类型定义
// 加密进度
export interface EncryptionProgress {
stage: 'deriving' | 'encrypting' | 'decrypting' | 'done'
progress: number
message: string
}
// 文件记录
export interface FileRecord {
id: string
originalName: string
fileName: string
fileSize: number
encryptedSize: number
encrypted: boolean
createdAt: number
lastAccessed: number
}
2.3 加密文件格式
加密后的文件采用自定义格式存储,结构如下:
[加密数据][Salt(16字节)][IV(12字节)][文件名长度(4字节)][原始文件名]
这种设计将加密元数据附加在文件末尾,确保解密时能够正确恢复原始文件名。
3. Web Crypto API 核心实现
3.1 密钥派生(PBKDF2)
使用 PBKDF2(Password-Based Key Derivation Function 2)从用户密码派生出加密密钥:
static async deriveKey(
password: string,
salt: Uint8Array,
progress?: (p: EncryptionProgress) => void
): Promise<CryptoKey> {
progress?.({ stage: 'deriving', progress: 10, message: '正在派生密钥...' })
const enc = new TextEncoder()
const passwordKey = await crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveKey']
)
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: this.PBKDF2_ITERATIONS,
hash: 'SHA-256'
},
passwordKey,
{ name: 'AES-GCM', length: this.KEY_LENGTH },
false,
['encrypt', 'decrypt']
)
progress?.({ stage: 'deriving', progress: 30, message: '密钥派生完成' })
return key
}
其中 PBKDF2_ITERATIONS 设置为 100,000 次迭代,这是 OWASP 推荐的安全值,能够有效抵抗暴力破解攻击。
3.2 文件加密流程
static async encryptFile(
fileData: ArrayBuffer,
password: string,
progress?: (p: EncryptionProgress) => void
): Promise<{ encrypted: ArrayBuffer; salt: Uint8Array; iv: Uint8Array }> {
progress?.({ stage: 'encrypting', progress: 0, message: '准备加密...' })
// 生成随机 Salt 和 IV
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH))
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH))
// 派生密钥
const key = await this.deriveKey(password, salt, progress)
progress?.({ stage: 'encrypting', progress: 40, message: '正在加密文件...' })
// 执行 AES-GCM 加密
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
fileData
)
progress?.({ stage: 'encrypting', progress: 100, message: '加密完成' })
return { encrypted, salt, iv }
}
3.3 文件解密流程
static async decryptFile(
encryptedData: ArrayBuffer,
password: string,
salt: Uint8Array,
iv: Uint8Array,
progress?: (p: EncryptionProgress) => void
): Promise<ArrayBuffer> {
progress?.({ stage: 'decrypting', progress: 0, message: '准备解密...' })
const key = await this.deriveKey(password, salt, progress)
progress?.({ stage: 'decrypting', progress: 40, message: '正在解密文件...' })
try {
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encryptedData
)
progress?.({ stage: 'done', progress: 100, message: '解密完成' })
return decrypted
} catch (error) {
throw new Error('解密失败:密码错误或文件已损坏')
}
}
GCM 模式的一个重要特性是内置完整性验证。如果密文被篡改或密码错误,crypto.subtle.decrypt 会抛出异常。
3.4 加密文件打包
将加密数据、Salt、IV 和原始文件名打包成一个文件:
const encoder = new TextEncoder()
const nameBytes = encoder.encode(pf.file.name)
const nameLength = new Uint8Array(4)
new DataView(nameLength.buffer).setUint32(0, nameBytes.length)
const metadata = new Uint8Array(48 + nameBytes.length)
metadata.set(salt, 0) // Salt: 16 字节
metadata.set(iv, 16) // IV: 12 字节
metadata.set(nameLength, 28) // 文件名长度: 4 字节
metadata.set(nameBytes, 32) // 原始文件名
const combined = new Uint8Array(encrypted.byteLength + metadata.length)
combined.set(new Uint8Array(encrypted), 0)
combined.set(metadata, encrypted.byteLength)
resultBlob = new Blob([combined])
3.5 强密码生成
使用密码学安全的随机数生成器创建强密码:
static async generateSecurePassword(length: number = 16): Promise<string> {
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'
const charsetLength = charset.length
const array = new Uint32Array(length)
crypto.getRandomValues(array)
let password = ''
for (let i = 0; i < length; i++) {
password += charset[array[i] % charsetLength]
}
return password
}
crypto.getRandomValues 使用的是系统级的熵源,生成的随机数不可预测,适合用于密码生成。
4. 前端界面实现
4.1 密码强度检测
实时检测密码强度并可视化展示:
const passwordStrength = computed(() => {
const p = password.value
let score = 0
if (p.length >= 8) score++
if (p.length >= 12) score++
if (p.length >= 16) score++
if (/[a-z]/.test(p)) score++
if (/[A-Z]/.test(p)) score++
if (/\d/.test(p)) score++
if (/[^a-zA-Z0-9]/.test(p)) score++
if (score <= 2) return { level: 'weak', label: '弱', color: '#f44336', percent: 25 }
if (score <= 4) return { level: 'medium', label: '中', color: '#ff9800', percent: 50 }
if (score <= 5) return { level: 'strong', label: '强', color: '#4caf50', percent: 75 }
return { level: 'very-strong', label: '很强', color: '#2196f3', percent: 100 }
})
4.2 批量文件处理
支持同时选择多个文件进行批量加密/解密:
const processFiles = async () => {
if (!canProcess.value) return
isProcessing.value = true
for (let i = 0; i < pendingFiles.value.length; i++) {
const pf = pendingFiles.value[i]
pf.status = 'processing'
try {
const fileData = await pf.file.arrayBuffer()
const isEncrypted = CryptoService.isEncryptedFile(pf.file.name)
if (isEncrypted) {
// 解密流程
const metadataStart = fileData.byteLength - 48
const metadata = new Uint8Array(fileData.slice(metadataStart))
const salt = metadata.slice(0, 16)
const iv = metadata.slice(16, 28)
const nameLength = new DataView(metadata.slice(28, 32).buffer).getUint32(0)
const originalName = new TextDecoder().decode(metadata.slice(32, 32 + nameLength))
const encryptedContent = fileData.slice(0, metadataStart)
const decrypted = await CryptoService.decryptFile(
encryptedContent,
password.value,
salt,
iv,
(p) => { pf.progress = p }
)
resultBlob = new Blob([decrypted])
outputName = originalName
} else {
// 加密流程
const { encrypted, salt, iv } = await CryptoService.encryptFile(
fileData,
password.value,
(p) => { pf.progress = p }
)
// 打包元数据
const combined = new Uint8Array(encrypted.byteLength + metadata.length)
combined.set(new Uint8Array(encrypted), 0)
combined.set(metadata, encrypted.byteLength)
resultBlob = new Blob([combined])
outputName = CryptoService.getEncryptedName(pf.file.name)
}
pf.resultBlob = resultBlob
pf.encryptedName = outputName
pf.status = 'done'
} catch (error) {
pf.status = 'error'
pf.errorMessage = error instanceof Error ? error.message : '处理失败'
}
}
isProcessing.value = false
}
4.3 操作历史记录
将每次加密/解密操作记录到 localStorage:
const saveRecord = (record: FileRecord) => {
fileRecords.value.unshift(record)
if (fileRecords.value.length > 100) {
fileRecords.value = fileRecords.value.slice(0, 100)
}
localStorage.setItem('crypto-file-records', JSON.stringify(fileRecords.value))
}
5. 完整代码展示
5.1 CryptoService 加密服务
import type { EncryptionProgress } from '../types/crypto'
export class CryptoService {
private static readonly SALT_LENGTH = 16
private static readonly IV_LENGTH = 12
private static readonly PBKDF2_ITERATIONS = 100000
private static readonly KEY_LENGTH = 256
static async deriveKey(
password: string,
salt: Uint8Array,
progress?: (p: EncryptionProgress) => void
): Promise<CryptoKey> {
progress?.({ stage: 'deriving', progress: 10, message: '正在派生密钥...' })
const enc = new TextEncoder()
const passwordKey = await crypto.subtle.importKey(
'raw',
enc.encode(password),
'PBKDF2',
false,
['deriveKey']
)
const key = await crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: this.PBKDF2_ITERATIONS,
hash: 'SHA-256'
},
passwordKey,
{ name: 'AES-GCM', length: this.KEY_LENGTH },
false,
['encrypt', 'decrypt']
)
progress?.({ stage: 'deriving', progress: 30, message: '密钥派生完成' })
return key
}
static async encryptFile(
fileData: ArrayBuffer,
password: string,
progress?: (p: EncryptionProgress) => void
): Promise<{ encrypted: ArrayBuffer; salt: Uint8Array; iv: Uint8Array }> {
progress?.({ stage: 'encrypting', progress: 0, message: '准备加密...' })
const salt = crypto.getRandomValues(new Uint8Array(this.SALT_LENGTH))
const iv = crypto.getRandomValues(new Uint8Array(this.IV_LENGTH))
const key = await this.deriveKey(password, salt, progress)
progress?.({ stage: 'encrypting', progress: 40, message: '正在加密文件...' })
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
fileData
)
progress?.({ stage: 'encrypting', progress: 100, message: '加密完成' })
return { encrypted, salt, iv }
}
static async decryptFile(
encryptedData: ArrayBuffer,
password: string,
salt: Uint8Array,
iv: Uint8Array,
progress?: (p: EncryptionProgress) => void
): Promise<ArrayBuffer> {
progress?.({ stage: 'decrypting', progress: 0, message: '准备解密...' })
const key = await this.deriveKey(password, salt, progress)
progress?.({ stage: 'decrypting', progress: 40, message: '正在解密文件...' })
try {
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encryptedData
)
progress?.({ stage: 'done', progress: 100, message: '解密完成' })
return decrypted
} catch (error) {
throw new Error('解密失败:密码错误或文件已损坏')
}
}
static async generateSecurePassword(length: number = 16): Promise<string> {
const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'
const charsetLength = charset.length
const array = new Uint32Array(length)
crypto.getRandomValues(array)
let password = ''
for (let i = 0; i < length; i++) {
password += charset[array[i] % charsetLength]
}
return password
}
static formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
static isEncryptedFile(name: string): boolean {
return name.endsWith('.encrypted')
}
static getOriginalName(encryptedName: string): string {
if (this.isEncryptedFile(encryptedName)) {
return encryptedName.slice(0, -10)
}
return encryptedName
}
static getEncryptedName(originalName: string): string {
return originalName + '.encrypted'
}
}
5.2 路由配置
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'CryptoTool',
component: () => import('../views/CryptoView.vue'),
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router
6. 项目构建与部署
6.1 环境要求
- Node.js 16+
- 支持 Web Crypto API 的现代浏览器
6.2 安装依赖
cd vue-app
npm install
6.3 开发模式
npm run dev
启动后访问 http://localhost:5173 即可使用。
6.4 构建生产版本
npm run build
构建产物将输出到 dist 目录。
6.5 预览生产版本
npm run preview
7. 使用指南
7.1 加密文件
- 选择文件:拖拽文件到上传区域,或点击选择文件
- 设置密码:输入至少 8 位的密码,或点击"生成强密码"
- 开始加密:点击"开始处理"按钮
- 下载文件:加密完成后,点击"下载"保存加密文件
7.2 解密文件
- 选择文件:拖拽
.encrypted文件到上传区域 - 输入密码:输入加密时使用的密码
- 开始解密:点击"开始处理"按钮
- 下载文件:解密完成后,点击"下载"恢复原始文件
7.3 批量操作
支持同时选择多个文件进行批量加密/解密,系统会依次处理每个文件并显示进度。
7.4 密码建议
| 密码类型 | 示例 | 安全等级 |
|---|---|---|
| 弱密码 | 12345678 |
易被破解 |
| 中密码 | password123 |
有一定安全性 |
| 强密码 | MyP@ssw0rd! |
较难破解 |
| 很强密码 | x7#Kp9$Qm2@Ln5! |
极难破解 |
建议使用系统生成的 16 位强密码,安全性最佳。
8. 安全性分析
8.1 加密强度
| 参数 | 值 | 说明 |
|---|---|---|
| 算法 | AES-256-GCM | 军事级加密算法 |
| 密钥长度 | 256 位 | 暴力破解需 2^256 次 |
| PBKDF2 迭代 | 100,000 次 | 抵抗字典攻击 |
| Salt 长度 | 128 位 | 防止彩虹表攻击 |
| IV 长度 | 96 位 | 确保每次加密结果不同 |
8.2 安全最佳实践
- 密码管理:不要使用简单密码,建议使用系统生成的强密码
- 密码存储:不要将密码记录在明文文件中,建议使用密码管理器
- 文件备份:加密前务必备份原始文件,防止密码遗忘
- 密码遗忘:AES 加密无法绕过密码,密码遗忘则数据永久丢失
8.3 性能指标
| 指标 | 数值 |
|---|---|
| 10MB 文件加密时间 | < 100ms |
| 100MB 文件加密时间 | < 1s |
| 内存占用 | 与文件大小成正比 |
| 浏览器兼容性 | Chrome 90+, Firefox 88+, Safari 14+ |
9. 技术原理详解
9.1 AES-GCM 工作原理
AES-GCM(Galois/Counter Mode)是一种认证加密模式,同时提供机密性和完整性保护:
明文 ──→ [AES 加密] ──→ 密文
│
└─→ [GHASH] ──→ 认证标签
- CTR 模式加密:使用 AES 在计数器模式下加密数据
- GHASH 认证:使用 Galois 域运算计算认证标签
- 完整性验证:解密时验证认证标签,确保数据未被篡改
9.2 PBKDF2 密钥派生
PBKDF2(Password-Based Key Derivation Function 2)将用户密码转换为加密密钥:
密码 ──→ [PBKDF2] ──→ 256位密钥
│
├─ Salt(随机)
└─ 100,000 次迭代
多次迭代的目的是增加计算成本,使暴力破解变得更加困难。
9.3 Salt 和 IV 的作用
| 参数 | 作用 | 为什么需要 |
|---|---|---|
| Salt | 确保相同密码产生不同密钥 | 防止彩虹表攻击 |
| IV | 确保相同明文产生不同密文 | 防止模式分析攻击 |
Salt 和 IV 都是随机生成的,不需要保密,但必须与加密数据一起保存。
10. 未来优化方向
10.1 大文件流式加密
对于超大文件(如视频文件),可以采用流式加密,分块读取和加密,降低内存占用。
10.2 非对称加密支持
引入 RSA 或 ECC 非对称加密,支持公钥加密、私钥解密的使用场景。
10.3 文件分片加密
支持大文件的分片加密,便于上传到云存储并支持断点续传。
10.4 指纹解锁
在支持 Web Authentication API 的浏览器中,可以使用指纹或面部识别替代密码。
11. 总结
本项目基于 Vue3 和 Web Crypto API 实现了一个功能完善的文件加密/解密工具,核心特性包括:
- 安全的加密算法:使用 AES-256-GCM + PBKDF2,提供军事级别的安全保障
- 本地加密处理:文件在浏览器本地完成加密,不会上传到任何服务器
- 批量文件处理:支持同时加密/解密多个文件,提高工作效率
- 密码强度检测:实时检测密码强度,支持一键生成强密码
- 操作记录管理:记录每次加密/解密操作,方便追溯
通过本项目,我们深入学习了 Web Crypto API、AES-GCM 加密算法、PBKDF2 密钥派生、ArrayBuffer 操作等前端安全相关技术。同时,也体会到了浏览器原生加密能力的强大。
如果你对文件加密技术感兴趣,可以参考本项目的实现思路,在此基础上进行二次开发或优化。欢迎在评论区交流讨论!
更多推荐


所有评论(0)