You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Swift中解密Python cryptography库生成的Fernet加密文本?

Fernet密文Swift解密失败解决方案

错误原因

当前实现存在几个核心逻辑错误导致解密失败:

  • 直接对Base64编码后的密文字符串做偏移拆分,Fernet的字段拆分规则是针对解码后的二进制数据的,编码后字符串的偏移和二进制偏移完全不对应,拆分出来的IV和密文自然是错误的
  • 没有正确拆分Fernet密钥:Fernet标准密钥是Base64URL编码的32字节二进制数据,前16字节为HMAC-SHA256签名校验密钥,后16字节才是AES解密密钥
  • 未做签名校验就直接解密,Fernet密文末尾携带32字节的HMAC签名,必须先校验签名合法性再执行解密操作
  • 未处理URL安全Base64的编码适配,Fernet用的是-/_替代+//的URL安全Base64标准,普通Base64解码前需要先做字符替换、补全补位等号

正确解密步骤

  1. 先对密钥和密文字符串做URL安全Base64转标准Base64处理,再解码为二进制数组
  2. 拆分密钥:取解码后密钥的后16字节作为AES密钥,前16字节作为HMAC校验密钥
  3. 拆分解码后的密文二进制:
    • 偏移0:1字节版本号
    • 偏移1:8字节时间戳
    • 偏移9:16字节IV
    • 偏移25到末尾前32字节:AES密文
    • 末尾32字节:HMAC签名
  4. 先校验HMAC签名:将版本号、时间戳、IV、密文拼接后用HMAC-SHA256和签名密钥计算签名,和密文携带的签名对比,一致再继续
  5. 用AES-CBC模式、PKCS7填充执行解密,得到明文

可运行Swift代码(基于CryptoSwift)

import CryptoSwift

func fernetDecrypt(keyStr: String, encStr: String) throws -> String {
    // 1. 处理URL安全Base64转标准Base64辅助函数
    func urlSafeBase64ToStandard(_ str: String) -> String {
        var res = str
            .replacingOccurrences(of: "-", with: "+")
            .replacingOccurrences(of: "_", with: "/")
        // 补全补位等号
        let padding = res.count % 4
        if padding > 0 {
            res += String(repeating: "=", count: 4 - padding)
        }
        return res
    }
    
    // 2. 解码并拆分密钥
    let standardKeyStr = urlSafeBase64ToStandard(keyStr)
    guard let keyData = Data(base64Encoded: standardKeyStr), keyData.count == 32 else {
        throw NSError(domain: "FernetError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的Fernet密钥"])
    }
    let aesKey = Array(keyData[16..<32]) // 后16字节为AES解密密钥
    let hmacKey = Array(keyData[0..<16]) // 前16字节为HMAC校验密钥
    
    // 3. 解码密文
    let standardEncStr = urlSafeBase64ToStandard(encStr)
    guard let encData = Data(base64Encoded: standardEncStr), encData.count >= 1+8+16+32 else {
        throw NSError(domain: "FernetError", code: -2, userInfo: [NSLocalizedDescriptionKey: "无效的Fernet密文"])
    }
    let encBytes = Array(encData)
    
    // 4. 拆分密文字段
    guard encBytes[0] == 0x80 else { // Fernet固定版本号为0x80
        throw NSError(domain: "FernetError", code: -3, userInfo: [NSLocalizedDescriptionKey: "不支持的Fernet版本"])
    }
    let iv = Array(encBytes[9..<25]) // 偏移9开始的16字节为IV
    let cipherText = Array(encBytes[25..<encBytes.count-32]) // 偏移25到末尾前32字节为密文
    let hmacSign = Array(encBytes[encBytes.count-32..<encBytes.count]) // 末尾32字节为HMAC签名
    
    // 5. 校验HMAC签名
    let signedContent = Array(encBytes[0..<encBytes.count-32])
    let calculatedSign = try HMAC(key: hmacKey, variant: .sha256).authenticate(signedContent)
    guard calculatedSign == hmacSign else {
        throw NSError(domain: "FernetError", code: -4, userInfo: [NSLocalizedDescriptionKey: "签名校验失败,密文被篡改"])
    }
    
    // 6. AES解密得到明文
    let aes = try AES(key: aesKey, blockMode: CBC(iv: iv), padding: .pkcs7)
    let decryptedBytes = try aes.decrypt(cipherText)
    guard let plaintext = String(bytes: decryptedBytes, encoding: .utf8) else {
        throw NSError(domain: "FernetError", code: -5, userInfo: [NSLocalizedDescriptionKey: "明文编码错误"])
    }
    return plaintext
}

// 测试调用
func testDecrypt() {
    let key = "3b-Nqg6ry-jrAuDyVjSwEe8wrdyEPQfPuOQNH1q5olE="
    let enc = "gAAAAABhBRBGKSwa7AluNJYhwWaHrQGwAA8UpMH8Wtw3tEoTD2E_-nbeoAvxbtBpFiC0ZjbVne_ZetFinKSyMjxwWaPRnXVSVqz5QqpUXp6h-34_TL7BaDs="
    do {
        let plain = try fernetDecrypt(keyStr: key, encStr: enc)
        print("解密成功,明文:", plain) // 输出:my deep dark secret
    } catch {
        print("解密失败:", error.localizedDescription)
    }
}

替换原有解密逻辑后调用testDecrypt()即可得到正确明文。

内容的提问来源于stack exchange,提问作者Syed Faizan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 06:27:02