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 的修订版 131c6e771c671867fabcec3bdf8c45c4c38a131b 上,使用 Xcode 16.2 运行此脚本,生成的文件与此存储库中的文件完全相同。

密钥加密

使用相同的密钥加密和解密消息,这也称为对称加密。

可以使用 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)!

使用 RandomBytes.Generator 作为生成器来生成密码安全的伪随机数。

var rng = RandomBytes.Generator()
let randomUInt32 = UInt32.random(in: 0...10, using: &rng)
let randomUInt64 = UInt64.random(in: 0...10, using: &rng)
let randomInt = Int.random(in: 0...10, using: &rng)
let randomDouble = Double.random(in: 0...1, using: &rng)

密码哈希

密码哈希提供了从低熵密码派生密钥材料的能力。 密码哈希函数被设计为计算成本高昂,以阻止暴力攻击,因此计算和内存参数可能是用户定义的。

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)

算法