Swift-Sodium
Swift-Sodium 提供了一种安全且易于使用的界面,可以在 iOS 和 macOS 上执行常见的加密操作。
它利用了 Sodium 库,虽然 Swift 是主要的目标,但该框架也可以用于 Objective-C 应用程序。
请提供帮助!
当前的 Swift-Sodium 文档并不出色。如果您能帮助改进它并使其变得出色,将非常感谢!
使用
要将 Swift-Sodium 添加为 Xcode 项目的依赖项,请选择 文件
> Swift 包
> 添加包依赖项
,输入其仓库 URL:https://github.com/jedisct1/swift-sodium.git
并导入 Sodium
以及 Clibsodium
。
然后,为了在源代码中使用,添加
import Sodium
Sodium 库本身不需要在系统上安装:该仓库已包含针对 armv7、armv7s、arm64 以及 iOS 模拟器、WatchOS 和 Catalyst 的预编译库。
Clibsodium.xcframework
框架由 dist-build/apple-xcframework.sh 脚本生成。
在Xcode 12.3 (12C33
) 的 a08a945c589462f524efddbc8d62427bb002bb6a
版本的libsodium上运行此脚本会生成与该仓库中现有文件完全相同的文件。
密钥加密
消息使用相同的密钥加密和解密,这被称为对称加密。
可以使用 key()
方法生成密钥,这些密钥是通过密码哈希API从密码派生出来的,或者通过使用密钥交换API的密钥和使用对等方的公钥来计算出来的。
序列消息的认证加密
let sodium = Sodium()
let message1 = "Message 1".bytes
let message2 = "Message 2".bytes
let message3 = "Message 3".bytes
let secretkey = sodium.secretStream.xchacha20poly1305.key()
/* stream encryption */
let stream_enc = sodium.secretStream.xchacha20poly1305.initPush(secretKey: secretkey)!
let header = stream_enc.header()
let encrypted1 = stream_enc.push(message: message1)!
let encrypted2 = stream_enc.push(message: message2)!
let encrypted3 = stream_enc.push(message: message3, tag: .FINAL)!
/* stream decryption */
let stream_dec = sodium.secretStream.xchacha20poly1305.initPull(secretKey: secretkey, header: header)!
let (message1_dec, tag1) = stream_dec.pull(cipherText: encrypted1)!
let (message2_dec, tag2) = stream_dec.pull(cipherText: encrypted2)!
let (message3_dec, tag3) = stream_dec.pull(cipherText: encrypted3)!
流是一系列消息,这些消息将在发送时被加密,并在到达时被解密。期望加密消息按照发送的顺序接收。
流可以是任意长。因此,可以通过将文件分成小块来使用此API进行文件加密,这样整个文件就不必同时存在于内存中。
它还可以用于在两个对等方之间交换一系列消息。
解密函数会自动检查块是否已收到而没有修改、截断或重新排序。
每个消息都会附加一个标签,可以用于指示子序列的结束(PUSH
)或字符串的结束(FINAL
)。
独立消息的认证加密
let sodium = Sodium()
let message = "My Test Message".bytes
let secretKey = sodium.secretBox.key()
let encrypted: Bytes = sodium.secretBox.seal(message: message, secretKey: secretKey)!
if let decrypted = sodium.secretBox.open(nonceAndAuthenticatedCipherText: encrypted, secretKey: secretKey) {
// authenticator is valid, decrypted contains the original message
}
此API加密消息。解密过程在解密之前检查它们是否被篡改。
以这种方式加密的消息是独立的:如果以这种方式发送多个消息,接收者无法检测到是否存在某些消息被重复、删除或重新排序,除非在每条信息中包含其他数据。
可选地,SecretBox
通过seal(message: secretKey: nonce:)
提供使用用户定义的非ces的能力。
公钥密码学
基于公钥密码学,每个节点拥有两个密钥:一个私钥,必须保持秘密,以及一个公钥,任何人都可以使用它向该节点发送加密消息。该公钥只能用于加密消息。相关私钥是解密所需。
认证加密
let sodium = Sodium()
let aliceKeyPair = sodium.box.keyPair()!
let bobKeyPair = sodium.box.keyPair()!
let message = "My Test Message".bytes
let encryptedMessageFromAliceToBob: Bytes =
sodium.box.seal(message: message,
recipientPublicKey: bobKeyPair.publicKey,
senderSecretKey: aliceKeyPair.secretKey)!
let messageVerifiedAndDecryptedByBob =
sodium.box.open(nonceAndAuthenticatedCipherText: encryptedMessageFromAliceToBob,
senderPublicKey: aliceKeyPair.publicKey,
recipientSecretKey: bobKeyPair.secretKey)
此操作使用接收者的公钥对消息进行加密并发送。
接收者还必须知道发送者的公钥,并将拒绝似乎不适用于预期公钥的消息。
seal()
自动生成一个nonce并将其添加到密文之前。open()
提取nonce并解密密文。
可选地,Box
通过seal(message: recipientPublicKey: senderSecretKey: nonce:)
提供了使用用户定义的nonce的能力。
Box
类还提供了替代函数和参数,以确定性地生成密钥对,检索nonce和/或认证器,并将它们从原始消息中分离。
匿名加密(密封盒子)
let sodium = Sodium()
let bobKeyPair = sodium.box.keyPair()!
let message = "My Test Message".bytes
let encryptedMessageToBob =
sodium.box.seal(message: message, recipientPublicKey: bobKeyPair.publicKey)!
let messageDecryptedByBob =
sodium.box.open(anonymousCipherText: encryptedMessageToBob,
recipientPublicKey: bobKeyPair.publicKey,
recipientSecretKey: bobKeyPair.secretKey)
seal()
生成一个临时的密钥对,在加密过程中使用临时的密钥,将临时的公钥与密文结合,然后销毁密钥对。
发送者无法解密生成的密文。open()
提取公钥,并使用接收者的私钥进行解密。验证消息的完整性,但不能将发送者的身份与密文相关联。
密钥交换
let sodium = Sodium()
let aliceKeyPair = sodium.keyExchange.keyPair()!
let bobKeyPair = sodium.keyExchange.keyPair()!
let sessionKeyPairForAlice = sodium.keyExchange.sessionKeyPair(publicKey: aliceKeyPair.publicKey,
secretKey: aliceKeyPair.secretKey, otherPublicKey: bobKeyPair.publicKey, side: .CLIENT)!
let sessionKeyPairForBob = sodium.keyExchange.sessionKeyPair(publicKey: bobKeyPair.publicKey,
secretKey: bobKeyPair.secretKey, otherPublicKey: aliceKeyPair.publicKey, side: .SERVER)!
let aliceToBobKeyEquality = sodium.utils.equals(sessionKeyPairForAlice.tx, sessionKeyPairForBob.rx) // true
let bobToAliceKeyEquality = sodium.utils.equals(sessionKeyPairForAlice.rx, sessionKeyPairForBob.tx) // true
此操作使用私钥和对等节点的公钥计算共享密钥。
公钥签名
签名允许多方使用作者消息的公钥验证公文的真实性。
这特别适用于签署软件更新。
分离签章
let sodium = Sodium()
let message = "My Test Message".bytes
let keyPair = sodium.sign.keyPair()!
let signature = sodium.sign.signature(message: message, secretKey: keyPair.secretKey)!
if sodium.sign.verify(message: message,
publicKey: keyPair.publicKey,
signature: signature) {
// signature is valid
}
附加签章
let sodium = Sodium()
let message = "My Test Message".bytes
let keyPair = sodium.sign.keyPair()!
let signedMessage = sodium.sign.sign(message: message, secretKey: keyPair.secretKey)!
if let unsignedMessage = sodium.sign.open(signedMessage: signedMessage, publicKey: keyPair.publicKey) {
// signature is valid
}
哈希运算
确定性哈希运算
let sodium = Sodium()
let message = "My Test Message".bytes
let h = sodium.genericHash.hash(message: message)
密钥哈希运算
let sodium = Sodium()
let message = "My Test Message".bytes
let key = "Secret key".bytes
let h = sodium.genericHash.hash(message: message, key: key)
流式处理
let sodium = Sodium()
let message1 = "My Test ".bytes
let message2 = "Message".bytes
let key = "Secret key".bytes
let stream = sodium.genericHash.initStream(key: key)!
stream.update(input: message1)
stream.update(input: message2)
let h = stream.final()
短输出哈希运算(SipHash)
let sodium = Sodium()
let message = "My Test Message".bytes
let key = sodium.randomBytes.buf(length: sodium.shortHash.KeyBytes)!
let h = sodium.shortHash.hash(message: message, key: key)
随机数生成
let sodium = Sodium()
let randomBytes = sodium.randomBytes.buf(length: 1000)!
let seed = "0123456789abcdef0123456789abcdef".bytes
let stream = sodium.randomBytes.deterministic(length: 1000, seed: seed)!
密码散列
let sodium = Sodium()
let password = "Correct Horse Battery Staple".bytes
let hashedStr = sodium.pwHash.str(passwd: password,
opsLimit: sodium.pwHash.OpsLimitInteractive,
memLimit: sodium.pwHash.MemLimitInteractive)!
if sodium.pwHash.strVerify(hash: hashedStr, passwd: password) {
// Password matches the given hash string
} else {
// Password doesn't match the given hash string
}
if sodium.pwHash.strNeedsRehash(hash: hashedStr,
opsLimit: sodium.pwHash.OpsLimitInteractive,
memLimit: sodium.pwHash.MemLimitInteractive) {
// Previously hashed password should be recomputed because the way it was
// hashed doesn't match the current algorithm and the given parameters.
}
认证标签
sodium.auth.tag()
函数使用消息和密钥计算一个认证标签(HMAC)。了解密钥的各方可以使用相同的参数和 sodium.auth.verify()
函数验证消息的真实性。
认证标签不是签名:用于计算和验证标签的密钥是相同的。因此,验证者也可以为任意消息计算标签。
let sodium = Sodium()
let input = "test".bytes
let key = sodium.auth.key()
let tag = sodium.auth.tag(message: input, secretKey: key)!
let tagIsValid = sodium.auth.verify(message: input, secretKey: key, tag: tag)
密钥派生
函数通过输入(主)密钥、索引和用于标识上下文的 8 字节字符串生成一个子密钥。对于每个上下文,可以通过递增索引生成高达 (2^64) - 1 个子密钥。
let sodium = Sodium()
let secretKey = sodium.keyDerivation.keygen()!
let subKey1 = sodium.keyDerivation.derive(secretKey: secretKey,
index: 0, length: 32,
context: "Context!")
let subKey2 = sodium.keyDerivation.derive(secretKey: secretKey,
index: 1, length: 32,
context: "Context!")
实用功能
清零内存
let sodium = Sodium()
var dataToZero = "Message".bytes
sodium.utils.zero(&dataToZero)
恒等时间比较
let sodium = Sodium()
let secret1 = "Secret key".bytes
let secret2 = "Secret key".bytes
let equality = sodium.utils.equals(secret1, secret2)
填充
let sodium = Sodium()
var bytes = "test".bytes
// make bytes.count a multiple of 16
sodium.utils.pad(bytes: &bytes, blockSize: 16)!
// restore original size
sodium.utils.unpad(bytes: &bytes, blockSize: 16)!
填充可以在加密前用来隐藏消息的长度。
恒等时间十六进制编码
let sodium = Sodium()
let bytes = "Secret key".bytes
let hex = sodium.utils.bin2hex(bytes)
十六进制解码
let sodium = Sodium()
let data1 = sodium.utils.hex2bin("deadbeef")
let data2 = sodium.utils.hex2bin("de:ad be:ef", ignore: " :")
恒等时间Base64编码
let sodium = Sodium()
let b64 = sodium.utils.bin2base64("data".bytes)!
let b64_2 = sodium.utils.bin2base64("data".bytes, variant: .URLSAFE_NO_PADDING)!
Base64解码
let data1 = sodium.utils.base642bin(b64)
let data2 = sodium.utils.base642bin(b64, ignore: " \n")
let data3 = sodium.utils.base642bin(b64_2, variant: .URLSAFE_NO_PADDING, ignore: " \n")
构建自定义结构辅助工具
只有当你绝对需要这些函数,并且知道如何正确使用它们时,才使用以下函数。
未认证加密
sodium.stream.xor()
函数将任意长度的输入与从密钥和非确定数派生的确定性的密钥流输出进行异或操作。相同的操作两次执行会得到原始输入。
输出不会添加任何认证标签。数据可能会被篡改;对手可以翻转任意的位。
要使用密钥加密数据,你很可能需要查找 SecretBox
类。
要从一个种子生成一个确定的流,你很可能需要使用 RandomBytes.deterministic_rand()
函数。
let sodium = Sodium()
let input = "test".bytes
let key = sodium.stream.key()
let (output, nonce) = sodium.stream.xor(input: input, secretKey: key)!
let twice = sodium.stream.xor(input: output, nonce: nonce, secretKey: key)!
XCTAssertEqual(input, twice)