如何从Curve25519公钥生成符合X9.62/PKCS#8的PEM(iOS实现)
iOS 实现 Curve25519(X25519)符合 X9.62/PKCS#8 的密钥交换方案
你遇到的核心问题是:CryptoKit 生成的公钥是裸32字节值,缺少 X9.62 标准要求的 ASN.1/DER 结构(包含算法标识符、曲线参数等元信息),导致后端无法识别。以下是两种可行实现方案:
方案一:手动构造符合标准的 PEM 公钥(无第三方依赖)
直接基于 CryptoKit 生成的密钥,手动拼接出符合 X.509 SubjectPublicKeyInfo 格式的 DER 编码,再转成 PEM。
代码实现
import CryptoKit // 将 X25519 公钥转换为符合 X9.62 的 PEM 格式 func x25519PublicKeyToPEM(_ publicKey: Curve25519.KeyAgreement.PublicKey) -> String { // 1. 构造 AlgorithmIdentifier(Curve25519 的 OID 为 1.3.101.110) let algorithmIdentifier = Data([0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x05, 0x00]) // 2. 构造 SubjectPublicKey(BIT STRING 格式,前缀 0x00 表示无未使用位) let subjectPublicKey = Data([0x00]) + publicKey.rawRepresentation let bitString = Data([0x03, 0x21]) + subjectPublicKey // 3. 拼接完整的 SubjectPublicKeyInfo 序列 let totalLength = algorithmIdentifier.count + bitString.count let spkiSequence = Data([0x30]) + byteLength(of: totalLength) + algorithmIdentifier + bitString // 4. 转成 Base64 并按 PEM 规范换行 let base64 = spkiSequence.base64EncodedString() let pemLines = base64.split(by: 64).joined(separator: "\n") return "-----BEGIN PUBLIC KEY-----\n\(pemLines)\n-----END PUBLIC KEY-----" } // 生成 X25519 密钥对并返回公钥 PEM func generateX25519KeyPair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKeyPEM: String) { let privateKey = Curve25519.KeyAgreement.PrivateKey() let publicKeyPEM = x25519PublicKeyToPEM(privateKey.publicKey) return (privateKey, publicKeyPEM) } // MARK: - 辅助工具函数 private func byteLength(of length: Int) -> Data { if length < 128 { return Data([UInt8(length)]) } else { let bytes = withUnsafeBytes(of: length.bigEndian) { Array($0) }.drop { $0 == 0 } return Data([UInt8(0x80 + bytes.count)]) + Data(bytes) } } extension String { func split(by chunkSize: Int) -> [String] { stride(from: 0, to: count, by: chunkSize).map { let start = index(startIndex, offsetBy: $0) let end = index(start, offsetBy: chunkSize, limitedBy: endIndex) ?? endIndex return String(self[start..<end]) } } }
方案二:使用系统 Security 框架生成密钥(更稳定)
利用 iOS 原生 Security 框架生成 Curve25519(标识为 kSecP25519Curve)密钥对,导出的公钥原生符合 X9.62 格式,无需手动拼接。
代码实现
import Security // 生成 X25519 密钥对并导出公钥 PEM func generateX25519KeyPairWithSecurityFramework() throws -> (privateKey: SecKey, publicKeyPEM: String) { let keyAttributes: [CFString: Any] = [ kSecAttrKeyType: kSecAttrKeyTypeEC, kSecAttrKeySizeInBits: 256, kSecAttrECDSACurve: kSecP25519Curve ] var error: Unmanaged<CFError>? guard let privateKey = SecKeyCreateRandomKey(keyAttributes as CFDictionary, &error) else { throw error!.takeRetainedValue() as Error } // 导出公钥为 DER 格式 guard let publicKey = SecKeyCopyPublicKey(privateKey), let publicKeyDER = SecKeyCopyExternalRepresentation(publicKey, &error) else { throw error!.takeRetainedValue() as Error } // 转成 PEM 格式 let base64 = publicKeyDER.base64EncodedString() let pemLines = base64.split(by: 64).joined(separator: "\n") let publicKeyPEM = "-----BEGIN PUBLIC KEY-----\n\(pemLines)\n-----END PUBLIC KEY-----" return (privateKey, publicKeyPEM) }
密钥交换与后端交互示例
拿到后端返回的公钥 PEM 后,解析并执行密钥交换:
// 解析后端返回的 PEM 公钥 func parseX25519PublicKey(from pem: String) throws -> Curve25519.KeyAgreement.PublicKey { let trimmedPEM = pem.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "") .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "") .replacingOccurrences(of: "\n", with: "") guard let derData = Data(base64Encoded: trimmedPEM) else { throw NSError(domain: "CryptoError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无效的 Base64"]) } // 解析 DER 结构,提取裸公钥 var index = 0 // 跳过 SubjectPublicKeyInfo 序列头 index += 1 + parseLength(from: derData, startingAt: &index).count // 跳过 AlgorithmIdentifier 序列头 index += 1 + parseLength(from: derData, startingAt: &index).count // 跳过 BIT STRING 头和未使用位标识 index += 1 + parseLength(from: derData, startingAt: &index).count + 1 // 提取32字节裸公钥 let rawPublicKey = derData.subdata(in: index..<index+32) return try Curve25519.KeyAgreement.PublicKey(rawRepresentation: rawPublicKey) } // 执行密钥交换生成对称密钥 func performKeyExchange(privateKey: Curve25519.KeyAgreement.PrivateKey, remotePublicKeyPEM: String) throws -> SymmetricKey { let remotePublicKey = try parseX25519PublicKey(from: remotePublicKeyPEM) return privateKey.sharedSecretFromKeyAgreement(with: remotePublicKey).hkdfDerivedSymmetricKey( using: SHA256.self, salt: Data(), info: Data(), outputByteCount: 32 ) } private func parseLength(from data: Data, startingAt index: inout Int) -> Data { let lengthByte = data[index] index += 1 if lengthByte < 0x80 { return Data([lengthByte]) } else { let numBytes = Int(lengthByte - 0x80) let lengthBytes = data.subdata(in: index..<index+numBytes) index += numBytes return lengthBytes } }
内容的提问来源于stack exchange,提问作者kmithi1
相关产品推荐
相关产品推荐

