Platform

Swift support Swift Package Manager compatible CocoaPods Compatible Carthage compatible

CryptoSwift

用 Swift 编写的,与加密相关的函数和帮助程序,适用于 Swift。(#PureSwift)

注意: main 分支遵循当前发布的最新 Swift 版本。如果您需要更早的版本用于旧版本的 Swift,请在您的 Podfile 中指定它的版本,或者使用该版本的分支上的代码。较旧的分支不受支持。查看版本以获取详细信息。


要求 | 特性 | 贡献 | 安装 | Swift 版本 | 使用方法 | 作者 | 许可证 | 更新日志

支持 & 赞助商

该项目的财务可持续性得益于我们的 GitHub 赞助商的持续贡献

高级赞助商

Emerge Tools 是一套革命性的产品,旨在增强移动应用以及构建它们的团队。

www.emergetools.com/

要求

好心情

特性

哈希(摘要)

MD5 | SHA1 | SHA2-224 | SHA2-256 | SHA2-384 | SHA2-512 | SHA3

循环冗余校验 (CRC)

CRC32 | CRC32C | CRC16

密码

AES-128, AES-192, AES-256 | ChaCha20 | XChaCha20 | Rabbit | Blowfish

RSA(公钥加密算法)

加密、签名

消息认证码

Poly1305 | HMAC (MD5, SHA1, SHA256) | CMAC | CBC-MAC

密码工作模式

基于密码的密钥派生函数

数据填充

带关联数据的认证加密 (AEAD)

为什么

为什么? 因为我可以

我如何参与?

您想提供帮助,太好了!请继续 fork 我们的 repo,进行更改并向我们发送 pull request。

贡献

查看 CONTRIBUTING.md 以获取有关如何帮助 CryptoSwift 的更多信息。

安装

强化运行时 (macOS) 和 Xcode

如果应用程序使用启用了强化运行时的 本地运行签名 证书,则二进制 CryptoSwift.xcframework(由 Swift Package Manager 包集成使用)将无法在您的应用程序中正确加载。 可以这样设置 Xcode。要解决此问题,您有两个选择

Xcode 项目

要安装 CryptoSwift,请将其作为子模块添加到您的项目(在顶层项目目录中)

git submodule add https://github.com/krzyzanowskim/CryptoSwift.git

建议启用 Whole-Module Optimization 以获得更好的性能。 非优化构建会导致性能显著降低。

Swift Package Manager

您可以使用 Swift Package Manager 并在 Package.swift 中通过添加以下内容来指定依赖项

.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", from: "1.8.3")

请参阅:Package.swift - 手册

注意:Swift Package Manager 使用调试配置进行调试 Xcode 构建,这可能导致性能显著降低(高达 x10000)。性能特征在 Release 构建中有所不同。为了克服这个问题,请考虑嵌入下面描述的 CryptoSwift.xcframework

CocoaPods

您可以使用 CocoaPods

pod 'CryptoSwift', '~> 1.8.3'

请记住,CocoaPods 将在不使用 Whole-Module Optimization 的情况下构建 CryptoSwift,这可能会影响性能。 您可以在安装后手动更改它,或者使用 cocoapods-wholemodule 插件。

Carthage

您可以使用 Carthage。 在 Cartfile 中指定

github "krzyzanowskim/CryptoSwift"

运行 carthage 以构建框架,并将构建的 CryptoSwift.framework 拖到您的 Xcode 项目中。 按照 构建说明常见问题

XCFramework

XCFrameworks 需要 Xcode 11 或更高版本,它们可以以类似于我们过去集成 .framework 格式的方式进行集成。请使用脚本 scripts/build-framework.sh 来生成二进制 CryptoSwift.xcframework 归档文件,您可以将其用作 Xcode 中的依赖项。

CryptoSwift.xcframework 是一个 Release(优化)二进制文件,可提供最佳可用的 Swift 代码性能。

Screen Shot 2020-10-27 at 00 06 32

嵌入式框架

嵌入式框架需要最低部署目标为 iOS 11.0 或 macOS Sierra (10.13)。 将 CryptoSwift.xcodeproj 文件拖到您的 Xcode 项目中,并将适当的框架作为依赖项添加到您的目标中。 现在选择您的应用程序,然后选择应用程序目标的“常规”选项卡。 找到*嵌入式二进制文件*,然后按“+”,然后选择 CryptoSwift.framework(iOS、macOS、watchOS 或 tvOS)

有时,“嵌入式框架”选项不可用。 在这种情况下,您必须为目标添加新的构建阶段。

iOS, macOS, watchOS, tvOS

在该项目中,您会找到适用于所有平台的 单个方案

Swift 版本支持

使用方法

基础
import CryptoSwift

CryptoSwift 使用字节数组,也就是Array<UInt8>,作为所有操作的基本类型。所有数据都可以转换为字节流。 你会找到接受 StringData 的便捷函数,它们会在内部被转换为字节数组。

数据类型转换

为了方便起见,CryptoSwift 提供了两个函数,可以轻松地将字节数组转换为 Data 或将 Data 转换为字节数组

Data 来自字节

let data = Data([0x01, 0x02, 0x03])

Data 转为 Array<UInt8>

let bytes = data.bytes                     // [1,2,3]

十六进制编码

let bytes = Array<UInt8>(hex: "0x010203")  // [1,2,3]
let hex   = bytes.toHexString()            // "010203"

String 构建字节

let bytes: Array<UInt8> = "cipherkey".bytes  // Array("cipherkey".utf8)

另外... 还可以查看处理 Base64 编码数据的助手函数

"aPf/i9th9iX+vf49eR7PYk2q7S5xmm3jkRLejgzHNJs=".decryptBase64ToString(cipher)
"aPf/i9th9iX+vf49eR7PYk2q7S5xmm3jkRLejgzHNJs=".decryptBase64(cipher)
bytes.toBase64()
计算摘要

哈希数据或字节数组(也就是Array<UInt8>

/* Hash struct usage */
let bytes: Array<UInt8> = [0x01, 0x02, 0x03]
let digest = input.md5()
let digest = Digest.md5(bytes)
let data = Data([0x01, 0x02, 0x03])

let hash = data.md5()
let hash = data.sha1()
let hash = data.sha224()
let hash = data.sha256()
let hash = data.sha384()
let hash = data.sha512()
do {
    var digest = MD5()
    let partial1 = try digest.update(withBytes: [0x31, 0x32])
    let partial2 = try digest.update(withBytes: [0x33])
    let result = try digest.finish()
} catch { }

哈希字符串并打印结果

let hash = "123".md5() // "123".bytes.md5()
计算 CRC
bytes.crc16()
data.crc16()

bytes.crc32()
data.crc32()
消息认证码
// Calculate Message Authentication Code (MAC) for message
let key: Array<UInt8> = [1,2,3,4,5,6,7,8,9,10,...]

try Poly1305(key: key).authenticate(bytes)
try HMAC(key: key, variant: .sha256).authenticate(bytes)
try CMAC(key: key).authenticate(bytes)
基于密码的密钥导出函数
let password: Array<UInt8> = Array("s33krit".utf8)
let salt: Array<UInt8> = Array("nacllcan".utf8)

let key = try PKCS5.PBKDF2(password: password, salt: salt, iterations: 4096, keyLength: 32, variant: .sha256).calculate()
let password: Array<UInt8> = Array("s33krit".utf8)
let salt: Array<UInt8> = Array("nacllcan".utf8)
// Scrypt implementation does not implement work parallelization, so `p` parameter will
// increase the work time even in multicore systems
let key = try Scrypt(password: password, salt: salt, dkLen: 64, N: 16384, r: 8, p: 1).calculate()
基于 HMAC 的密钥导出函数
let password: Array<UInt8> = Array("s33krit".utf8)
let salt: Array<UInt8> = Array("nacllcan".utf8)

let key = try HKDF(password: password, salt: salt, variant: .sha256).calculate()
数据填充

某些内容加密算法假设输入长度是 k 个八位字节的倍数,其中 k 大于 1。对于这些算法,需要对输入进行填充。

Padding.pkcs7.add(to: bytes, blockSize: AES.blockSize)

使用密码

ChaCha20
let encrypted = try ChaCha20(key: key, iv: iv).encrypt(message)
let decrypted = try ChaCha20(key: key, iv: iv).decrypt(encrypted)
Rabbit
let encrypted = try Rabbit(key: key, iv: iv).encrypt(message)
let decrypted = try Rabbit(key: key, iv: iv).decrypt(encrypted)
Blowfish
let encrypted = try Blowfish(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).encrypt(message)
let decrypted = try Blowfish(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(encrypted)
AES

关于填充的说明:可以手动填充数据,但默认情况下 CryptoSwift 使用 PKCS7 填充。 如果需要手动禁用/启用填充,可以通过为 AES 类设置参数来实现。

AES 加密的变体 (AES-128, AES-192, AES-256) 取决于给定的密钥长度

AES-256 示例

let encryptedBytes = try AES(key: [1,2,3,...,32], blockMode: CBC(iv: [1,2,3,...,16]), padding: .pkcs7)

完整示例

let password: [UInt8] = Array("s33krit".utf8)
let salt: [UInt8] = Array("nacllcan".utf8)

/* Generate a key from a `password`. Optional if you already have a key */
let key = try PKCS5.PBKDF2(
    password: password,
    salt: salt,
    iterations: 4096,
    keyLength: 32, /* AES-256 */
    variant: .sha256
).calculate()

/* Generate random IV value. IV is public value. Either need to generate, or get it from elsewhere */
let iv = AES.randomIV(AES.blockSize)

/* AES cryptor instance */
let aes = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7)

/* Encrypt Data */
let inputData = Data()
let encryptedBytes = try aes.encrypt(inputData.bytes)
let encryptedData = Data(encryptedBytes)

/* Decrypt Data */
let decryptedBytes = try aes.decrypt(encryptedData.bytes)
let decryptedData = Data(decryptedBytes)
一次性
do {
    let aes = try AES(key: "keykeykeykeykeyk", iv: "drowssapdrowssap") // aes128
    let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
} catch { }
增量更新

增量操作使用 Cryptor 的实例并一次加密/解密一部分,这样可以为大文件节省内存。

do {
    var encryptor = try AES(key: "keykeykeykeykeyk", iv: "drowssapdrowssap").makeEncryptor()

    var ciphertext = Array<UInt8>()
    // aggregate partial results
    ciphertext += try encryptor.update(withBytes: Array("Nullam quis risus ".utf8))
    ciphertext += try encryptor.update(withBytes: Array("eget urna mollis ".utf8))
    ciphertext += try encryptor.update(withBytes: Array("ornare vel eu leo.".utf8))
    // finish at the end
    ciphertext += try encryptor.finish()

    print(ciphertext.toHexString())
} catch {
    print(error)
}
AES 高级用法
let input: Array<UInt8> = [0,1,2,3,4,5,6,7,8,9]

let key: Array<UInt8> = [0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00]
let iv: Array<UInt8> = // Random bytes of `AES.blockSize` length

do {
    let encrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).encrypt(input)
    let decrypted = try AES(key: key, blockMode: CBC(iv: iv), padding: .pkcs7).decrypt(encrypted)
} catch {
    print(error)
}

没有数据填充的 AES

let input: Array<UInt8> = [0,1,2,3,4,5,6,7,8,9]
let encrypted: Array<UInt8> = try! AES(key: Array("secret0key000000".utf8), blockMode: CBC(iv: Array("0123456789012345".utf8)), padding: .noPadding).encrypt(input)

使用便捷扩展

let plain = Data([0x01, 0x02, 0x03])
let encrypted = try! plain.encrypt(ChaCha20(key: key, iv: iv))
let decrypted = try! encrypted.decrypt(ChaCha20(key: key, iv: iv))
AES-GCM

伽罗瓦/计数器模式 (GCM) 加密的结果是密文和身份验证标签,该标签稍后用于解密。

加密

do {
    // In combined mode, the authentication tag is directly appended to the encrypted message. This is usually what you want.
    let gcm = GCM(iv: iv, mode: .combined)
    let aes = try AES(key: key, blockMode: gcm, padding: .noPadding)
    let encrypted = try aes.encrypt(plaintext)
    let tag = gcm.authenticationTag
} catch {
    // failed
}

解密

do {
    // In combined mode, the authentication tag is appended to the encrypted message. This is usually what you want.
    let gcm = GCM(iv: iv, mode: .combined)
    let aes = try AES(key: key, blockMode: gcm, padding: .noPadding)
    return try aes.decrypt(encrypted)
} catch {
    // failed
}

注意:GCM 实例不应重复使用。因此,您不能使用来自编码的同一个 GCM 实例来执行解码。

AES-CCM

带有密码块链接消息认证码的计数器加密的结果是密文和身份验证标签,该标签稍后用于解密。

do {
    // The authentication tag is appended to the encrypted message.
	let tagLength = 8
	let ccm = CCM(iv: iv, tagLength: tagLength, messageLength: ciphertext.count - tagLength, additionalAuthenticatedData: data)
    let aes = try AES(key: key, blockMode: ccm, padding: .noPadding)
    return try aes.decrypt(encrypted)
} catch {
    // failed
}

有关 CCM 的有效参数,请查看文档或 CCM 规范。

AEAD
let encrypt = try AEADChaCha20Poly1305.encrypt(plaintext, key: key, iv: nonce, authenticationHeader: header)
let decrypt = try AEADChaCha20Poly1305.decrypt(ciphertext, key: key, iv: nonce, authenticationHeader: header, authenticationTag: tagArr: tag)
RSA

从参数初始化 RSA

let input: Array<UInt8> = [0,1,2,3,4,5,6,7,8,9]

let n: Array<UInt8> = // RSA modulus
let e: Array<UInt8> = // RSA public exponent
let d: Array<UInt8> = // RSA private exponent

let rsa = RSA(n: n, e: e, d: d)

do {
    let encrypted = try rsa.encrypt(input)
    let decrypted = try rsa.decrypt(encrypted)
} catch {
    print(error)
}

RSA 密钥生成

let rsa = try RSA(keySize: 2048) // This generates a modulus, public exponent and private exponent with the given size

RSA 加密 & 解密示例

// Alice Generates a Private Key
let alicesPrivateKey = try RSA(keySize: 1024)
    
// Alice shares her **public** key with Bob
let alicesPublicKeyData = try alicesPrivateKey.publicKeyExternalRepresentation()
    
// Bob receives the raw external representation of Alices public key and imports it
let bobsImportOfAlicesPublicKey = try RSA(rawRepresentation: alicesPublicKeyData)
    
// Bob can now encrypt a message for Alice using her public key
let message = "Hi Alice! This is Bob!"
let privateMessage = try bobsImportOfAlicesPublicKey.encrypt(message.bytes)
    
// This results in some encrypted output like this
// URcRwG6LfH63zOQf2w+HIllPri9Rb6hFlXbi/bh03zPl2MIIiSTjbAPqbVFmoF3RmDzFjIarIS7ZpT57a1F+OFOJjx50WYlng7dioKFS/rsuGHYnMn4csjCRF6TAqvRQcRnBueeINRRA8SLaLHX6sZuQkjIE5AoHJwgavmiv8PY=
      
// Bob can now send this encrypted message to Alice without worrying about people being able to read the original contents
    
// Alice receives the encrypted message and uses her private key to decrypt the data and recover the original message
let originalDecryptedMessage = try alicesPrivateKey.decrypt(privateMessage)
    
print(String(data: Data(originalDecryptedMessage), encoding: .utf8))
// "Hi Alice! This is Bob!"

RSA 签名 & 验证示例

// Alice Generates a Private Key
let alicesPrivateKey = try RSA(keySize: 1024)
    
// Alice wants to sign a message that she agrees with
let messageAliceSupports = "Hi my name is Alice!"
let alicesSignature = try alicesPrivateKey.sign(messageAliceSupports.bytes)
    
// Alice shares her Public key and the signature with Bob
let alicesPublicKeyData = try alicesPrivateKey.publicKeyExternalRepresentation()
    
// Bob receives the raw external representation of Alices Public key and imports it!
let bobsImportOfAlicesPublicKey = try RSA(rawRepresentation: alicesPublicKeyData)
        
// Bob can now verify that Alice signed the message using the Private key associated with her shared Public key.
let verifiedSignature = try bobsImportOfAlicesPublicKey.verify(signature: alicesSignature, for: "Hi my name is Alice!".bytes)
    
if verifiedSignature == true {
  // Bob knows that the signature Alice provided is valid for the message and was signed using the Private key associated with Alices shared Public key.
} else {
  // The signature was invalid, so either
  // - the message Alice signed was different then what we expected.
  // - or Alice used a Private key that isn't associated with the shared Public key that Bob has.
}

CryptoSwift RSA Key -> Apple 的 Security Framework SecKey 示例

/// Starting with a CryptoSwift RSA Key
let rsaKey = try RSA(keySize: 1024)

/// Define your Keys attributes
let attributes: [String:Any] = [
  kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
  kSecAttrKeyClass as String: kSecAttrKeyClassPrivate, // or kSecAttrKeyClassPublic
  kSecAttrKeySizeInBits as String: 1024, // The appropriate bits
  kSecAttrIsPermanent as String: false
]
var error:Unmanaged<CFError>? = nil
guard let rsaSecKey = try SecKeyCreateWithData(rsaKey.externalRepresentation() as CFData, attributes as CFDictionary, &error) else {
  /// Error constructing SecKey from raw key data
  return
}

/// You now have an RSA SecKey for use with Apple's Security framework

Apple 的 Security Framework SecKey -> CryptoSwift RSA Key 示例

/// Starting with a SecKey RSA Key
let rsaSecKey:SecKey

/// Copy External Representation
var externalRepError:Unmanaged<CFError>?
guard let cfdata = SecKeyCopyExternalRepresentation(rsaSecKey, &externalRepError) else {
  /// Failed to copy external representation for RSA SecKey
  return
}

/// Instantiate the RSA Key from the raw external representation
let rsaKey = try RSA(rawRepresentation: cfdata as Data)

/// You now have a CryptoSwift RSA Key

作者

CryptoSwift 由 Marcin Krzyżanowski 拥有和维护

您可以在 Twitter 上关注我 @krzyzanowskim,以获取项目更新和版本发布。

密码学声明

此发行版包含密码学软件。您目前居住的国家/地区可能对加密软件的进口、拥有、使用和/或再出口到其他国家/地区有限制。在使用任何加密软件之前,请检查您所在国家/地区的法律、法规和政策,了解是否允许进口、拥有、使用和再出口加密软件。有关更多信息,请参阅 https://www.wassenaar.org/

许可

版权所有 (C) 2014-2022 Marcin Krzyżanowski marcin@krzyzanowskim.com 本软件按“原样”提供,不作任何明示或暗示的保证。

在任何情况下,作者均不对因使用本软件而造成的任何损害承担责任。

特此授予任何人为任何目的(包括商业应用)使用本软件的许可,并可以对其进行修改和自由再分发,但须遵守以下限制:

变更日志

请参阅 CHANGELOG 文件。