在Python和Swift之间交换加密消息

3

我需要一段Python代码和Swift代码来交换加密消息。

这是我尝试过的:

  1. Fernet

经过评估,我认为对称密钥算法可以很好地工作。

在Python中(通常情况下),加密和解密非常简单

Fernet(key).encrypt(b"mdg") # encrypt
Fernet(key).decrypt(encryptedMsg) # decrypt

在Swift中,最初似乎很简单,大致如下:
func encrypt(key: String, msg: String) throws -> String {
  let data = Data(base64URL: key)!
  let symetricKey = try! SymmetricKey(data: d)
  let msgUtf8 = msg.data(using: .utf8)!
  let sealBox = try! AES.GCM.seal(msgUtf8, using: symetricKey, nonce: nil)
  return sealBox.combined.base64EncodedString();
}

然而,我一直无法找到与Python的Fernet匹配的Swift算法。

  1. ChaCha

在寻找问题的过程中,我发现了Bram的这个惊人的答案。非常不幸的是,它只解决了我的一个问题:在Python中加密消息并在Swift中解码它们。我还需要相反的过程。

如何解决这个问题?

1个回答

1
首先,我们需要一种创建安全随机值以生成IV和密钥的方法。您也可以使用CryptoKit的SymmetricKey生成密钥并从中提取数据,但现在我将使用此函数。
extension Data {
    static func secureRandom(ofSize size: Int) -> Data {
        var output = [UInt8](repeating: 0, count: size)
        _ = SecRandomCopyBytes(kSecRandomDefault, size, &output)
        return Data(output)
    }
}

我们需要计算AES CBC密文的可能性,这可以使用CommonCrypto完成。
func encrypt(plaintext: Data, key: Data, iv: Data) -> Data {
    var encryptor: CCCryptorRef?
    defer {
        CCCryptorRelease(encryptor)
    }

    var key = Array(key)
    var iv = Array(iv)
    var plaintext = Array(plaintext)

    CCCryptorCreate(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOperation(kCCOptionPKCS7Padding), &key, key.count, &iv, &encryptor)

    var outputBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(encryptor, plaintext.count, false))
    CCCryptorUpdate(encryptor, &plaintext, plaintext.count, &outputBytes, outputBytes.count, nil)

    var movedBytes = 0
    var finalBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(encryptor, 0, true))
    CCCryptorFinal(encryptor, &finalBytes, finalBytes.count, &movedBytes)

    return Data(outputBytes + finalBytes[0 ..< movedBytes])
}

同时使用 SHA-256 哈希函数进行 HMAC 计算。我建议在这里使用 CryptoKit 的 HMAC 实现,但为了保持简单,我选择了 CommonCrypto 的实现方法。

func computeHMAC(_ data: Data, using key: Data) -> Data {
    var data = Array(data)
    var key = Array(key)
    var macOut = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), &key, key.count, &data, data.count, &macOut)
    return Data(macOut)
}

这将所有内容融合在一起,形成以下结果。
let plaintext = Data("Hello world!".utf8)

let signingKey = Data.secureRandom(ofSize: kCCKeySizeAES128)
let cryptoKey = Data.secureRandom(ofSize: kCCKeySizeAES128)
let fernetKey = (signingKey + cryptoKey).base64EncodedString()

let version: [UInt8] = [0x80]
let timestamp: [UInt8] = {
    let timestamp = Int(Date().timeIntervalSince1970).bigEndian
    return withUnsafeBytes(of: timestamp, Array.init)
}()
let iv = Data.secureRandom(ofSize: kCCBlockSizeAES128)
let ciphertext = encrypt(plaintext: plaintext, key: cryptoKey, iv: iv)
let hmac = computeHMAC(version + timestamp + iv + ciphertext, using: signingKey)

let fernetToken = (version + timestamp + iv + ciphertext + hmac).base64EncodedString()

print("Fernet key: \(fernetKey)")
print("Fernet token: \(fernetToken)")

一个示例输出可以是

Fernet key: 7EwFlYNKTGfj+2fSgL3AUqtrRqRs4D1TWNK7t2XbGJQ=
Fernet token: gAAAAABivCLM0y0poDtGOohT1yK4XTDJppYPJdu4fuDTZ5tb9P9KP5ACgX8aJq4imsSdbzOCcvY3Tueo4FYbwyG+ZugozILL+Q==

我们可以在Python中使用cryptography.io的实现。
from cryptography.fernet import Fernet

key = b'7EwFlYNKTGfj+2fSgL3AUqtrRqRs4D1TWNK7t2XbGJQ='
token = b'gAAAAABivCLM0y0poDtGOohT1yK4XTDJppYPJdu4fuDTZ5tb9P9KP5ACgX8aJq4imsSdbzOCcvY3Tueo4FYbwyG+ZugozILL+Q=='

Fernet(key).decrypt(token)
# b'Hello world!'

1
从SecRandomCopyBytes的文档中:"始终测试返回的状态.."!! - Sajjon
@Sajjon 对啊,根据演示,我没有这样做。更好的方法是异或几个随机源,以获得最佳熵值。但这都超出了这个问题的范围。此外,我已经提到了CryptoKit的SymmetricKey的使用,它为我们完成了所有繁重的工作。 - Bram

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接