使用CryptoKit EdDSA创建私钥时抛出incorrectKeySize错误
参考Stack Overflow上《How do I generate a JWT to use in API authentication for Swift app》的方案,尝试用EdDSA算法对私钥进行签名操作,但创建私钥时始终抛出incorrectKeySize错误。该私钥通过openssl genpkey生成,是否需要对其进行额外编码?
相关操作及代码如下:
1. OpenSSL生成公私钥命令
openssl genpkey -algorithm ED25519 -out ed25519-private.pem \ && openssl pkey -pubout -in ed25519-private.pem > ed25519-public.pem
2. Swift JWT签名代码
struct Header: Encodable { let alg = "EdDSA" let typ = "ABCDEFGHIJ" } struct Payload: Encodable { let sub = "ABCDEFGHIJ" let iat = 1516239022 let exp = 1516249022 } private func createJWT_apple() async -> String? { do { let secret = "MC4CAQAwBQYDK2VwBCIEIE9dWdfSb3YjvA9gASX8ZY7BiQubC4jQLiwQZtUmHXrD" // 该密钥已作废,仅作示例 let data = Data(base64Encoded: secret)! let privateKey = try Curve25519.Signing.PrivateKey(rawRepresentation: data) // 此处抛出"incorrectKeySize"错误! let headerJSONData = try JSONEncoder().encode(Header()) let headerBase64String = headerJSONData.urlSafeBase64EncodedString() let payloadJSONData = try JSONEncoder().encode(Payload()) let payloadBase64String = payloadJSONData.urlSafeBase64EncodedString() let toSign = Data((headerBase64String + "." + payloadBase64String).utf8) let signature = try privateKey.signature(for: toSign) let signatureBase64String = Data(signature).urlSafeBase64EncodedString() let token = [headerBase64String, payloadBase64String, signatureBase64String].joined(separator: ".") return token } catch { printLog(error) } return nil }
问题核心是OpenSSL生成的PEM格式私钥包含ASN.1编码的结构信息,而Curve25519.Signing.PrivateKey(rawRepresentation:)需要的是32字节的原始私钥数据,直接解码PEM的Base64字符串会得到多余的结构数据,导致密钥长度不符合要求。
解决步骤:
1. 从PEM文件提取原始私钥数据
OpenSSL生成的ED25519私钥PEM包含ASN.1 DER结构,可通过OpenSSL命令直接提取32字节原始私钥:
openssl pkcs8 -topk8 -inform PEM -outform DER -in ed25519-private.pem -nocrypt | tail -c +17 > ed25519-private.raw
这条命令将PKCS#8格式私钥转为DER编码,再截取从第17字节开始的32字节(ED25519原始私钥长度),保存为ed25519-private.raw文件。
2. 将原始私钥转为Base64用于代码
把ed25519-private.raw文件转为Base64字符串:
base64 -i ed25519-private.raw
得到的字符串就是Curve25519.Signing.PrivateKey所需的合法原始私钥数据。
3. 修改Swift代码使用正确私钥
替换代码中的secret为上述提取的原始私钥Base64字符串即可。
可选:代码内直接解析PEM私钥
如果不想提前处理私钥,可在Swift代码中手动解析PEM格式,处理ASN.1解码:
private func parseEd25519PrivateKey(from pemString: String) throws -> Curve25519.Signing.PrivateKey { // 去除PEM头部尾部及空白字符 let cleanedPem = pemString .replacingOccurrences(of: "-----BEGIN PRIVATE KEY-----", with: "") .replacingOccurrences(of: "-----END PRIVATE KEY-----", with: "") .trimmingCharacters(in: .whitespacesAndNewlines) // 解码Base64得到DER数据 guard let derData = Data(base64Encoded: cleanedPem) else { throw NSError(domain: "KeyError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的Base64格式"]) } // 从DER数据中提取32字节原始私钥(偏移16字节开始) guard derData.count >= 48 else { throw NSError(domain: "KeyError", code: -2, userInfo: [NSLocalizedDescriptionKey: "密钥长度无效"]) } let rawKeyData = derData.subdata(in: 16..<48) return try Curve25519.Signing.PrivateKey(rawRepresentation: rawKeyData) }
在createJWT_apple函数中调用该方法:
let pemSecret = """ -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIE9dWdfSb3YjvA9gASX8ZY7BiQubC4jQLiwQZtUmHXrD -----END PRIVATE KEY----- """ let privateKey = try parseEd25519PrivateKey(from: pemSecret)
通过以上方式即可避免incorrectKeySize错误,正确创建ED25519私钥并完成JWT签名。
内容的提问来源于stack exchange,提问作者stephen

