在鸿蒙(HarmonyOS)应用开发中,用户敏感文件(如账号凭证、隐私配置、加密日志等)的存储安全是核心需求之一。一旦这些文件被其他应用非法读取或用户手动拷贝泄露,将直接威胁用户数据安全。本文将从鸿蒙系统自带的安全存储机制入手,结合 ArkTS(鸿蒙主推开发语言)实战代码,详解敏感文件的安全存储方案,帮助开发者构建高安全性的应用。
文章目录
- 一、鸿蒙敏感文件存储的核心安全痛点
- 二、优先用鸿蒙系统自带安全存储机制
-
- [1. 应用沙箱私有目录:基础防护](#1. 应用沙箱私有目录:基础防护)
- [2. 系统加密存储(EncryptedFile):高敏感数据防护](#2. 系统加密存储(EncryptedFile):高敏感数据防护)
- [3. KeyStore 密钥管理:保护加密密钥](#3. KeyStore 密钥管理:保护加密密钥)
- 三、手动加密方案:应对复杂场景
- [四、最佳实践:敏感文件存储的 "黄金法则"](#四、最佳实践:敏感文件存储的 “黄金法则”)
-
- [1. 分层选择存储方案](#1. 分层选择存储方案)
- [2. 禁止硬编码密钥](#2. 禁止硬编码密钥)
- [3. 限制文件权限](#3. 限制文件权限)
- [4. 清理敏感缓存](#4. 清理敏感缓存)
- 五、总结
一、鸿蒙敏感文件存储的核心安全痛点
在讨论解决方案前,我们需先明确敏感文件存储面临的两大核心风险:
跨应用访问风险:普通文件若存储在公共目录,可能被其他拥有权限的应用读取;
手动拷贝风险:用户通过文件管理器或 ADB 工具拷贝文件后,可在其他设备上直接查看内容;
密钥泄露风险:若加密密钥硬编码在代码中,攻击者反编译应用即可获取密钥,破解加密文件。
针对这些风险,鸿蒙提供了 "系统级防护 + 开发者自定义加密" 的双层解决方案,开发者可根据需求灵活选择。
二、优先用鸿蒙系统自带安全存储机制
鸿蒙为敏感数据设计了三类系统级存储能力,无需手动加密即可满足大部分场景,安全性与兼容性更强,是开发首选。
1. 应用沙箱私有目录:基础防护
鸿蒙为每个应用分配独立的 "沙箱目录",其他应用(包括系统应用)默认无访问权限,用户也无法通过文件管理器直接查看。这是最基础且易用的安全存储方案,适合存储无需跨应用共享的敏感文件。
ArkTS 实现代码:
bash
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
/**
* 向应用沙箱私有目录写入敏感文件
* @param context 应用上下文(从Ability中获取)
* @param filename 文件名(如"user_config.dat")
* @param content 待存储的敏感内容
*/
export async function saveToPrivateDir(
context: Context,
filename: string,
content: string
): Promise<void> {
try {
// 1. 获取应用私有目录(files目录,路径格式:/data/app/el2/100/base/[包名]/files)
const privateDir = context.filesDir;
const filePath = `${privateDir}/${filename}`;
// 2. 打开文件(若不存在则创建,仅当前应用可读写)
const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 3. 写入内容(覆盖原有内容)
const textEncoder = new TextEncoder();
const contentBuffer = textEncoder.encode(content);
await fs.write(file.fd, contentBuffer);
// 4. 关闭文件,释放资源
await fs.close(file.fd);
console.log(`敏感文件已保存至私有目录:${filePath}`);
} catch (error) {
const err = error as BusinessError;
console.error(`写入私有目录失败:${err.code} - ${err.message}`);
throw error;
}
}
核心优势
权限隔离:私有目录的文件默认仅当前应用可读写,通过fs.setPermissions可进一步限制权限(如仅可读);
无需加密:系统层面已阻断外部访问,普通敏感数据(如应用配置)无需额外加密;
易用性高:通过context.filesDir直接获取路径,无需处理复杂的权限申请。
2. 系统加密存储(EncryptedFile):高敏感数据防护
对于账号密码、支付信息等极高敏感数据,仅靠沙箱隔离仍有风险(如设备 root 后可能被访问)。此时可使用鸿蒙的EncryptedFile接口,文件内容会被系统自动加密后存储在安全分区,解密过程由系统托管,开发者无需处理加密细节。
ArkTS 实现代码:
bash
import fs from '@ohos.file.fs';
import { EncryptedFile } from '@ohos.file.encryptedFile';
import { BusinessError } from '@ohos.base';
/**
* 使用系统加密存储高敏感文件
* @param context 应用上下文
* @param filename 加密文件名(如"account_secret.dat")
* @param content 高敏感内容(如账号密码)
*/
export async function saveToEncryptedDir(
context: Context,
filename: string,
content: string
): Promise<void> {
try {
// 1. 获取系统加密目录(需API 9+,路径由系统管理,不可直接访问)
const encryptedDir = await EncryptedFile.getEncryptedDir(context);
const filePath = `${encryptedDir}/${filename}`;
// 2. 打开加密文件(系统自动处理加密逻辑)
const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 3. 写入内容(系统实时加密)
const contentBuffer = new TextEncoder().encode(content);
await fs.write(file.fd, contentBuffer);
// 4. 关闭文件
await fs.close(file.fd);
console.log(`高敏感文件已加密存储:${filePath}`);
} catch (error) {
const err = error as BusinessError;
console.error(`加密存储失败:${err.code} - ${err.message}`);
throw error;
}
}
核心优势
系统级加密:文件内容采用硬件级加密算法(如 AES-256),密钥由鸿蒙安全芯片管理,无法导出;
防 root 访问:即使设备被 root,加密分区的文件也无法解密;
无感知使用:开发者调用方式与普通文件一致,无需手动实现加密逻辑。
3. KeyStore 密钥管理:保护加密密钥
若需手动加密文件,加密密钥的安全存储至关重要。鸿蒙的KeyStore服务可将密钥存储在硬件安全区域(如 TEE 可信执行环境),防止密钥被反编译或内存 dump 获取,是手动加密方案的 "安全基石"。
ArkTS 实现代码(密钥生成与获取)
bash
import keystore from '@ohos.security.keystore';
import { BusinessError } from '@ohos.base';
/**
* 从KeyStore获取或创建AES加密密钥
* @param keyAlias 密钥别名(需唯一,如"app_sensitive_key")
* @returns AES密钥(不可导出)
*/
export async function getOrCreateAesKey(keyAlias: string): Promise<keystore.SecretKey> {
try {
// 1. 检查密钥是否已存在
const keyExists = await keystore.keyExists(keyAlias);
if (keyExists) {
console.log(`密钥${keyAlias}已存在,直接获取`);
return await keystore.getSecretKey(keyAlias);
}
// 2. 若不存在,创建AES-256密钥(不可导出)
const keyParam: keystore.AesKeyGenParameterSpec = {
keySize: 256, // AES-256,安全性更高
purpose: [keystore.KeyPurpose.ENCRYPT, keystore.KeyPurpose.DECRYPT], // 密钥用途
isExtractable: false, // 禁止导出密钥,防止泄露
isUserAuthenticationRequired: false // 可选:是否需要用户验证(如指纹)
};
await keystore.generateAesKey(keyAlias, keyParam);
console.log(`密钥${keyAlias}创建成功`);
// 3. 返回新创建的密钥
return await keystore.getSecretKey(keyAlias);
} catch (error) {
const err = error as BusinessError;
console.error(`密钥管理失败:${err.code} - ${err.message}`);
throw error;
}
}
三、手动加密方案:应对复杂场景
当需要跨设备同步加密文件或自定义加密策略时,需在系统机制基础上增加手动加密。推荐使用AES-GCM 模式(带认证的对称加密),兼顾安全性与效率,同时防止文件被篡改。
ArkTS 实现:AES-GCM 文件加密工具类
bash
import fs from '@ohos.file.fs';
import cryptoFramework from '@ohos.security.cryptoFramework';
import keystore from '@ohos.security.keystore';
import { BusinessError } from '@ohos.base';
/**
* AES-GCM文件加密工具类(基于ArkTS)
*/
export class AesGcmFileEncryptor {
private context: Context;
private keyAlias: string;
constructor(context: Context, keyAlias: string = 'app_aes_key') {
this.context = context;
this.keyAlias = keyAlias;
}
/**
* 加密文件并存储到应用沙箱
* @param filename 目标文件名(如"encrypted_user_data.dat")
* @param rawContent 原始敏感内容
*/
async encryptAndSave(filename: string, rawContent: string): Promise<void> {
try {
// 1. 获取AES密钥(从KeyStore)
const secretKey = await getOrCreateAesKey(this.keyAlias);
// 2. 生成12字节IV向量(GCM模式推荐,确保每次加密IV不同)
const iv = cryptoFramework.generateRandom(12);
// 3. 初始化加密器(AES-GCM模式,无填充)
const cipher = cryptoFramework.createCipher('AES/GCM/NoPadding');
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, secretKey, { iv: iv });
// 4. 加密内容(返回加密后的数据+认证标签)
const rawBuffer = new TextEncoder().encode(rawContent);
const encryptedResult = await cipher.doFinal(rawBuffer);
// 5. 组合IV和加密数据(IV需与密文一起存储,解密时需用到)
const combinedBuffer = new Uint8Array(iv.length + encryptedResult.length);
combinedBuffer.set(iv, 0); // 前12字节为IV
combinedBuffer.set(encryptedResult, iv.length); // 后续为加密数据
// 6. 写入应用沙箱私有目录
const privateDir = this.context.filesDir;
const filePath = `${privateDir}/${filename}`;
const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
await fs.write(file.fd, combinedBuffer);
await fs.close(file.fd);
console.log(`文件加密成功,存储路径:${filePath}`);
} catch (error) {
const err = error as BusinessError;
console.error(`文件加密失败:${err.code} - ${err.message}`);
throw error;
}
}
/**
* 读取并解密文件
* @param filename 加密文件名
* @returns 解密后的原始内容
*/
async readAndDecrypt(filename: string): Promise<string> {
try {
// 1. 读取加密文件
const privateDir = this.context.filesDir;
const filePath = `${privateDir}/${filename}`;
const file = await fs.open(filePath, fs.OpenMode.READ_ONLY);
const fileStat = await fs.stat(filePath);
const combinedBuffer = new Uint8Array(fileStat.size);
await fs.read(file.fd, combinedBuffer.buffer);
await fs.close(file.fd);
// 2. 分离IV和加密数据(前12字节为IV)
const iv = combinedBuffer.subarray(0, 12);
const encryptedData = combinedBuffer.subarray(12);
// 3. 获取AES密钥
const secretKey = await getOrCreateAesKey(this.keyAlias);
// 4. 初始化解密器
const cipher = cryptoFramework.createCipher('AES/GCM/NoPadding');
await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, secretKey, { iv: iv });
// 5. 解密并返回原始内容
const decryptedBuffer = await cipher.doFinal(encryptedData);
return new TextDecoder().decode(decryptedBuffer);
} catch (error) {
const err = error as BusinessError;
console.error(`文件解密失败:${err.code} - ${err.message}`);
throw error;
}
}
}
// 复用之前的KeyStore密钥管理函数
async function getOrCreateAesKey(keyAlias: string): Promise<keystore.SecretKey> {
const keyExists = await keystore.keyExists(keyAlias);
if (keyExists) {
return await keystore.getSecretKey(keyAlias);
}
const keyParam: keystore.AesKeyGenParameterSpec = {
keySize: 256,
purpose: [keystore.KeyPurpose.ENCRYPT, keystore.KeyPurpose.DECRYPT],
isExtractable: false
};
await keystore.generateAesKey(keyAlias, keyParam);
return await keystore.getSecretKey(keyAlias);
}
手动加密的适用场景
跨设备同步:加密后的文件可通过云端同步,即使被拦截也无法解密;
自定义权限控制:如仅允许特定用户角色解密文件;
第三方集成:需向其他应用提供加密文件(非明文)时。
四、最佳实践:敏感文件存储的 "黄金法则"
结合鸿蒙系统特性与安全开发规范,总结以下最佳实践:
1. 分层选择存储方案

2. 禁止硬编码密钥
所有加密密钥必须通过KeyStore管理,禁止在代码中硬编码(如const key = "123456"),否则攻击者可通过反编译轻松获取密钥。
3. 限制文件权限
即使在私有目录,也可通过fs.setPermissions进一步限制权限,例如:
bash
// 设置文件仅当前应用可读(不可写,防止被篡改)
await fs.setPermissions(filePath, 0o400); // 八进制权限,4表示可读
4. 清理敏感缓存
临时敏感文件(如加密过程中的中间文件)使用后需立即删除,避免残留:
bash
// 删除临时文件
await fs.unlink(tempFilePath);
五、总结
简单总结一下,鸿蒙系统的敏感文件存储,我建议是优先使用系统自带的沙箱目录和加密存储,可满足大部分场景;复杂场景下结合KeyStore与 AES-GCM 手动加密,兼顾安全性与灵活性。这里我提供的只是 ArkTS 代码示例。希望对大家有所帮助。