EllipticCurveKeyPair 2.0

EllipticCurveKeyPair 2.0

测试已测试
语言语言 SwiftSwift
许可 NOASSERTION
发布最新发布2020 年 10 月
SPM支持 SPM

Håvard Fossli 维护。



  • Håvard Fossli 和 Marcin Krzyżanowski 建立

椭圆曲线密钥对

在 iOS 和 MacOS 上使用 Secure Enclave 进行签名、验证、加密和解密。

macOS 10.12.1 support iOS 9 support Swift 3 and 4 support Carthage compatible Cocoapods compatible

特性

  • 创建私钥和公钥对
  • 在安全区域存储私钥
  • 将公钥存储在密钥链中
  • 每次使用私键时,用户都会被提示使用 FaceID、TouchID、设备密码或应用密码
  • 以正确的 ASN.1 标头/结构导出 X.509 DER 公钥
  • 使用 openssl 在命令行中轻松验证签名

支持 FaceID、TouchID、设备密码和应用密码。

关于

使用安全框架可能有点复杂。这就是我创建这个的原因。您可以将其用作示例代码和指导,或者您也可以将其用作微框架。

我发现弄清楚如何在 Swift 3 中使用 C API SecKeyRawVerifySecKeyGeneratePairSecItemCopyMatching 相当棘手,但得益于 Swift 的出色功能,实现相当直接。

安装

手册

只需将文件 Sources/EllipticCurveKeyPair.swiftSources/SHA256.swift 拖入您的 Xcode 项目中。

使用 Cocoapods

pod EllipticCurveKeyPair

使用 Carthage

github "agens-no/EllipticCurveKeyPair"

使用案例

Secure Enclave 有很多强大的可能性。以下是一些示例

加密

  1. 使用公钥加密一条消息
  2. 使用私钥解密消息 – 仅可通过 FaceID / TouchID / 设备密码访问

仅在 iOS 10 及以上版本中可用

签名

  1. 使用私钥对服务器接收到的数据进行签名 - 仅可使用 FaceID / TouchID / 设备密码访问
  2. 使用公钥验证签名的有效性

一个用例可能是

  1. 用户请求新的协议/购买
  2. 服务器发送一个带有会话令牌的推送通知,该令牌应该被签名
  3. 在设备上,我们使用私钥对会话令牌进行签名,同时提示用户使用 TouchID 进行确认
  4. 然后将签名的令牌发送到服务器
  5. 服务器已经拥有公钥,并使用公钥验证签名
  6. 服务器现在相信用户使用 TouchID 对此协议进行了签名

示例

更多信息请查看演示应用程序。

创建密钥对管理器

struct KeyPair {
    static let manager: EllipticCurveKeyPair.Manager = {
        let publicAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAlwaysThisDeviceOnly, flags: [])
        let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: [.userPresence, .privateKeyUsage])
        let config = EllipticCurveKeyPair.Config(
            publicLabel: "payment.sign.public",
            privateLabel: "payment.sign.private",
            operationPrompt: "Confirm payment",
            publicKeyAccessControl: publicAccessControl,
            privateKeyAccessControl: privateAccessControl,
            token: .secureEnclave)
        return EllipticCurveKeyPair.Manager(config: config)
    }()
}

您也可以通过使用不同的访问控制标志,在不支持 Secure Enclave 的情况下优雅地降级使用密钥链

let privateAccessControl = EllipticCurveKeyPair.AccessControl(protection: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, flags: {
    return EllipticCurveKeyPair.Device.hasSecureEnclave ? [.userPresence, .privateKeyUsage] : [.userPresence]
}())

在这种情况下,您需要记得在 Config 对象中将 token 变量设置为 .secureEnclaveIfAvailable

以 DER 格式获取公钥

do {
    let key = try KeyPair.manager.publicKey().data().DER // Data
} catch {
    // handle error
}

请查看演示应用程序以获取示例

以 PEM 格式获取公钥

do {
    let key = try KeyPair.manager.publicKey().data().PEM // String
} catch {
    // handle error
}

签名

do {
    let digest = "some text to sign".data(using: .utf8)!
    let signature = try KeyPair.manager.sign(digest, hash: .sha256)
} catch {
    // handle error
}

签名时也可以传递一个 LAContext 对象

加密

do {
    let digest = "some text to encrypt".data(using: .utf8)!
    let encrypted = try KeyPair.manager.encrypt(digest, hash: .sha256)
} catch {
    // handle error
}

在不同的设备/操作系统/平台上加密

您还可以使用公钥在不同的设备/操作系统/平台上进行加密。如果您想了解如何以Secure Enclave可理解的方式来加密的所有细节,那么您绝对需要阅读由 @dschuetz 撰写的这篇文章!

https://darthnull.org/security/2018/05/31/secure-enclave-ecies/

解密

do {
    let encrypted = ...
    let decrypted = try KeyPair.manager.decrypt(encrypted, hash: .sha256)
    let decryptedString = String(data: decrypted, encoding: .utf8)
} catch {
    // handle error
}

解密时也可以传递一个 LAContext 对象

错误处理

最常见的是捕获与以下相关的错误

  • Secure Enclave 不可用
  • 用户取消指纹对话框
  • 未注册指纹

使用 do/catch

do {
    let decrypted = try KeyPair.manager.decrypt(encrypted)
} catch EllipticCurveKeyPair.Error.underlying(_, let underlying) where underlying.code == errSecUnimplemented {
    print("Unsupported device")
} catch EllipticCurveKeyPair.Error.authentication(let authenticationError) where authenticationError.code == .userCancel {
    print("User cancelled/dismissed authentication dialog")
} catch {
    print("Some other error occurred. Error \(error)")
}

使用 if let

if case let EllipticCurveKeyPair.Error.underlying(_, underlying) = error, underlying.code == errSecUnimplemented {
    print("Unsupported device")
} else if case let EllipticCurveKeyPair.Error.authentication(authenticationError), authenticationError.code == .userCancel {
  print("User cancelled/dismissed authentication dialog")
} else {
  print("Some other error occurred. Error \(error)")
}

调试

为了检查与Keychain往来的查询,您可以像这样将此库在Keychain上所做的每个变更输出到控制台

EllipticCurveKeyPair.logger = { print($0) }

验证签名

在演示应用中,您会看到每次创建签名时都会将一些有用的信息记录到控制台。

示例输出

#! /bin/sh
echo 414243 | xxd -r -p > dataToSign.dat
echo 3046022100842512baa16a3ec9b977d4456923319442342e3fdae54f2456af0b7b8a09786b022100a1b8d762b6cb3d85b16f6b07d06d2815cb0663e067e0b2f9a9c9293bde8953bb | xxd -r -p > signature.dat
cat > key.pem <<EOF
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdDONNkwaP8OhqFTmjLxVcByyPa19
ifY2IVDinFei3SvCBv8fgY8AU+Fm5oODksseV0sd4Zy/biSf6AMr0HqHcw==
-----END PUBLIC KEY-----
EOF
/usr/local/opt/openssl/bin/openssl dgst -sha256 -verify key.pem -signature signature.dat dataToSign.dat

为了运行此脚本,您可以

  1. 将其粘贴到一个文件:verify.sh
  2. 使文件可执行:chmod u+x verify.sh
  3. 运行它:./verify.sh

然后您应该会看到

Verified OK

PS: 此脚本将在您的当前目录中创建4个文件。

关键词

安全框架,Swift 3, Swift 4, Swift, SecKeyRawVerify, SecKeyGeneratePair, SecItemCopyMatching, secp256r1, 椭圆曲线密码学,ECDSA,ECDH,ASN.1,Apple,iOS,Mac OS,kSecAttrKeyTypeECSECPrimeRandom,kSecAttrKeyTypeEC,kSecAttrTokenIDSecureEnclave,LAContext,本地认证,FaceID,面容识别,TouchID,指纹识别,应用密码,设备PIN,设备PIN

感谢和致谢

TrailOfBits

TrailOfBits 在之前发布了一些 Objective-C 代码,这对我们非常有帮助!感谢分享 使用 Secure Enclave 密码 API 的指南和 SecureEnclaveCrypto。他们还有一些其他非常有趣的项目。去检查一下吧!

Quinn “the Eskimo!”,Apple

他分享了有关如何正确导出公开密钥为 DER X.509 格式的非常有价值的见解。

SHA256

SHA256 类(原本的代码为 SHA2.swift)位于 Marcin Krzyżanowski 编写的极具价值的 CryptoSwift 库中。此类已经经过大量修改,以便将其简化到本项目所需的最小程度。

常见问题解答(FAQ)

为什么在我的模拟器上没有得到触摸 ID / 设备 PIN 提示?

模拟器没有任何安全区域,因此尝试访问它只会导致错误。如果您将 fallbackToKeychainIfSecureEnclaveIsNotAvailable 设置为 true,则私钥将在模拟器的钥匙串中存储,从而使得在模拟器上测试您的应用程序变得容易。

我可以在哪里了解更多?

请查看关于安全性的视频 WWDC 2015,或者点击此处直接跳转到关于安全区域的章节。

为什么它被封装在一个枚举中?

我试图在将您需要的文件拖放到 xcode 和支持依赖管理器(如 carthage 和 cocoapods)之间取得平衡。如果您有更好的想法或不同意这个决定,我很乐意讨论替代方案:)

反馈

我们想要😍听取您对这款库的看法。无论您喜欢与否,如果有什么内容您希望看到改进,请提交问题。您可以在推特上通过@hfossli或其它地方联系我。😀