你的 Android Keystore 认证有多安全?(上)
2020-04-17 10:30:51 Author: www.4hou.com(查看原文) 阅读量:388 收藏

引言

拥有特权的恶意软件或者可以实际访问安卓设备的攻击者是一个难以防范的攻击向量。 在这种情况下,你的应用程序如何保持安全性?

本文将讨论 Android keystore 机制以及在尝试实现安全本地身份验证时遇到的困难。 通过提供对 AndroidKeystore 的介绍,你将能够理解与密钥存储库相关的常见漏洞。 本文的核心将重点介绍可用于审计应用程序的本地身份验证的开发工具。 最后将提供关于安全实现的一般指导,并介绍一个可作为参考的应用程序。

Keystore 简介

Android Keystore 是一个允许开发人员在容器中创建和存储密钥的系统,这使得从设备中提取密钥变得更加困难。 这些密钥存储在专门的硬件中,即所谓的可信执行环境。 密钥可以在其内部生成,甚至操作系统本身也不应该直接访问这个安全内存。 Android Keystore 提供 API 来在这个受信任的环境中执行加密操作并接收结果。 它是在 API 18(Android 4.3)中引入的。 一个支持保险箱的 Android Keystore 是目前最安全和推荐的密钥存储类型。

利用 Android 密钥库系统,你可以在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入密钥库后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。

keyStore有什么作用?

· 程序升级
新旧版本的数字证书相同时 Android系统才会认为这是同一程序的不同版本 如果数字证书不同 会 产生冲突 要求更改包名

· 程序的模块化和开发
拥有同一签名的程序可以运行在同一进程中 可以分模块开发 用户在需要的时候下载对应的模块

· 多个程序间共享数据和代码
Android 提供了基于数字证书的权限赋予机制 如果某个权限(permission)的protectionLevel是signature 则这个权限就只能授予那些跟该权限所在的包拥有同一个数字证书的程序。

密钥库系统并不是让程序直接进行存储程序的私密信息的,比如说用户账号密码,其提供了一个密钥安全容器,保护密钥材料免遭未经授权的使用,一个应用程序可以在密钥库中存储多个密钥并且只允许应用自身访问,应用程序可以在密钥库系统中生成,存储,获取存储其中的公钥或者私钥,因此可使用密钥库系统中的密钥来进行数据的加密。

密钥库系统由 KeyChain API 以及在 Android 4.3(API 级别 18)中引入的 Android 密钥库提供程序功能使用。

Android Keystore 支持7种不同类型的密钥存储机制,每种机制各有优缺点。 例如,Android Keystore 使用硬件芯片以安全的方式存储密钥,而 Bouncy Castle Keystore (BKS)是一个软件密钥存储库,并使用放置在文件系统上的加密文件。 Android 文档提供了许多对开发人员有用的代码示例,但是在描述其 keystore 机制时有些令人困惑和费解。 这导致了很多开发人员头疼的问题。 在许多与 keystore 相关的类中,Android 文档通常直接取自 Java 文档。 在没有找到一个简洁明了的解释和一个开发安全本地身份验证的解决方案之后,开发人员搜索到了 StackOverflow,直接复制和粘贴代码,这些代码不安全,可以很容易地用一些 Frida 脚本绕过。 支离破碎的 Android 生态系统也使得使用 keystore 成为一种不愉快的体验,因为需要执行多个兼容性检查,并且需要实现各种代码路径以支持各种设备。

让我们看看下面的截图。 左边是 Android 文档,右边是 Java 文档。

image.png

AndroidKeystore 实现不支持解锁密钥存储库或其特定条目的密码。 文档中显示的代码片段将引发异常。 上面的截图显示 Android 文档中的关于 AndroidKeystore 实现的知识来源并不是100% 的可靠。 由于 Android 支持一种不包含在传统 Java 中的新型密钥存储系统,因此上面的例子不适用于 AndroidKeystore。 在发现了更多的问题之后,我们决定更深入地研究一下 Android 中可用的 keystore 系统。 在本文的后续部分,你可以找到用于测试不安全密钥存储库使用情况的方法。

Keystores 到处都是 keystores

一个 Android Keystore 只是一个 Android 开发者可以使用的 Java 类。 Android Keystore 是 Java Keystore API 的另一个实现,其他类型的 Keystore,比如 BSK,也实现了这个 API。

我们可以使用普通的 Java KeyStore API 来访问 Android KeyStore:

KeyStore ks = KeyStore.getInstance("AndroidKeystore");

Keystore 类型

下面的表格列出了普通安卓系统(直到安卓9)支持的密钥库类型:

image.png

更新的列表可以在这篇博文中找到:

https://developer.android.com/reference/java/security/KeyStore

还有其他的密钥存储类型,例如三星支持它自己的名为 TIMA 的密钥存储类型。

如何使用 Android Keystore?

Java 密钥库 API 包含一个 java.security。 带有插入密钥的方法的 KeyStore 类。 但是,大多数应该在密钥库中插入密钥的调用都会引发异常。 这样做是为了防止开发人员插入硬编码的密钥。 Android Keystore 的假设是密钥永远不能离开可信环境,因此开发人员只能使用 android.security.keystore.KeyGenParameterSpec.Builder。 一个实现本地身份验证的示例引用应用程序可以点击这里查看

存储在密钥存储库中的每个密钥都可以设置以下参数:

· alias - 别名,用来识别密钥

· key size - 密钥大小(API 23)

· purpose –加密 / 解密(API 23)

· 加密模式、算法和填充(API 23)

· 密钥在使用之前是否应该通过密钥存储库进行身份验证(API 23)

· 一个成功的身份验证之后密钥可以被使用的持续时间(API 23)

· 新登记的指纹上的钥匙应该作废吗(API 24) ?

· 密钥存储库在执行加密操作之前是否应该要求解锁屏幕? (API 28) 

· 硬碟安全模组? 密钥应该由 StrongBox  硬件安全模块保护吗?(API 28)

支持的 Android API 版本包含在括号中,以显示在不同的 Android 版本中引入了哪些安全特性。 更多的设置和相关信息可以在适当的 Android 文档中找到。

让我们来讨论一下与密钥库相关的常见漏洞:

· 使用中的不安全密钥存储类型: 例如,如果正在使用 BKS 密钥存储,密钥存储在特权用户可以访问的文件中。 AndroidKeystore 支持硬件支持的容器,应该是首选的。

· 在新的指纹登记中没有失效的密钥: 有物理访问权限的攻击者可以登记他们自己的指纹并使用生物识别锁定的密钥。

· 无需屏幕解锁即可访问的 Keystore: 允许在不解锁屏幕的情况下使用密钥这无疑增加了攻击面。

· 使用中的弱加密算法: KeyGenerator.getInstance 方法中使用了弱加密算法。

· 为密钥存储库或密钥存储库条目设置了的弱密码或者硬编码了密码: 只有在可以设置密码访问密钥存储库时,才会出现这种类型的漏洞。 AndroidKeystore 是推荐的密钥存储类型,但如果应用程序设计需要使用软件支持的密钥存储,则建议设置一个强大的用户派生密码。

Frida 审计脚本

为了加快 keystore 审计并使评估更加健壮,我们准备了一些有用的 Frida 脚本。 这些脚本可以在这里找到。 下面的列表描述了它们的功能。

Keystore tracer

通用的 keystore 调试脚本:

· 列出应用程序使用的密钥存储类型

· 列出特定密钥存储库(或应用程序中的任何密钥存储库)使用的密钥存储库条目别名

· 列出所有关于 AndroidKeystore 密钥的信息。当确定是否可以绕过生物特征识别时非常有用。

· 在寻找有趣的密钥时,在逆向工程中有用的其他实用程序。

KeyGenParameterSpec tracer

· 列出有关在运行时使用 KeyGenParameterSpec API生成的密钥的信息。信息包括密钥大小、加密模式、算法等。

· 在审查应用程序中使用的加密时非常有用。

SecretKeyFactory tracer

· HOOK 掉 PBEKeySpec 构造函数和getInstance() 方法,以提取用于创建 PBKDF 密钥的密码、迭代次数、盐值和密钥大小。

· 在审查应用程序中使用的加密技术时非常有用——不仅仅是密钥库

Cipher tracer

· HOOK 掉 Cipher API,列出关于特定密钥的信息,如密钥大小、加密模式、算法等

· 在检查应用程序中使用的加密技术时非常有用——不仅仅是密钥存储

这些脚本可以通过以下命令启动:

$ frida -U -f com.example.keystorecrypto --no-pause -l SCRIPT-PATH.js

下面的代码片段显示了用于示例应用程序的 Keystore 跟踪程序脚本的输出:

$ frida -U -f com.example.keystorecrypto --no-pause -l keystore-tracer.js
...
[Google Pixel::com.example.keystorecrypto]-> KeystoreListAndroidKeystoreAliases()
...
[
    "'SYMMETRIC_MASTER_KEY'",
    "'ASYMMETRIC_MASTER_KEY'"
]
[Google Pixel::com.example.keystorecrypto]-> DumpKeystoreKeyInfo('SYMMETRIC_MASTER_KEY')
...
{
    "blockModes": [
        "GCM"
    ],
    "digests": [],
    "encryptionPaddings": [
        "NoPadding"
    ],
    "isInsideSecureHardware": true,
    "isInvalidatedByBiometricEnrollment": false,
    "isUserAuthenticationRequired": false,
    "isUserAuthenticationRequirementEnforcedBySecureHardware": true,
    "isUserAuthenticationValidWhileOnBody": false,
    "keyAlgorithm": "AES",
    "keySize": 256,
    "keyValidityForConsumptionEnd": null,
    "keyValidityForOriginationEnd": null,
    "keyValidityStart": null,
    "keystoreAlias": "SYMMETRIC_MASTER_KEY",
    "origin": 1,
    "purposes": 3,
    "signaturePaddings": [],
    "userAuthenticationValidityDurationSeconds": -1
}

如上所示,我们用一个函数发现了两个脆弱点:

· 可以添加新的指纹解锁应用程序

· 不需要对密钥使用进行用户身份验证

Cipher 跟踪程序可以成为检查加密操作非常有用的脚本。 该脚本转储有关应用程序使用的加密算法、模式和填充的信息。 此外,它 HOOK 了 doFinal 方法,并出现在加密 / 解密之前和之后可以理解为数据的操作输入和输出。 Cipher 跟踪输出的下列片段以可读的格式显示所描述的功能:

[Cipher.getInstance()]: type: AES/GCM/NoPadding
[Cipher.getInstance()]:  cipherObj: javax.crypto.Cipher@875ca09
[Cipher.init()]: mode: Decrypt mode, secretKey: android.security.keystore.AndroidKeyStoreSecretKey spec:[object Object] , cipherObj: javax.crypto.Cipher@875ca09
[Cipher.init()]: mode: Decrypt mode, secretKey: android.security.keystore.AndroidKeyStoreSecretKey spec:[object Object] secureRandom: java.security.SecureRandom@cc52b6a , cipherObj: javax.crypto.Cipher@875ca09
[Cipher.doFinal2()]:   cipherObj: javax.crypto.Cipher@875ca09
In buffer:
  Offset  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  B1 5A 1E 0F F3 19 AD 80 80 A7 8F 9A E5 F8 4A 1A  .Z............J.
00000010  5E DA C4 F0 D6 E0 0C 7D 56 14 6F 92 CA 4E B2 C0  ^......}V.o..N..
00000020  CD 42 A9 5F 06 05 BA 6B 9D 36 3A 73 61 87 34 C7  .B._...k.6:sa.4.
00000030  F8 BB 0C 2D 21 8A 80 2E FB 0B 41 EB 63 7B B4 12  ...-!.....A.c{..
00000040  BE A6 48 19 D2 C3 C7 97 9E 93 5E 6B 57 07 15 A0  ..H.......^kW...
00000050  A3 4F A6 07 C7 27 10 2B D0 81 3E 17 F6 C3 69 7D  .O...'.+..>...i}
00000060  25 F7 B2 0D 25 8D 72 6B 56 5B 95 4C FB CD 5F 69  %...%.rkV[.L.._i
00000070  74 A8 5E 91 29 0C 3D E5                          t.^.).=.        
Result:
  Offset  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000  7B 22 63 6C 61 73 73 69 63 43 69 70 68 65 72 4B  {"classicCipherK
00000010  65 79 22 3A 6E 75 6C 6C 2C 22 63 6C 61 73 73 69  ey":null,"classi
00000020  63 4D 61 63 4B 65 79 22 3A 6E 75 6C 6C 2C 22 6D  cMacKey":null,"m
00000030  6F 64 65 72 6E 4B 65 79 22 3A 22 38 77 30 32 5A  odernKey":"8w02Z
00000040  61 47 4B 35 46 30 6D 64 46 38 66 76 61 5A 6C 4A  aGK5F0mdF8fvaZlJ
00000050  38 5A 50 74 54 37 74 42 74 72 37 66 45 58 4F 63  8ZPtT7tBtr7fEXOc
00000060  78 4E 6D 56 64 55 22 7D                          xNmVdU"}

安卓 Keystore 和应用程序 HOOK

所以我们有一个 Android Keystore,它被认为是安全的,因为我们不能访问密钥。 但是,攻击者实际上可能并不需要密钥内容。 Keystore API 可用于检索密钥引用,然后可以使用它们初始化 Cipher 对象,然后可以使用它们对应用程序存储进行解密或加密。

是的,这是可能的,而且大多数应用程序都很容易受到这类攻击,具有设备实际访问权限或拥有特权的恶意软件的攻击者可以做以下事情:

· 启动受害者的应用程序

· HOOK 受害者的应用程序并使用 Frida 在受害者应用程序的上下文中执行代码,可以做到如下事情:

· 使用Keystore API检索对AndroidKeystore密钥的引用。

· 使用检索到的密钥引用初始化密码对象。

· 在应用程序存储中解密/加密/签名数据。

啊! 使用 Android Keystore 并不能保证二进制安全性。 为了防止这种攻击,开发人员必须将密钥库密钥标记为只有在以下情况下才能访问:

· 设备已经解锁

· 指纹或其他生物识别技术已被验证

对于此配置,开发人员必须在生成密钥期间将 setUserAuthenticationRequired() 设置为 true。 另一个重要属性是 setUserAuthenticationValidityDurationSeconds()。 如果设置为 -1,那么只有使用指纹或生物识别技术才能解锁密钥。 如果它被设置为任何其他值,也可以使用设备屏幕锁解锁密钥。

在设备屏幕锁定的情况下,首先通过调用  KeyguardManager.createConfirmDeviceCredentialIntent()来访问密钥。

需要注意的是,KeyguardManager API 不允许开发人员检查配置了哪种类型的屏幕锁,也不允许验证密码或PIN码或手势图案模式策略。 因此,这个设备可能有一个不安全的屏幕锁:

· 简单的图案(在大多数设备上是3x3,可以通过尝试常见的图案或检查屏幕上的指纹来猜测)

· 简单的PIN码(通常是4-5个数字,常见的PIN码或特别简单的如0000或1234这样的连续PIN码)

· 可猜测的密码(你的狗的名字)

因此,建议对于银行应用程序等高度敏感的应用程序,密码管理器或安全信使的 setUserAuthenticationValidityDurationSeconds() 不应该有除 -1以外的任何值。

此脚本(https://github.com/mwrlabs/android-keystore-audit/blob/master/frida-scripts/keyguard-credential-intent.js)可用于使用 KeyguardManager 触发“设备解锁”状态,并解锁未将有效期设置为 -1的密钥。

本文翻译自:https://labs.f-secure.com/blog/how-secure-is-your-android-keystore-authentication/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/L5LA
如有侵权请联系:admin#unsafe.sh