快速背景:我希望服务器使用客户端提供的公钥加密少量数据有效负载,以便客户端可以解密它,并且服务器不必存储敏感数据。
我的基本工作流程是:
我正在使用ECDH和AES-256-GCM,因为这些似乎很适合这项任务。
客户端和服务器都使用 Ruby,效果很好。我还有一个使用 SubtleCrypto/WebCrypto 运行的 JavaScript 客户端。服务器响应:
客户端只需要这个就可以解密数据。完美。
现在的问题是尝试用 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
当然,在发布此内容后,我重新思考并弄清楚了。我不确定的那一步(派生密钥)实际上是问题所在。共享秘密是对称密钥。
进行这一更改即可解决问题:
let symmetricKey = SymmetricKey(data: sharedSecret)