跳过内容

iOS 加密 API

概述

“移动应用密码学” 章节中,我们介绍了通用的密码学最佳实践,并描述了不正确使用密码学时可能出现的典型问题。在本章中,我们将更详细地介绍 iOS 的密码学 API。我们将展示如何在源代码中识别这些 API 的用法,以及如何解释加密配置。在审查代码时,请务必将使用的加密参数与本指南中链接的当前最佳实践进行比较。

Apple 提供了包含大多数常见加密算法实现的库。Apple 的加密服务指南 是一个很好的参考。它包含关于如何使用标准库初始化和使用加密原语的通用文档,这些信息对源代码分析很有用。

CryptoKit

Apple CryptoKit 随 iOS 13 发布,并且构建在 Apple 原生加密库 corecrypto 之上,该库已经 通过 FIPS 140-2 验证。Swift 框架提供了一个强类型 API 接口,具有有效的内存管理,符合 equatable,并支持泛型。CryptoKit 包含用于哈希、对称密钥加密和公钥加密的安全算法。该框架还可以利用安全 Enclave 中的基于硬件的密钥管理器。

Apple CryptoKit 包含以下算法

哈希

  • MD5(不安全模块)
  • SHA1(不安全模块)
  • SHA-2 256 位摘要
  • SHA-2 384 位摘要
  • SHA-2 512 位摘要

对称密钥

  • 消息认证码 (HMAC)
  • 认证加密
    • AES-GCM
    • ChaCha20-Poly1305

公钥

  • 密钥协商
    • Curve25519
    • NIST P-256
    • NIST P-384
    • NIST P-512

示例

生成和释放对称密钥

let encryptionKey = SymmetricKey(size: .bits256)

计算 SHA-2 512 位摘要

let rawString = "OWASP MTSG"
let rawData = Data(rawString.utf8)
let hash = SHA512.hash(data: rawData) // Compute the digest
let textHash = String(describing: hash)
print(textHash) // Print hash text

有关 Apple CryptoKit 的更多信息,请访问以下资源

CommonCrypto、SecKey 和包装器库

用于加密操作的最常用类是 CommonCrypto,它与 iOS 运行时打包在一起。最好通过查看 头文件的源代码 来剖析 CommonCrypto 对象提供的功能。

  • Commoncryptor.h 给出了对称加密操作的参数。
  • CommonDigest.h 给出了哈希算法的参数。
  • CommonHMAC.h 给出了支持的 HMAC 操作的参数。
  • CommonKeyDerivation.h 给出了支持的 KDF 函数的参数。
  • CommonSymmetricKeywrap.h 给出了用于使用密钥加密密钥包装对称密钥的函数。

不幸的是,CommonCryptor 的公共 API 缺少一些类型的操作,例如:GCM 模式仅在其私有 API 中可用。请参阅 它的源代码。为此,需要一个额外的绑定头,或者可以使用其他包装器库。

接下来,对于非对称操作,Apple 提供了 SecKey。Apple 在其 开发者文档 中提供了关于如何使用它的一个很好的指南。

如前所述:为了提供便利,存在一些包装器库。常用的典型库例如有

第三方库

有各种第三方库可用,例如

  • CJOSE:随着 JWE 的兴起以及对 AES GCM 缺乏公开支持,其他库找到了自己的出路,例如 CJOSE。CJOSE 仍然需要更高级别的包装,因为它们只提供 C/C++ 实现。
  • CryptoSwift:一个 Swift 库,可以在 GitHub 上找到。该库支持各种哈希函数、MAC 函数、CRC 函数、对称密码和基于密码的密钥派生函数。它不是包装器,而是每个密码的完全自我实现的版本。验证函数的有效实现非常重要。
  • OpenSSLOpenSSL 是用于 TLS 的工具包库,用 C 语言编写。它的大多数加密函数都可用于执行必要的各种加密操作,例如创建 (H)MAC、签名、对称和非对称密码、哈希等。有各种包装器,例如 OpenSSLMIHCrypto
  • LibSodium:Sodium 是一个现代、易于使用的软件库,用于加密、解密、签名、密码哈希等。它是 NaCl 的一个可移植、可交叉编译、可安装、可打包的分支,具有兼容的 API 和扩展的 API,以进一步提高可用性。有关更多详细信息,请参阅 LibSodiums 文档。有一些包装器库,例如 Swift-sodiumNAChloridelibsodium-ios
  • Tink:Google 的一个新的密码库。Google 在 其安全博客上 解释了该库背后的原因。可以在 Tinks GitHub 仓库 中找到源代码。
  • Themis:一个用于 Swift、Obj-C、Android/Java、С++、JS、Python、Ruby、PHP、Go 的存储和消息传递的加密库。Themis 使用 LibreSSL/OpenSSL 引擎 libcrypto 作为依赖项。它支持 Objective-C 和 Swift,用于密钥生成、安全消息传递(例如,有效负载加密和签名)、安全存储和建立安全会话。有关更多详细信息,请参阅 他们的 wiki
  • 其他:还有许多其他库,例如 CocoaSecurityObjective-C-RSAaerogear-ios-crypto。其中一些不再维护,可能从未经过安全审查。与往常一样,建议寻找受支持和维护的库。
  • DIY:越来越多的开发人员创建了他们自己实现的密码或加密函数。强烈 不鼓励这种做法,如果使用,应由密码学专家进行非常彻底的审查。

密钥管理

有多种方法可以在设备上存储密钥。根本不存储密钥将确保不会转储任何密钥材料。这可以通过使用密码密钥派生函数来实现,例如 PKBDF-2。见下面的例子

func pbkdf2SHA1(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2SHA256(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2SHA512(password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    return pbkdf2(hash: CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512), password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

func pbkdf2(hash: CCPBKDFAlgorithm, password: String, salt: Data, keyByteCount: Int, rounds: Int) -> Data? {
    let passwordData = password.data(using: String.Encoding.utf8)!
    var derivedKeyData = Data(repeating: 0, count: keyByteCount)
    let derivedKeyDataLength = derivedKeyData.count
    let derivationStatus = derivedKeyData.withUnsafeMutableBytes { derivedKeyBytes in
        salt.withUnsafeBytes { saltBytes in

            CCKeyDerivationPBKDF(
                CCPBKDFAlgorithm(kCCPBKDF2),
                password, passwordData.count,
                saltBytes, salt.count,
                hash,
                UInt32(rounds),
                derivedKeyBytes, derivedKeyDataLength
            )
        }
    }
    if derivationStatus != 0 {
        // Error
        return nil
    }

    return derivedKeyData
}

func testKeyDerivation() {
    let password = "password"
    let salt = Data([0x73, 0x61, 0x6C, 0x74, 0x44, 0x61, 0x74, 0x61])
    let keyByteCount = 16
    let rounds = 100_000

    let derivedKey = pbkdf2SHA1(password: password, salt: salt, keyByteCount: keyByteCount, rounds: rounds)
}

当需要存储密钥时,建议使用 Keychain,只要选择的保护类不是 kSecAttrAccessibleAlways。将密钥存储在任何其他位置,例如 NSUserDefaults、属性列表文件或来自 Core Data 或 Realm 的任何其他接收器,通常比使用 KeyChain 不安全。即使 Core Data 或 Realm 的同步受到使用 NSFileProtectionComplete 数据保护类的保护,我们仍然建议使用 KeyChain。有关更多详细信息,请参阅 “iOS 上的数据存储” 章节。

KeyChain 支持两种类型的存储机制:密钥要么由存储在安全 Enclave 中的加密密钥保护,要么密钥本身位于安全 Enclave 中。后者仅在使用 ECDH 签名密钥时成立。有关其实现的更多详细信息,请参阅 Apple 文档

最后三个选项包括在源代码中使用硬编码的加密密钥、基于稳定属性的具有可预测密钥派生函数以及将生成的密钥存储在与其他应用程序共享的位置。使用硬编码的加密密钥显然不是一个好方法,因为这意味着应用程序的每个实例都使用相同的加密密钥。攻击者只需要做一次工作就可以从源代码中提取密钥(无论是本地存储还是在 Objective-C/Swift 中存储)。因此,攻击者可以解密应用程序加密的任何其他数据。接下来,当您有一个基于其他应用程序可以访问的标识符的可预测的密钥派生函数时,攻击者只需要找到 KDF 并将其应用于设备即可找到密钥。最后,公开存储对称加密密钥也是非常不鼓励的。

当涉及密码学时,还有两个概念你应该永远不会忘记

  1. 始终使用公钥加密/验证,并始终使用私钥解密/签名。
  2. 切勿将密钥(对)用于其他目的:这可能会泄漏有关密钥的信息:为签名设置单独的密钥对,并为加密设置单独的密钥(对)。

随机数生成器

Apple 提供了 随机化服务 API,该 API 生成密码学上安全的随机数。

随机化服务 API 使用 SecRandomCopyBytes 函数生成数字。这是 /dev/random 设备文件的包装函数,它提供来自 0 到 255 的密码学上安全的伪随机值。确保所有随机数都是使用此 API 生成的。开发人员没有理由使用其他 API。