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

如何从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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:54:53