CryptoSwift
CryptoSwift 使用 Swift 实现的 Swift 项目中的加密相关函数和辅助工具。(#PureSwift)
注意:主分支遵循当前发布的最新 Swift 版本。如果您需要旧版本的 Swift,可以在您的 Podfile
中指定其版本,或使用该版本分支上的代码。旧分支不受支持。请查看 版本 以获取详细信息。
要求 | 特性 | 贡献 | 安装 | Swift 版本 | 如何使用 | 作者 | 许可证 | 变更日志
赞助
为了您的便利,我花费了一些时间来维护,所以或许您可以赞助 1 美元,这样我可以继续工作。每天有超过 8000 次克隆。如果每个使用我在这里的工作的公司每个月赞助我 1 美元,我会说我们已经持平了。赶快行动吧,找到 赞助 按钮,履行您的责任。
CryptoSwift 并未得到任何大公司的支持,它是在我的业余时间开发的,同时也是我作为自由职业者的一部分。
需求
好心情
功能
- 易于使用
- 方便的 String 和 Data 扩展
- 支持增量更新(流等)
- 支持 iOS、Android、macOS、AppleTV、watchOS、Linux 平台
哈希(摘要)
MD5 | SHA1 | SHA2-224 | SHA2-256 | SHA2-384 | SHA2-512 | SHA3
循环冗余校验(CRC)
加密方法
AES-128, AES-192, AES-256 | ChaCha20 | Rabbit | Blowfish
RSA (公钥加密算法)
消息认证器
Poly1305 | HMAC (MD5, SHA1, SHA256) | CMAC | CBC-MAC
加密操作模式
- 电子密码本 (ECB)
- 分组密码块链接 (CBC)
- 传播密码块链链 (PCBC)
- 密码反馈 (CFB)
- 输出反馈 (OFB)
- 计数模式(CTR)
- Galois/计数模式(GCM)
- 带有密码块链接消息认证码的计数(CCM)
- OCB认证加密算法(OCB)
基于密码的密钥派生函数
数据填充
带关联数据的认证加密(AEAD)
为什么
如何参与?
你想帮忙,太好了!去fork我们的仓库,做出改变并发送给我们一个pull request。
贡献
查看CONTRIBUTING.md了解如何帮助CryptoSwift。
安装
加固运行时(macOS)和Xcode
Binary CryptoSwift.xcframework(用于Swift Package Manager包集成)如果在应用中使用带有启用加固运行时的Sign to Run Locally签名证书则无法在应用中正确加载。要解决这个问题,你有两种选择:
- 使用合适的签名证书,例如:Development <- 这是一个正确的操作
- 使用
Disable Library Validation
也称为com.apple.security.cs.disable-library-validation
权限
Xcode 项目
要安装 CryptoSwift,将其作为子模块添加到项目中(在顶级项目目录中)
git submodule add https://github.com/krzyzanowskim/CryptoSwift.git
建议启用 整个模块优化 以获得更好的性能。未经优化的构建会导致性能显著下降。
Swift 包管理器
您可以使用 Swift 包管理器 并在 Package.swift
中指定依赖项,添加以下内容
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMajor(from: "1.7.1"))
请参阅: Package.swift - 手册
注意:Swift 包管理器在调试 Xcode 构建中使用调试配置,这可能会导致性能严重下降(高达 x10000)。发布构建中性能特性不同。为了克服这个问题,请考虑嵌入以下所描述的 CryptoSwift.xcframework
。
CocoaPods
您可以使用 CocoaPods。
pod 'CryptoSwift', '~> 1.7.1'
请注意,CocoaPods 将在未启用 整个模块优化 的情况下构建 CryptoSwift,这可能会影响性能。您可以在安装后手动更改它,或使用 cocoapods-wholemodule 插件。
Carthage
您可以使用 Carthage 并在 Cartfile 中指定。
github "krzyzanowskim/CryptoSwift"
运行 carthage
构建框架,并将构建的 CryptoSwift.framework 拖入您的 Xcode 项目中。遵循 构建说明。常见问题 请见。
XCFramework
构建 XCFramework 需要使用 Xcode 11 或更高版本,并且它们的集成方式类似于我们常用的 .framework
格式的集成方式。请使用脚本 scripts/build-framework.sh 生成二进制的 CryptoSwift.xcframework
归档,您可以用作 Xcode 中的依赖项。
CryptoSwift.xcframework 是一个发布(优化)二进制文件,提供最佳可用的 Swift 代码性能。
嵌入式框架
嵌入式框架需要 iOS 11.0 或 macOS Sierra(10.13)的最低部署目标。将 CryptoSwift.xcodeproj
文件拖入您的 Xcode 项目中,并将相应的框架作为依赖项添加到您的目标中。现在选择您的 App,并选择应用程序目标的“通用”选项卡。找到“嵌入式二进制文件”,然后按“+”,选择 CryptoSwift.framework
(iOS、macOS、watchOS 或 tvOS)
有时“嵌入式框架”选项不可用。在这种情况下,您必须为目标添加新的构建阶段。
iOS、macOS、watchOS、tvOS
在项目中,您将找到一个适用于所有平台的 单个方案。
- CryptoSwift
Swift 版本支持
- Swift 1.1: 分支 swift11 版本 <= 0.0.6
- Swift 2.1: 分支 swift21 版本 <= 0.2.3
- Swift 2.2, 2.3: 分支 swift2 版本 <= 0.5.2
- Swift 3.1: 分支 swift3 版本 <= 0.6.9
- Swift 3.2: 分支 swift32 版本 = 0.7.0
- Swift 4.0: 分支 swift4 版本 <= 0.12.0
- Swift 4.2: 分支 swift42 版本 <= 0.15.0
- Swift 5.0: 分支 swift5 版本 <= 1.2.0
- Swift 5.1: 分支 swift51 版本 <= 1.3.3
- Swift 5.3 及以上版本,分支 main
如何使用
- 基础(数据类型,转换……)
- 摘要(MD5,SHA…)
- 信息认证码(HMAC,CMAC…)
- 基于密码的关键派生函数(PBKDF2,…)
- 基于HMAC的关键派生函数(HKDF)
- 数据填充
- ChaCha20
- Rabbit
- Blowfish
- AES - 高级加密标准
- AES-GCM
- 带有关联数据的身份验证加密(AEAD)
基础知识
import CryptoSwift
CryptoSwift使用字节数组aka Array<UInt8>
作为所有操作的基本类型。每个数据都可以转换为字节流。您将找到接受String
或Data
的便利函数,并将其内部转换为字节数组。
数据类型转换
为了您的方便,CryptoSwift提供了两个函数来轻松地将字节数组转换为Data
或将Data
转换为字节数组
字节数据
let data = Data([0x01, 0x02, 0x03])
Data
转换为Array
let bytes = data.bytes // [1,2,3]
十六进制编码
let bytes = Array<UInt8>(hex: "0x010203") // [1,2,3]
let hex = bytes.toHexString() // "010203"
从字符串构建字节
let bytes: Array<UInt8> = "cipherkey".bytes // Array("cipherkey".utf8)
另外,查看与Base64编码数据一起工作的辅助函数
"aPf/i9th9iX+vf49eR7PYk2q7S5xmm3jkRLejgzHNJs=".decryptBase64ToString(cipher)
"aPf/i9th9iX+vf49eR7PYk2q7S5xmm3jkRLejgzHNJs=".decryptBase64(cipher)
bytes.toBase64()
计算摘要
散列数据或字节数组(即Array
)
/* 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)
兔子
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-128 = 16字节
- AES-192 = 24字节
- AES-256 = 32字节
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实例不应重复使用。因此,您不能从编码到解码同时使用相同的代码实例。
AES-CCM
计数密文块链接-消息认证码(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 密钥 -> Apple的安全框架 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的安全框架 SecKey -> CryptoSwift RSA 密钥示例
/// 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 关注我的项目更新和发布
加密通知
本分发包括加密软件。您目前居住的国家可能对加密软件的进口、拥有、使用和/或将加密软件再出口到另一个国家的行为有限制。在使用任何加密软件之前,请检查您国家的法律、法规和政策,以了解加密软件的进口、拥有或使用、再出口是否被允许。有关更多信息,请见http://www.wassenaar.org/。
许可
版权 (C) 2014-2022 Marcin Krzyżanowski [email protected] 本软件按“原样”提供,不提供任何明示或暗示的担保。
在任何情况下,作者不应对使用本软件引起的任何损害承担责任。
任何人许可使用此软件,以任何目的,包括商业应用,并将其自由修改和重新分发,但受以下限制。
- 本软件的来源不得虚假陈述;您不得宣称您是原始软件的作者。如果您在产品中使用本软件,产品文档中必须有一次承认。
- 修改后的源版本必须清楚地标记为修改版本,并且不得谎称是原始软件。
- 本通知不得从任何源或二进制分发中删除或修改。
- 任何形式的重新分发都必须保留以下承认:“本产品包含由“Marcin Krzyzanowski”(http://krzyzanowskim.com/)开发的软件。”
变更日志
参见变更日志文件。