如何将OpenSSL生成的44字节X25519公钥转换为CryptoKit所需的32字节?
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
SEQUENCEcontaining two elements:- Another
SEQUENCE(the algorithm identifier):- An
OBJECT IDENTIFIER(1.3.101.110, which identifies X25519) - A
NULLvalue (required by the ASN.1 schema for this algorithm)
- An
- An
OCTET STRINGthat holds exactly the raw 32-byte X25519 public key we need for CryptoKit.
- Another
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

