Swift-Sodium 提供了一个安全且易于使用的接口,用于在 macOS、iOS、tvOS 和 watchOS 上执行常见的加密操作。
它利用了 Sodium 库,虽然 Swift 是主要目标,但该框架也可以在 Objective-C 应用程序中使用。
当前的 Swift-Sodium 文档还不够完善。 非常感谢您能帮助改进它,使其变得更棒!
要将 Swift-Sodium 作为依赖项添加到您的 Xcode 项目,请选择File
> Swift Packages
> Add Package Dependency
,输入其仓库 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 脚本生成。
在 libsodium 的修订版 7d71804ee05f71b65c94282f706c5aa82b279e8a
上,使用 Xcode 12.5.1 (12E507
) 运行此脚本会生成与此仓库中存在的文件相同的文件。
消息使用相同的密钥进行加密和解密,这也称为对称加密。
密钥可以使用 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:)
使用用户定义的 nonce 的能力。
使用公钥密码学,每个对等方都有两个密钥:一个必须保密的秘密(私有)密钥,以及一个任何人都可以用来向该对等方发送加密消息的公钥。 该公钥只能用于加密消息。 解密需要相应的秘密密钥。
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
}
哈希有效地“指纹”输入数据,无论其大小,并返回固定长度的“摘要”。
可以根据需要配置摘要长度,从 16 到 64 字节。
let sodium = Sodium()
let message = "My Test Message".bytes
let hash = sodium.genericHash.hash(message: message)
let hashOfSize32Bytes = sodium.genericHash.hash(message: message, outputLength: 32)
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()
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)
sodium.keyDerivation.derive()
函数使用输入(主)密钥、索引和一个标识上下文的 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: " :")
let sodium = Sodium()
let b64 = sodium.utils.bin2base64("data".bytes)!
let b64_2 = sodium.utils.bin2base64("data".bytes, variant: .URLSAFE_NO_PADDING)!
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()
函数将任意长度的输入与从密钥和 nonce 派生的确定性密钥流的输出组合(使用 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)