3、常见加密操作及其性能影响:对称加密、非对称加密、哈希

加密操作在 Android 开发里太常见了。登录要加密、支付要加密、本地存个敏感数据也得加密。但说实话,很多人只调了 API,没考虑过性能开销。我见过不少 ANR,就是因为加密操作直接扔在主线程上跑。今天咱就掰开揉碎聊聊三类核心加密:对称、非对称、哈希。它们各自什么特点?性能差多少?怎么用才不会卡死 UI?

3.1 对称加密:性能之王,但密钥分发是难题

对称加密,说白了就是加解密用同一把钥匙。它的优点是快,非常快。硬件指令集(比如 AES-NI)能直接把加解密速度拉到 GB 级别。所以你想想看,如果要加密大量数据,对称加密几乎是不二之选。

我个人习惯首选 AES,密钥长度至少 256 位。模式上推荐 GCM(Galois/Counter Mode),既能加密又能校验完整性,不用额外再算哈希。嗯,这里要注意:ECB 模式千万别用,它会把相同明文块加密成相同密文块,安全上就是个筛子。

经验之谈: 我之前在一个即时通讯项目里加密聊天记录,数据量很大,用了 AES-256-GCM。单条消息加密耗时不到 1ms,完全不影响 UI 响应。但如果用错了模式或者密钥派生太慢,照样会卡。
// AES-256-GCM 加密示例
val secretKey = SecretKeySpec(keyBytes, "AES")
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(Cipher.ENCRYPT_MODE, secretKey)

val iv = cipher.iv  // 保存 IV,解密时需要
val ciphertext = cipher.doFinal(plaintext)

// 注意:doFinal 是耗时操作,务必在 IO 线程执行

3.2 非对称加密:安全之盾,但性能开销大

非对称加密使用密钥对:公钥加密,私钥解密。安全等级高,但代价是性能比对称加密慢了 2~3 个数量级。RSA 加密 256 字节的数据,在普通手机上可能要 10~50ms,而 AES 加密同样数据只需要微秒级。

为什么会这样?因为 RSA 依赖大数模幂运算,密钥越长计算越重。2048 位的 RSA 已经算底线了,4096 位更安全但更慢。ECC(椭圆曲线加密)在同安全强度下比 RSA 快不少,密钥也更短,我建议你优先考虑 ECC。

我记得有一次,一个金融 App 在登录时用 RSA 加密密码,直接在 UI 线程执行。结果用户连续输错三次密码后,界面完全卡死——主线程被 RSA 阻塞了足足 200ms。从那以后,我强制要求所有非对称加密必须用协程异步处理。

警告: 非对称加密是 ANR 的高发区。不要在 LaunchedEffect 或 rememberCoroutineScope 中不加调度器直接运行。建议使用 Dispatchers.IO 或自定义线程池。
// RSA 加密示例(异步执行)
suspend fun rsaEncrypt(data: ByteArray, publicKey: PublicKey): ByteArray =
    withContext(Dispatchers.IO) {
        val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
        cipher.init(Cipher.ENCRYPT_MODE, publicKey)
        cipher.doFinal(data)  // 可能耗时 10~50ms
    }

3.3 哈希:数据指纹,不可逆的利器

哈希算法不是加密,它不可逆。它的作用是给数据算一个固定长度的"指纹"。只要原始数据变了哪怕一个比特,哈希值就完全不同。你想想看,这特性用来做啥?密码存储、数据完整性校验、文件去重,都是经典场景。

SHA-256 是目前最通用的选择,安全强度够,性能也适中。MD5 和 SHA-1 已经被破解了,别再用了。不过哈希虽然比非对称加密快,但也不是零成本的——对大文件做哈希,照样可能卡住 UI。

我之前做过一个文件上传功能,需要对 100MB 的文件算 SHA-256 做完整性校验。如果直接在主线程算,UI 会卡死好几秒。后来改用 Dispatchers.IO 分块读取,每读 8KB 就更新一次进度,体验好了很多。

// SHA-256 哈希示例(分块读取大文件)
suspend fun sha256File(file: File): String = withContext(Dispatchers.IO) {
    val digest = MessageDigest.getInstance("SHA-256")
    val buffer = ByteArray(8192)
    FileInputStream(file).use { fis ->
        var bytesRead = fis.read(buffer)
        while (bytesRead != -1) {
            digest.update(buffer, 0, bytesRead)
            bytesRead = fis.read(buffer)
        }
    }
    digest.digest().joinToString("") { "%02x".format(it) }
}

3.4 性能对比与ANR风险分析

三种操作在 Android 设备上的性能差异很明显。我整理了一份典型数据,供你参考。测试环境:骁龙 8 Gen 2,单线程,数据量 1KB。

操作类型 代表算法 耗时(1KB) ANR风险 主要瓶颈
对称加密 AES-256-GCM 0.05 ~ 0.2 ms 低
(但大数据量需注意)
密钥派生、IV 生成
非对称加密 RSA-2048 10 ~ 50 ms 高
(必须异步)
模幂运算、密钥生成
哈希 SHA-256 0.2 ~ 0.8 ms 中
(大文件风险高)
文件 I/O、分块读取

从表格能看出:对称加密最安全,哈希最通用,非对称加密最危险(指 ANR 风险)。 但这不是说对称加密就可以随便在主线程跑。如果你加密 100MB 文件,AES 也要几十毫秒,同样会卡。

我个人有个原则:任何加密操作都不放在主线程。 不管是 AES 还是哈希,只要数据量超过 1KB,一律走 Dispatchers.IO。Compose 的协程配合 withContext 写起来很顺手,别偷懒。

常见加密操作性能与 ANR 风险对比 对称加密 AES-256 / ChaCha20 速度快 · 单一密钥 · 需密钥分发 性能高 · ANR 风险低(大数据量注意) 非对称加密 RSA-2048 / ECC 速度慢 · 密钥对 · 安全等级高 性能低 · ANR 风险高 · 必须异步 哈希算法 SHA-256 / SHA-3 不可逆 · 定长输出 · 抗碰撞 性能中 · ANR 风险中(大文件需注意) 核心建议 所有加密操作(包括哈希)都应避免在主线程执行,推荐使用 Dispatchers.IO + withContext

核心结论:

  • 对称加密(AES)是性能基准,适合大数据量加密,但密钥分发需额外保护。
  • 非对称加密(RSA/ECC)最安全,但性能开销大,是 ANR 重灾区——必须异步。
  • 哈希(SHA-256)不可逆,适合校验和存储,大文件时注意分块处理。

高频踩坑点:

  • 把 RSA 加密直接写在 LaunchedEffect 里——它默认跑在 Dispatchers.Main。
  • 对超大文件做哈希时不分块,导致堆内存暴涨、GC 频繁引发卡顿。
  • 对称加密用了 ECB 模式或固定 IV——安全漏洞直接拉满。

我个人推荐的最佳实践:

在 Compose 中封装一个 rememberCryptoScope(),内部绑定 Dispatchers.IO,所有加密操作统一通过它执行。这样既不会忘切线程,测试时也容易 mock。