在 Swift 中使用 AES.GCM.open 进行 AES-256-GCM 解密失败

问题描述 投票:0回答:1

快速背景:我希望服务器使用客户端提供的公钥加密少量数据有效负载,以便客户端可以解密它,并且服务器不必存储敏感数据。

我的基本工作流程是:

  1. 让客户端生成公钥和私钥
  2. 在请求中将公钥发送到服务器
  3. 服务器生成自己的一次性密钥对,用客户端的公钥加密数据
  4. 服务器响应客户端解密所需的信息。

我正在使用ECDHAES-256-GCM,因为这些似乎很适合这项任务。

客户端和服务器都使用 Ruby,效果很好。我还有一个使用 SubtleCrypto/WebCrypto 运行的 JavaScript 客户端。服务器响应:

  • 派生共享秘密时使用的公钥
  • 使用的IV
  • 密文

客户端只需要这个就可以解密数据。完美。

现在的问题是尝试用 Swift 编写客户端时。 (这也是我第一次编写 Swift,所以请耐心等待)。我正在使用一个简单的 CLI 应用程序和 swift-crypto 进行概念验证。一切工作正常,包括计算共享秘密。这与服务器使用的秘密完全匹配,所以很好。当涉及到实际解密数据时,我收到错误:

Crypto.CryptoKitError.underlyingCoreCryptoError(error: 503316581)
import Foundation

#if os(Linux)
import FoundationNetworking
import Crypto
#endif

extension Data {
  public func urlsafeBase64() -> String {
    return base64EncodedString()
      .replacingOccurrences(of: "+", with: "-")
      .replacingOccurrences(of: "/", with: "_")
      .replacingOccurrences(of: "=", with: "")
  }

  public init?(base64urlEncoded input: String) {
      var base64 = input
      base64 = base64.replacingOccurrences(of: "-", with: "+")
      base64 = base64.replacingOccurrences(of: "_", with: "/")
      while base64.count % 4 != 0 {
          base64 = base64.appending("=")
      }
      self.init(base64Encoded: base64)
  }
}

let ourPrivateKey = P256.KeyAgreement.PrivateKey()
let ourPublicKey = ourPrivateKey.publicKey

let reqData: [String: Any] = ["pem": ourPublicKey.pemRepresentation]

// SNIP -- HTTP interaction with the backend. Server returns:
// iv, ciphertext, tag, server's public key in PEM format
// Response loaded into `EncryptedData` struct.
// iv, tag, and ciphertext are all base64-urlsafe-encoded

struct EncryptedData: Codable {
  var ciphertext: String?
  var iv: String?
  var tag: String?
  var pem: String?
}

let serverPubKey = try! P256.KeyAgreement.PublicKey(pemRepresentation: ed.pem!)
let sharedSecret = try! ourPrivateKey.sharedSecretFromKeyAgreement(with: serverPubKey)
// At this point, we're good. sharedSecret is exactly the same as the server generates

// I don't have confidence in this next step. It's not a step I had to do
// in the Ruby or JavaScript implementations.
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(
  using: SHA256.self,
  salt: Data(),
  sharedInfo: Data(),
  outputByteCount: 32
)

// In Ruby and JS this is called the IV. I'm guessing it's referred to as nonce in Swift?
let nonceData = Data(base64urlEncoded: ed.iv!)!
let ciphertextData = Data(base64urlEncoded: ed.ciphertext!)!
let tagData = Data(base64urlEncoded: ed.tag!)!

let sealedBox = try! AES.GCM.SealedBox(
  nonce: AES.GCM.Nonce(data: nonceData),
  ciphertext: ciphertextData,
  tag: tagData
)

let decrypted = try! AES.GCM.open(sealedBox, using: symmetricKey)
// Boom: Crypto.CryptoKitError.underlyingCoreCryptoError(error: 503316581)

所以问题是:我在这里错过了什么?为什么即使客户端和服务器到达相同的秘密,解密也会失败并出现如此模糊的错误消息?

如果有兴趣,这是我正在测试的后端代码:

def enc_test
  message = JSON.generate(ts: Time.now.to_s)

  user_pubkey = OpenSSL::PKey::EC.new(params.require("pem"))

  render(json: encrypt(message, user_pubkey))
end

private

ALGO = "aes-256-gcm"

def encode64(data) = Base64.urlsafe_encode64(data, padding: false)

def encrypt(message, user_pubkey)
  server_key = OpenSSL::PKey::EC.generate("prime256v1")
  derived_key = server_key.derive(user_pubkey)

  ciphertext, tag, iv = OpenSSL::Cipher.new(ALGO).encrypt.then do |c|
    iv = c.random_iv

    c.key = derived_key
    c.iv = iv
    [c.update(message) + c.final, c.auth_tag, iv]
  end

  {
    ciphertext: encode64(ciphertext),
    tag: encode64(tag),
    iv: encode64(iv),
    pem: server_key.to_public_pem,
  }
end
swift encryption openssl elliptic-curve aes-gcm
1个回答
0
投票

当然,在发布此内容后,我重新思考并弄清楚了。我不确定的那一步(派生密钥)实际上是问题所在。共享秘密对称密钥。

进行这一更改即可解决问题:

let symmetricKey = SymmetricKey(data: sharedSecret)
© www.soinside.com 2019 - 2024. All rights reserved.