如何确保密码散列在计算机上是安全的,同时在移动设备上又不会过慢?

信息安全 密码 哈希 安卓 bcrypt
2021-09-02 02:49:36

我是密码学及其实现的新手。我正在设计一个 Android 应用程序,用户输入密码来检索一些加密数据。在对可能的解决方案进行了一些研究之后,我最终使用了 bcrypt,如下所示:

用户输入密码 -> bcrypt(Password) -> 加密密钥

然后将其存储在数据库中,如下所示:

SHA256(bcrypt(password)) -> 用户检查(存储在数据库中)

我计划在 Android 中使用 jBCrypt 实现,但据我所知,bcrypt 在手机上的速度非常慢(超过几分钟,具体取决于所选手机的型号/强度。)

我知道减慢哈希过程是 bcrypt 算法的核心,但用户在移动设备上需要 2 分钟登录/解密,而在普通 PC 上执行相同操作需要几秒钟,这是不可接受的。但是,如果我降低智能手机上的迭代次数,攻击者可以在更快的 PC 上暴力破解它。我怎样才能调和问题的这两个方面?


编辑

我最终决定在 Android 上尝试 jBCrypt,并且我已经在我拥有的几款具有不同 SO 版本的手机上对其进行了测试。我还在家用 PC 上对其进行了测试,并编制了一张结果表:

Android 与 PC 上的 jBCrypt 结果

看起来 10-11 的工作系数是可以接受的,同时仍然相当安全。我将尝试实现一个 NDK 解决方案,看看我是否可以改进结果。(如果我最终进行测试,我会发布结果。)

4个回答

jBcrypt 是一个 Java 实现。Android 应用程序几乎是用 Java 编写的,用于一个名为Dalvik的几乎是 Java 的虚拟机

对于计算密集型任务,尤其是加密算法,与同等优化的 C 代码相比,Java 的速度通常会降低 3 倍:好的 Java 虚拟机运行JIT 编译器,代码的性能将受到 JVM 必须运行的约束的限制in (从字节码到本机代码的转换必须快速且增量,方法在每次调用的基础上进行转换;并且结果必须仍然适合垃圾收集器)。在本机代码可以从 CPU 提供的额外操作码中受益的某些特定情况下,可以观察到进一步的减速,典型情况是对大整数的计算(例如,对于 RSA),因为 CPU 可能提供“扩展”乘法(64x64- >128) Java 代码无法使用,因为 Java 中缺少 128 位整数类型。

有关散列函数上下文中相当彻底的实验(不是“密码散列”,只是“散列”),请参阅sphlib :一个在 CJava中实现大量散列函数的库,“在优化方面做出了类似的努力”(整个批次的相同开发人员),并包括针对各种平台类型的措施。

现在,Android 应用程序可能会出现额外的减速:

  • 智能手机的 CPU 速度不如 PC 快。智能手机 CPU 可以达到千兆赫兹范围,但每个周期的工作量仍比 x86 CPU 少——因为智能手机 CPU 的主要目标之一是节省电池。智能手机必须在进行无线电活动的同时使用一整天,而笔记本电脑的电池容量更大,只需启动几个小时。现代智能手机效率很高,但与台式电脑相比仍有差距。假设一个额外的 3 倍因子。

  • Dalvik 不一定是最好的 JIT 编译器。相对缺乏原始肌肉意味着 JIT 无法应用最复杂的优化策略,因为它会使方法的 JIT 转换成本过高。

  • 直到 Android 2.1(包括在内),Dalvik 没有 JIT是其他答案所暗示的最严重的放缓。在 Android 2.1(及之前)中,Java 字节码是被解释的,而不是被翻译成本机代码,这会产生巨大的额外成本(比 JIT 代码慢 10 到 20 倍)。Android 2.2 及更高版本具有 JIT。

切换到 PBKDF2 不会改变太多。如果您使用PBKDF2,请务必使用 SHA-256(或 SHA-1),而不是 SHA-512,因为智能手机 CPU 是基于 ARM 的,并且对于 SHA-512 严重依赖的 64 位算术运算会非常不舒服,从而减慢您的代码速度——而攻击者有一台 PC,早餐吃 64 位操作,并且不会导致同样的减慢。

总结: bcrypt 在 Android 2.1(和之前的版本)上会很慢。它在 Android 2.2 上会有点快,尽管不如 PC 快(比如慢 5 到 10 倍)。PBKDF2, scrypt...不要以显着方式改变这张图片。


所有这些都是关于 bcrypt 的性能的。我没有以任何方式评论正确使用 bcrypt 派生的秘密进行加密密码验证。实际上,当您存储“某物”来验证密码时,您不会因为该“某物”太接近用于加密的密码派生密钥。

一种“密码学上合理的方法”是使用 bcrypt 或 PBKDF2(使用盐和迭代等)计算密码哈希,然后获取输出并使用密钥派生函数将其扩展为足够的密钥材料以满足您的需要。在您的上下文中,获取 bcrypt 输出,然后使用 SHA-256 对其进行哈希处理。这产生 256 位。将数据库中的前 128 位存储为“密码验证令牌”。另一半用作加密密钥。

PBKDF2 使这个过程稍微简单一些,因为它是一个 KDF,所以你可以立即请求 256 位的密码派生输出,而不需要额外的散列;而 bcrypt 的输出大小固定为 192 位,这可能不足以满足您的需求。

我也遇到过同样的情况。在 Android 上,您应该能够PBKDF2舒适地使用。我还没有在移动设备上测试过 scrypt,但我希望在生成哈希时性能不会比 scrypt 好。除此之外,SHA-256您可以轻松地使用a 而不是使用PBKDF2a SHA-256 HMAC请记住,PBKDF2 还允许您设置哈希的输出长度(最好在 PBKDF2 的输出上使用 HKDF-Extract 代替)。所以通常服用 SHA-256(pbkdf2(pass))会太多。

但是,我建议您使用另一个PBKDF2(PKBDF2(pass))并将其存储在您的应用程序中。这样,您可以检查,当用户输入密码并且您使用 PBKDF2 生成密钥(内部PBKDF2)时,该密钥确实是解密之前的正确密钥。

所以:

  • password => 用于生成密钥(秘密,不应存储)
  • PBKDF2(password) => 用作解密加密数据的密钥(秘密,不应存储)
  • PBKDF2(PBKDF2(password)) => 用于在尝试解密之前验证密钥(不是秘密,可以存储在您的应用程序中)

在跨多个平台配置像 KeePass 这样的密码管理器时,也是同样的困境——你关注哪个平台?

因为bcrypt是一个密钥派生函数,用于扩展通过可预测的人类密码的其他快速过程;如果你回避人工密码的问题,你就回避了性能问题。

有权访问由会话密钥管理的数据。当会话密钥需要重新生成(例如,每 24 小时或手机重启后)bcrypt(password)bcrypt(password + master key)使用时。在会话期间,会话密钥仅通过非 bcrypt 密码加密来保护速度。

或者,您可以保留bcrypt()相关延迟,并简单地改善流程的用户界面体验。

如果解密进程作为 CPU 上限的后台任务运行,前台通知用进度条和百分比标记解密状态;如果应用程序的核心目的是安全,大多数用户会发现这是可以接受的。事实上,它甚至可能让他们觉得应用程序比超快速解密更安全。

当然,如果应用程序的核心目的与安全无关,如果它确实需要安全增强bcrypt()和相关的移动设备延迟,您可能需要重新考虑。

正如您所承认的,您有相互矛盾的要求。您希望 bcrypt 的成本尽可能高,以帮助防御离线破解者,同时您希望它对于使用相对低功率设备的合法用户来说成本不会过高。

有两种方法,你都应该追求。调整您的成本因素(或迭代),并找到一个散列实现,让您获得最佳(最小)攻击者/防御者比率。

降低攻击者/防御者比率。

密码破解者 Bitweisel谈到了他所谓的“攻击者/防御者比率”(ADR)。当防御者以攻击者可用速度的 1/10 的方式运行 bcrypt、scrypt 或 PBKDF2 时,ADR 为 10 比 1。在您的情况下,防御者不仅在一个不太强大的设备,也可能会被在这些系统上表现不佳的编译器卡住。

我对 Android 的了解不足以为您指明正确的方向,但您应该尝试找到针对其操作环境进行了仔细优化的东西。是 bcrypt 还是 PBKDF2,我不知道。同样,您(还)不关心绝对速度,而是关心您的环境与攻击者之间的比率。

例如,使用 HMAC-SHA512 的 PBKDF2 可能对您来说非常慢,但如果使用 SHA512 排除了攻击者使用 GPU 进行破解的能力,这可能是值得的。因此,即使速度很慢,这实际上可能会让您失去 ADR。

安装一个ocl-hashcat+(外面的禁食破解器)的副本来测试您的台式计算机上的破解率,这样您就可以根据等式的那一侧来判断不同哈希方案的 ADR。

调整您的成本因素

PBKDF2、bcrypt 和 scrypt 都允许您设置工作因子。声明“bcrypt 需要两分钟”可以通过将成本参数减少一(使其花费一分钟)或二(30 秒)来修复。使用 PBKDF2,您可以设置线性缩放的迭代。

因此,一旦您找到了获得最佳 ADR 的算法和实现,那么只需决定您的用户可以容忍的时间。如果是 2 秒,那么调整成本因素或迭代以获得两分钟。