起因:加密后的东西,一眼就能看出"被加密了"
说实话,这事儿困扰我挺久的。
我平时有个习惯,会在备忘录里记一些账号密码、私密想法之类的东西。用过几款加密工具,Base64 编码也好,AES 加密也好,输出的结果都是一堆乱码或者 U2FsdGVkX1... 这种字符串。
问题在哪?太明显了。
任何人瞥一眼就知道"这段内容被加密过"。被加密本身就是一种信息——它告诉别人"这里有秘密"。
我就想:能不能让加密结果看起来像一段正常的中文句子?外人看到觉得是一句莫名其妙但人畜无害的话,只有知道密钥的人才能还原?
这就是密语盒子的出发点。
做了什么:AES-GCM 加密 + 自定义字谱映射
核心思路分两步:
- 先用 AES-GCM 对明文做强加密(密钥通过 PBKDF2 从用户输入的密码派生),拿到密文字节流
-
- 把密文字节流按自定义的「字谱」做映射,把每个字节替换成一个汉字或可读字符
举个例子,假设我的字谱是一组 256 个汉字(刚好对应一个字节的 256 种取值),那加密后的每个字节都能找到对应的汉字。最终输出就是一串中文字,比如:
- 把密文字节流按自定义的「字谱」做映射,把每个字节替换成一个汉字或可读字符
“梧桐落叶时节青山远眺归雁南飞”
外人看了觉得像诗句碎片,实际上它是一段被加密的内容。
这和简单的替换密码(凯撒密码那种)完全不同——底层是 AES-GCM 这样的认证加密,字谱只是最后一层"视觉伪装"。破解者即使猜到了字谱映射关系,没有密钥照样还原不了明文。
为什么选鸿蒙
今年上半年开始做鸿蒙适配,1.2.0 版本已经上架。选 HarmonyOS 有几个原因:
- 鸿蒙的安全能力底座挺好,
@ohos.security.cryptoFramework提供了完整的 AES-GCM 和 PBKDF2 支持,不用自己引第三方库 -
- ArkTS 写 UI 确实快,声明式的方式做表单交互很顺手
-
- 说实话也有私心——鸿蒙应用市场现在竞争没那么卷,同类工具几乎没有
技术细节:鸿蒙侧的加密实现
加密核心用了 @ohos.security.cryptoFramework,这里贴一下密钥派生和加密的关键逻辑:
import cryptoFramework from '@ohos.security.cryptoFramework'
// PBKDF2 密钥派生
async function deriveKey(password: string, salt: Uint8Array): Promise<cryptoFramework.SymKey> {
let kdfSpec: cryptoFramework.PBKDF2Spec = {
algName: 'PBKDF2',
password: password,
salt: salt,
iterations: 100000,
keySize: 32
}
let kdf = cryptoFramework.createKdf('PBKDF2|SHA256')
let keyBlob = await kdf.generateSecret(kdfSpec)
let symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256')
return await symKeyGenerator.convertKey({ data: keyBlob.data })
}
// AES-GCM 加密
async function encrypt(plainText: Uint8Array, symKey: cryptoFramework.SymKey, iv: Uint8Array): Promise<Uint8Array> {
let cipher = cryptoFramework.createCipher('AES256|GCM|PKCS7')
let gcmParams: cryptoFramework.GcmParamsSpec = {
iv: { data: iv },
aad: { data: new Uint8Array(0) },
authTag: { data: new Uint8Array(16) },
algName: 'GcmParamsSpec'
}
await cipher.init(cryptoFramework.CryptoMode.ENCRYPT_MODE, symKey, gcmParams)
let output = await cipher.doFinal({ data: plainText })
return output.data
}
```
拿到加密后的字节数组后,就是字谱映射的环节。这一步反而简单——本质就是一个 256 长度的数组查表:
```arkts
function bytesToCipherText(encrypted: Uint8Array, charset: string[]): string {
let result = ''
for (let i = 0; i < encrypted.length; i++) {
result += charset[encrypted[i]] // 每个字节 0-255 对应字谱中的一个字符
}
return result
}
```
解密就是反过来:先把密文中每个字符在字谱里查位置还原成字节,再用 AES-GCM 解密。
## ArkUI 做交互的一些体会
UI 层用的 ArkUI,几个页面:加密页、解密页、字谱管理页。
说实话 ArkUI 的 `TextArea` 组件在处理大段文本输入时体验还行,但有个小坑——`onChange` 在输入法组合输入时的触发时机和我预期不太一样,调了一阵才稳定。
字谱管理那边,我用了 `@ohos.data.preferences` 做本地持久化,每套字谱就是一个 256 长度的字符串数组。用户可以自己编辑、导入导出,这样不同朋友之间可以约定不同的字谱。
## 关于"完全离线"这件事
密语盒子没有网络权限。对,一个字节都不往外发。
这不是什么高尚的设计哲学,纯粹是因为——一个加密工具如果联网,我自己用着都不放心。密钥派生、加解密、字谱存储全在本地完成。鸿蒙的沙箱机制保证了 preferences 数据不会被其他应用读取。
## 几个真实使用场景
1. **备忘录里存密码**:把"支付宝密码: abc123"加密后变成"碧海潮生曲终人散",存在系统便签里。手机被人借去用也不担心。
2. **和朋友发"密电"**:两个人约定同一套字谱和密码,在微信聊天里发密文。截图发朋友圈别人也看不懂——关键是看起来不像被加密的,不会引起好奇心。
3. **日记类内容**:有些想法不想被任何人看到,加密后发在自己的社交平台上当"树洞",只有自己能解。
## 当前状态和一些数据
- 上架时间:2024年,鸿蒙应用市场可搜"密语盒子"
- - 当前版本:1.2.0
- - 下载量:说实话还很少,刚起步阶段
- - 包体积:控制在 5MB 以内(没有网络模块,没有大型依赖)
下载量不高我觉得正常——这东西的目标用户本来就小众,是那些对隐私有洁癖、同时觉得传统加密工具"太丑太明显"的人。
## 踩过的坑
1. **字谱长度问题**:一开始我想用 65536 个字符的字谱(对应两个字节一组映射),这样密文更短。但实际操作中,找到 65536 个不重复的、看起来自然的汉字组合太难了,而且 Unicode 有些区段的字在鸿蒙字体里显示不出来。最后还是退回到 256 字符的方案,接受密文长一些。
2. **GCM 的 authTag 处理**:AES-GCM 加密后会产生一个认证标签,解密时需要用到。一开始我把 authTag 拼在密文尾部一起做字谱映射,结果解密时拆分位置算错了,调了一整个下午。
3. **PBKDF2 迭代次数**:10 万次迭代在手机上大概要 200-300ms,用户体感还行。试过 50 万次,等待超过 1 秒就有人受不了了。
## 写在后面
如果你也在做鸿蒙开发,特别是安全相关的方向,欢迎交流。`@ohos.security.cryptoFramework` 这个模块的文档说实话还不够详细,很多用法是我翻源码和试错试出来的。
鸿蒙应用市场搜索「密语盒子」可以直接下载体验。有问题评论区聊。
3682



被折叠的 条评论
为什么被折叠?



