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

如何将OpenSSL生成的44字节X25519公钥转换为CryptoKit所需的32字节?

How to Convert OpenSSL-Generated X25519 Public Key to CryptoKit's 32-Byte Format

Got it, let's break down why your initial approach didn't work and fix it properly. The core issue here is that OpenSSL outputs X25519 public keys wrapped in an ASN.1/DER encoded structure—not just the raw 32-byte public key material. Cutting off the last 12 bytes won't work because you're ignoring the structured metadata that precedes the actual key.

What's Inside the OpenSSL Public Key?

The PEM-encoded public key from OpenSSL decodes to a DER structure following RFC 8410. Here's the breakdown:

  • A top-level SEQUENCE containing two elements:
    1. Another SEQUENCE (the algorithm identifier):
      • An OBJECT IDENTIFIER (1.3.101.110, which identifies X25519)
      • A NULL value (required by the ASN.1 schema for this algorithm)
    2. An OCTET STRING that holds exactly the raw 32-byte X25519 public key we need for CryptoKit.

The Correct Solution: Parse the ASN.1 Structure

We need to extract that inner OCTET STRING from the DER data. Below is a self-contained Swift implementation that handles PEM decoding, ASN.1 parsing, and initialization of the CryptoKit public key:

import CryptoKit
import Foundation

extension Curve25519.KeyAgreement.PublicKey {
    init(openSSLPEMEncoded pemString: String) throws {
        // Strip PEM headers/footers and clean up whitespace
        let strippedPEM = pemString
            .replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
            .replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
            .trimmingCharacters(in: .whitespacesAndNewlines)
        
        // Decode base64 to DER data
        guard let derData = Data(base64Encoded: strippedPEM) else {
            throw NSError(domain: "X25519KeyError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Invalid base64-encoded PEM data"])
        }
        
        // Parse the ASN.1 DER structure
        let asn1Parser = ASN1DERParser(data: derData)
        guard let topLevelSequence = try asn1Parser.parseSequence(), topLevelSequence.count == 2 else {
            throw NSError(domain: "X25519KeyError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Invalid top-level ASN.1 sequence"])
        }
        
        // Skip the algorithm identifier sequence (first element)
        _ = try topLevelSequence[0].parseSequence()
        
        // Extract the raw 32-byte public key from the octet string (second element)
        guard let rawPublicKeyData = try topLevelSequence[1].parseOctetString(), rawPublicKeyData.count == 32 else {
            throw NSError(domain: "X25519KeyError", code: 3, userInfo: [NSLocalizedDescriptionKey: "Invalid public key length (expected 32 bytes)"])
        }
        
        // Initialize the CryptoKit public key
        self.init(rawRepresentation: rawPublicKeyData)
    }
}

// Helper class to parse basic ASN.1 DER structures
class ASN1DERParser {
    private let data: Data
    private var currentOffset: Int = 0
    
    init(data: Data) {
        self.data = data
    }
    
    func parseSequence() throws -> [ASN1DERParser] {
        // Verify we're looking at a SEQUENCE tag (0x30)
        guard currentOffset < data.count, data[currentOffset] == 0x30 else {
            throw NSError(domain: "ASN1Error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Expected ASN.1 SEQUENCE tag"])
        }
        currentOffset += 1
        
        // Parse the sequence length
        let sequenceLength = try parseLength()
        let sequenceEndOffset = currentOffset + sequenceLength
        guard sequenceEndOffset <= data.count else {
            throw NSError(domain: "ASN1Error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Sequence length exceeds available data"])
        }
        
        // Create a parser for the sequence content
        let sequenceData = data.subdata(in: currentOffset..<sequenceEndOffset)
        currentOffset = sequenceEndOffset
        
        var elementParsers: [ASN1DERParser] = []
        var elementParser = ASN1DERParser(data: sequenceData)
        while elementParser.currentOffset < sequenceData.count {
            elementParsers.append(elementParser)
            // Advance past the current element's tag and length
            try elementParser.skipTagAndLength()
        }
        
        return elementParsers
    }
    
    func parseOctetString() throws -> Data {
        // Verify we're looking at an OCTET STRING tag (0x04)
        guard currentOffset < data.count, data[currentOffset] == 0x04 else {
            throw NSError(domain: "ASN1Error", code: 3, userInfo: [NSLocalizedDescriptionKey: "Expected ASN.1 OCTET STRING tag"])
        }
        currentOffset += 1
        
        // Parse the octet string length
        let octetLength = try parseLength()
        guard currentOffset + octetLength <= data.count else {
            throw NSError(domain: "ASN1Error", code: 4, userInfo: [NSLocalizedDescriptionKey: "Octet string length exceeds available data"])
        }
        
        // Extract the octet data
        let octetData = data.subdata(in: currentOffset..<currentOffset+octetLength)
        currentOffset += octetLength
        
        return octetData
    }
    
    private func parseLength() throws -> Int {
        guard currentOffset < data.count else {
            throw NSError(domain: "ASN1Error", code: 5, userInfo: [NSLocalizedDescriptionKey: "No length data available"])
        }
        
        let lengthByte = data[currentOffset]
        currentOffset += 1
        
        if lengthByte & 0x80 == 0 {
            // Short form length (1 byte)
            return Int(lengthByte)
        } else {
            // Long form length: first byte indicates number of length bytes
            let lengthByteCount = Int(lengthByte & 0x7F)
            guard currentOffset + lengthByteCount <= data.count else {
                throw NSError(domain: "ASN1Error", code: 6, userInfo: [NSLocalizedDescriptionKey: "Length field exceeds available data"])
            }
            
            var length = 0
            for i in 0..<lengthByteCount {
                length = (length << 8) | Int(data[currentOffset + i])
            }
            currentOffset += lengthByteCount
            
            return length
        }
    }
    
    private func skipTagAndLength() throws {
        guard currentOffset < data.count else {
            throw NSError(domain: "ASN1Error", code: 7, userInfo: [NSLocalizedDescriptionKey: "No tag data available"])
        }
        currentOffset += 1 // Skip tag byte
        _ = try parseLength() // Skip length bytes
    }
}

// Example usage with your sample public key
let samplePEM = """
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VuAyEAE0eiiP0PKjy9AVM/0z2ZIZn453WSJNemrQ58HAXDaX0=
-----END PUBLIC KEY-----
"""

do {
    let cryptoKitPublicKey = try Curve25519.KeyAgreement.PublicKey(openSSLPEMEncoded: samplePEM)
    print("Successfully created CryptoKit public key: \(cryptoKitPublicKey)")
} catch {
    print("Error converting key: \(error.localizedDescription)")
}

Why Your Initial Approach Failed

The 44-byte Base64-decoded data isn't just a 32-byte key plus 12 extra bytes. The ASN.1 structure adds variable-length metadata before the raw key, so truncating arbitrary bytes won't reliably extract the correct 32 bytes. Parsing the ASN.1 structure ensures you always get exactly the raw key material CryptoKit expects.

内容的提问来源于stack exchange,提问作者Moritz Herbert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 16:32:43