Swift-Sodium 构建状态

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()

短输出哈希 (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)

密钥派生

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: " :")

恒定时间 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() 函数将任意长度的输入与从密钥和 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)

算法