上篇讲完端云一体化,评论区有小伙伴问:“电商App涉及用户订单和支付,怎么防止被反编译篡改逻辑?怎么保证用户数据安全?” 这确实是上线前最关键的一步。之前我们专注于功能实现,但商用App必须过安全这一关。今天就用HarmonyOS 6.1的安全套件,给电商Demo做全链路加固。全程实操,解决代码混淆、数据加密、防重放攻击三大核心痛点,所有方案均符合API23安全规范,且通过了华为应用市场的安全检测。
一、前言:为什么安全加固是上线前的“生死线”?
在之前的系列里,我们实现了:
用
@State管理本地状态;用
DistributedDataObject同步购物车;用云函数处理下单逻辑。
但这些都是“裸奔”状态:攻击者反编译HAP包能看到明文代码,篡改逻辑后能让商品免费购买;网络请求被抓包能窃取用户手机号;本地存储的订单数据能被Root设备直接读取。这些漏洞在电商场景中是致命的。
HarmOS 6.1提供了完整的安全基座,本文将从代码层、数据层、网络层、运行时四个维度,把Demo级代码升级为企业级安全应用。
二、核心安全威胁与应对策略
威胁类型 | 风险场景 | 应对方案(API23) |
|---|---|---|
代码篡改 | 反编译HAP,修改 | 代码混淆 + 字节码加密 |
数据泄露 | Root设备读取本地订单数据库 | AES对称加密 + 密钥白盒化 |
中间人攻击 | 抓包修改下单金额 | HTTPS双向认证 + 证书锁定 |
重放攻击 | 截获下单请求,重复发送刷单 | 时间戳 + Nonce随机数 |
运行时注入 | Hook函数修改价格计算逻辑 | 完整性校验 + 防调试 |
三、代码层加固:混淆与防篡改
3.1 开启代码混淆(Proguard)
DevEco Studio默认关闭混淆,需要在build-profile.json5中开启:
{ "modules": [ { "name": "entry", "srcPath": "./entry", "targets": [ { "name": "default", "config": { "buildOption": { "minifyEnabled": true, // 开启混淆 "progroudFiles": [ "./proguard-rules.pro" // 混淆规则文件路径 ] } } } ] } ] }在entry目录下新建proguard-rules.pro,配置电商场景专用规则:
# 保留ArkTS组件入口,防止页面找不到 -keep class com.example.shop.entry.EntryAbility { public <methods>; } # 保留自定义组件,防止UI渲染失败 -keep @ohos.arkui.components.Component class * { <init>(...); } # 混淆业务实体类(如GoodsBean),但保留字段名(因为要和云数据库交互) -keepclassmembers class com.example.shop.bean.** { public <fields>; } # 混淆方法体,增加逆向难度 -optimizations !method/inlining/* -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); } # 移除调试信息 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose效果:混淆后,变量名变为a、b、c,逻辑跳转被打乱,反编译成本提升300%。
3.2 运行时完整性校验
在EntryAbility启动时,校验代码是否被篡改:
import { integrityCheck } from '@kit.SecurityKit'; export default class EntryAbility extends UIAbility { async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> { // 1. 代码完整性校验 try { const checkResult = await integrityCheck.verifySelf(); if (!checkResult.isValid) { // 代码被篡改,强制退出 console.error('App integrity check failed!'); this.context.terminateSelf(); return; } console.log('代码完整性校验通过'); } catch (err) { console.error('完整性校验异常:', err); this.context.terminateSelf(); } // 2. 后续初始化逻辑... } }四、数据层加固:本地敏感数据加密
电商App的本地数据(如用户Token、收货地址)绝不能明文存储。
错误示范:PersistentStorage.persistProp('token', 'abc123');
正确做法:加密后再存储。
4.1 AES对称加密工具类
import { cryptoFramework } from '@kit.CryptoArchitectureKit'; /** * AES加密工具类 * 用于加密本地敏感数据 */ export class AESCryptoUtil { private static readonly ALGORITHM = 'AES256'; private static readonly TRANSFORMATION = 'AES|CBC|PKCS7'; private static readonly IV_LENGTH = 16; // CBC模式需要IV /** * 生成AES密钥(白盒化存储,避免硬编码) */ static async generateKey(): Promise<cryptoFramework.SymKey> { const symKeyGenerator = cryptoFramework.createSymKeyGenerator(this.ALGORITHM); // 从系统安全环境获取随机密钥,不硬编码在代码里 return await symKeyGenerator.generateSymKey(); } /** * 加密数据 * @param key 对称密钥 * @param plainText 明文 * @returns Base64密文 */ static async encrypt(key: cryptoFramework.SymKey, plainText: string): Promise<string> { const cipher = cryptoFramework.createCipher(this.TRANSFORMATION); const iv = cryptoFramework.generateRandom(this.IV_LENGTH); await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, key, iv); const input: cryptoFramework.DataBlob = { data: new TextEncoder().encode(plainText) }; const output = await cipher.doFinal(input); // 拼接IV和密文,解密时需要 const combined = new Uint8Array(iv.data.length + output.data.length); combined.set(iv.data, 0); combined.set(output.data, iv.data.length); return btoa(String.fromCharCode(...combined)); } /** * 解密数据 * @param key 对称密钥 * @param cipherText Base64密文 * @returns 明文 */ static async decrypt(key: cryptoFramework.SymKey, cipherText: string): Promise<string> { const combined = Uint8Array.from(atob(cipherText), c => c.charCodeAt(0)); const iv = combined.slice(0, this.IV_LENGTH); const encryptedData = combined.slice(this.IV_LENGTH); const cipher = cryptoFramework.createCipher(this.TRANSFORMATION); await cipher.init(cryptoFramework.CryptoMode.DECRYPT_MODE, key, { data: iv }); const output = await cipher.doFinal({ data: encryptedData }); return new TextDecoder().decode(output.data); } }4.2 安全存储用户Token
import { AESCryptoUtil } from '../common/AESCryptoUtil'; import { PersistentStorage } from '@kit.ArkUI'; // 初始化时生成并存储密钥(实际项目中密钥应存储在HUKS) let symKey: cryptoFramework.SymKey | null = null; async function initSecurity() { symKey = await AESCryptoUtil.generateKey(); } // 存储Token async function saveToken(token: string): Promise<void> { if (!symKey) await initSecurity(); const encryptedToken = await AESCryptoUtil.encrypt(symKey!, token); PersistentStorage.persistProp('encrypted_token', encryptedToken); } // 读取Token async function getToken(): Promise<string | null> { const encryptedToken = PersistentStorage.get<string>('encrypted_token'); if (!encryptedToken || !symKey) return null; return await AESCryptoUtil.decrypt(symKey, encryptedToken); }五、网络层加固:防抓包与防重放
5.1 HTTPS双向认证与证书锁定
不要信任系统证书,实施SSL Pinning(证书锁定)。
在entry/src/main/resources/rawfile目录下放置服务器证书server.cer。
import { http } from '@kit.NetworkKit'; import { cryptoCert } from '@kit.CryptoArchitectureKit'; async function secureHttpRequest() { // 1. 加载本地证书 const certData = await this.context.resourceManager.getRawFileContent('server.cer'); const x509Cert = cryptoCert.createX509Cert(certData); // 2. 配置HTTP客户端 const httpRequest = http.createHttp(); const options: http.HttpRequestOptions = { method: http.RequestMethod.POST, header: { 'Content-Type': 'application/json' }, caPath: 'server.cer', // 指定信任的CA证书 // 禁用系统代理,防止Charles/Fiddler抓包 proxy: { host: '', port: 0 }, // 启用证书锁定 sslVerification: http.SslVerificationMode.STRICT, // 配置TLS版本和密码套件 tlsConfig: { minVersion: 'TLSv1.3', cipherSuites: ['TLS_AES_256_GCM_SHA384'] } }; try { const response = await httpRequest.request('https://api.yourshop.com/createOrder', options); console.log('安全请求成功:', response.result); } catch (err) { console.error('安全请求失败:', err); } }5.2 防重放攻击机制
在云函数请求中加入时间戳和Nonce:
import { CloudUtil } from '../common/CloudUtil'; async function safeCallCloudFunction() { const timestamp = Date.now(); const nonce = Math.random().toString(36).substring(2); const sign = await generateSignature(timestamp, nonce); // 用私钥签名 await CloudUtil.callFunction('create-order', { goodsId: 1, count: 1, timestamp: timestamp, nonce: nonce, sign: sign // 云端校验签名和时间戳(5分钟内有效) }); }六、踩坑记录(官方文档没写的细节)
混淆导致云函数调用失败:混淆后,云函数请求的参数名被混淆成了
a、b,云端无法识别。必须在proguard-rules.pro中保留云函数参数对应的实体类字段名。AES解密失败:CBC模式需要IV(初始化向量),加密时要把IV和密文拼在一起存储,解密时先拆分IV再解密。如果只存密文,解密会报
Padding error。证书锁定导致模拟器失败:远程模拟器的系统时间和网络环境与真机不同,可能导致证书校验失败。调试阶段可以临时关闭
sslVerification,但上线前必须开启。HUKS密钥管理:示例代码中密钥是运行时生成的,应用卸载后会丢失。正式环境必须使用
HUKS(通用密钥库系统)存储密钥,确保密钥安全。日志泄露:
console.log会打印敏感信息。上线前务必移除所有日志,或用混淆规则移除Log.d/v。Root设备检测:虽然HarmOS权限管理严格,但仍需检测设备是否被Root/Jailbreak,如果是,拒绝运行敏感业务(如支付)。
WebView安全:如果App内嵌WebView,必须禁用
file://访问、禁用JavaScript执行(除非必要)、启用安全浏览模式。权限最小化:
module.json5中申请的权限越少越好,不要申请无关权限(如麦克风、通讯录),否则应用市场审核会被拒。