Zum Inhalt springen
ecdsa.com
Swift

swift-crypto

swift-crypto, Apples CryptoKit-API als Open Source für Linux und andere Plattformen ohne CryptoKit: dieselben Swift-Typen auf einem mitgelieferten BoringSSL – mit deterministischem Ed25519, wo CryptoKit unter macOS randomisiert, und ohne Secure Enclave.
Lizenz
Apache-2.0
Geprüfte Version
5.0.3 ·
Quellcode
github.com/apple/swift-crypto
Dokumentation
swiftpackageindex.com/apple/swift-crypto/documentation/crypto

Kompatibilität auf einen Blick

ECDSAEdDSAPost-Quanten-Verfahren

ECDSA-Kurven
P-256P-384P-521
EdDSA
Ed25519
Post-Quanten-Algorithmen
ML-KEMML-DSAX-Wing
Formate öffentlicher Schlüssel
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32
Formate privater Schlüssel
pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32

Mit swift-crypto kann serverseitiger und plattformübergreifender Swift-Code die API von CryptoKit über import Crypto nutzen. Das README beschreibt die Bibliothek als Implementierung eines wesentlichen Teils dieser API für Linux und Windows auf ARM64; auf Apple-Plattformen exportiert das Modul CryptoKit selbst weiter (#if canImport(CryptoKit)), die Fakten hier beschreiben also die anderen Plattformen. Geprüft wurden sie mit swift-crypto 5.0.3 und swift-asn1 1.7.3 unter Linux im offiziellen Image für Swift 6.4.1, wo der Code auf einer mitgelieferten Kopie von BoringSSL läuft.

Derselbe Quellcode kann sich auf den beiden Plattformen unterschiedlich verhalten. Ed25519-Signaturen sind hier deterministisch – der Testschlüssel aus RFC 8032 reproduzierte die Signatur des RFC exakt –, während CryptoKit sie unter macOS randomisiert; beide Arten bestanden die Prüfung mit OpenSSL, Tests sollten Signaturen also prüfen, statt Bytes zu vergleichen. Die Parse-Fehler stimmten überein: Dieselben rohen r‖s-Bytes lösten in ECDSASignature(derRepresentation:) auf beiden Plattformen truncatedASN1Field aus, welcher Fall von CryptoKitASN1Error auftritt, hängt aber von den Eingabebytes ab – reagiere also auf das Scheitern an sich, nicht auf einen bestimmten Fehlerfall.

Alles andere, was gemessen wurde, stimmte mit CryptoKit überein. ECDSA-Signaturen bieten rawRepresentation (r‖s mit 64 Byte, wie in JWS) und derRepresentation (wie in X.509); konvertiere mit dem Konverter DER ⇄ Raw. Die Nonces sind zufällig, und s wird nicht normalisiert; High-S-Signaturen bestehen die Prüfung. signature(for: data) hasht mit dem Hash der Kurve, die Digest-API akzeptierte aber SHA-256 bei einem P-384-Schlüssel und kürzte einen SHA-512-Digest bei P-256, was OpenSSL bestätigte. Bei Ed25519 wird ein nicht kanonisches S abgelehnt, das neutrale Element dagegen als öffentlicher Schlüssel akzeptiert, zusammen mit einer Signatur mit R = neutrales Element und S = 0.

Die Post-Quanten-Typen entsprechen CryptoKit 26: ML-KEM-768 und ML-KEM-1024, ML-DSA-65 und ML-DSA-87 sowie das hybride X-Wing-KEM, auch als HPKE-Cipher-Suite. ML-DSA signiert im Hedged-Modus und unterstützt Kontext-Strings nach FIPS 204, die ML-KEM-Entkapselung eines beschädigten Chiffrats gibt ohne Fehler einen anderen Schlüssel zurück, und private Schlüssel werden als Seeds gespeichert (64 Byte für ML-KEM, 32 für ML-DSA). Vergleiche mit dem ML-KEM-Tool und dem Tool für Post-Quanten-Signaturen.

In diesen Quellen gibt es kein SecureEnclave, Code mit Schlüsseln in der Secure Enclave darf also nur für Apple-Plattformen kompiliert werden. Die Schlüsselformate entsprechen CryptoKit: Private Schlüssel für P-256 werden als PKCS#8 geschrieben und aus PKCS#8 oder SEC1 gelesen, jeweils als PEM oder DER, nicht aber aus verschlüsseltem PKCS#8; Ed25519-Schlüssel sind 32 rohe Bytes.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/swift-crypto/run.sh hat swift-crypto 5.0.3 am 20. Oktober 2026 im Container-Image swift:6.4.1-noble ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • Kodierung von ECDSA-Signaturen

    structured

    ECDSASignature hat eine 64 Byte lange rawRepresentation und eine derRepresentation; seine Bytes (withUnsafeBytes) sind die rohe Form.

    Von der Prüfung aufgezeichnet
    fact ecdsa-signature-encoding = structured
    evidence ecdsa-signature-encoding: signature(for:) returns P256.Signing.ECDSASignature: rawRepresentation 64 bytes, derRepresentation starts with 0x30 and converts back: yes, withUnsafeBytes yields the raw form: yes
  • ECDSA-Nonce

    random

    Zwei Signaturen derselben Nachricht unterschieden sich, und Schlüssel und Nachricht aus RFC 6979 A.2.5 ergaben nicht das r aus dem RFC.

    Von der Prüfung aufgezeichnet
    fact ecdsa-nonce = random
    evidence ecdsa-nonce: two signatures of one message are different; r for the RFC 6979 A.2.5 key and message equals the RFC's r: no
  • Low-S beim Signieren

    either

    s wird nicht normalisiert: Unter 64 Signaturen traten High-S-Werte auf.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • High-S bei der Prüfung

    accepts-high-s

    Eine Signatur, in der s durch n − s ersetzt war, bestand die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: isValidSignature with s replaced by n - s: true
  • Was der Signaturaufruf hasht

    hashes-message-or-digest

    signature(for: data) hasht die Eingabe; die Signatur von signature(for: SHA256.hash(data: message)) bestand die Prüfung gegen die Nachricht.

    Von der Prüfung aufgezeichnet
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: signature(for: SHA256.hash(data: message)) verifies with isValidSignature(_:for: message): yes
  • Auf Nachrichten angewandter Hash

    curve-matched

    Signaturen über Daten bestanden die Prüfung als Signaturen über SHA-256 bei P-256, SHA-384 bei P-384 und SHA-512 bei P-521.

    Von der Prüfung aufgezeichnet
    fact ecdsa-message-hash = curve-matched
    evidence ecdsa-message-hash: signature(for: data) verifies as a signature over SHA-256 on P-256: yes, SHA-384 on P-384: yes, SHA-512 on P-521: yes
  • An die Kurve gebundener Hash

    none

    Ein P-384-Schlüssel signierte einen SHA-256-Digest, und die Signatur bestand die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ecdsa-hash-curve-binding = none
    evidence ecdsa-hash-curve-binding: a P-384 key signed a SHA-256 digest, and the signature verifies: yes
  • Digest länger als die Kurvenordnung

    truncated

    Ein P-256-Schlüssel signierte einen SHA-512-Digest; openssl pkeyutl prüfte die Signatur erfolgreich gegen die ersten 32 Byte des Digests.

    Von der Prüfung aufgezeichnet
    fact ecdsa-long-input = truncated
    evidence ecdsa-long-input: a P-256 key signed a SHA-512 digest without an error
    evidence ecdsa-long-input: openssl pkeyutl verifies that signature over the first 32 digest bytes: yes
  • Fehlerhafte ECDSA-Signatur

    throws

    init(derRepresentation:) warf truncatedASN1Field bei einer rohen r‖s-Signatur und bei 3 Byte; init(rawRepresentation:) warf incorrectParameterSize bei DER und bei 3 Byte.

    Von der Prüfung aufgezeichnet
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: ECDSASignature(derRepresentation:) with the RFC 6979 A.2.5 signature as raw r||s: threw CryptoKitASN1Error.truncatedASN1Field; with 3 bytes: threw CryptoKitASN1Error.truncatedASN1Field
    evidence ecdsa-malformed-signature: ECDSASignature(rawRepresentation:) with DER: threw CryptoKitError.incorrectParameterSize; with 3 bytes: threw CryptoKitError.incorrectParameterSize
    evidence ecdsa-malformed-signature: isValidSignature for a valid signature over another message: false
  • ECDSA-Kurven

    P-256P-384P-521

    Signieren und Prüfen funktionierten mit P-256, P-384 und P-521; die Quellen deklarieren keine weitere ECDSA-Kurve.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-256, P-384, P-521
    evidence ecdsa-curves: key types declared in Sources/Crypto and Sources/CryptoExtras: Curve25519 MLDSA65 MLDSA87 MLKEM1024 MLKEM768 P256 P384 P521 XWingMLKEM768X25519
  • EdDSA

    Ed25519

    Curve25519.Signing (Ed25519); die Quellen deklarieren keinen Typ für Ed448.

    Von der Prüfung aufgezeichnet
    fact eddsa = Ed25519
  • Deterministische Ed25519-Signaturen

    yes

    Zwei Signaturen derselben Nachricht waren identisch und entsprachen der Signatur aus Test 1 von RFC 8032; eine Signatur von swift-crypto bestand die Prüfung mit OpenSSL.

    Von der Prüfung aufgezeichnet
    fact ed25519-deterministic = yes
    evidence ed25519-deterministic: RFC 8032 test 1: the derived public key matches: yes, the RFC signature verifies: yes
    evidence ed25519-deterministic: swift-crypto's signature with the same key and message equals the RFC signature: yes; two signatures are identical: yes; both verify: yes
    evidence ed25519-deterministic: OpenSSL 3.0.13 pkeyutl on a swift-crypto Ed25519 signature: Signature Verified Successfully
  • Ed25519-Signatur mit nicht kanonischem S

    rejected

    Die Signatur aus Test 1 von RFC 8032 mit S + L anstelle von S wurde abgelehnt.

    Von der Prüfung aufgezeichnet
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: the RFC 8032 test 1 signature with S replaced by S + L
  • Öffentlicher Schlüssel kleiner Ordnung (Ed25519)

    accepted

    Das neutrale Element ließ sich als öffentlicher Schlüssel importieren, und R = neutrales Element mit S = 0 bestand die Prüfung.

    Von der Prüfung aufgezeichnet
    fact ed25519-small-order-key = accepted
    evidence ed25519-small-order-key: the identity point as public key: import accepted; signature R = identity, S = 0 verifies: yes
  • Fehlerhafte Ed25519-Signatur

    rejected

    64 Byte mit dem Wert 0xab und eine 63-Byte-Signatur ergaben false.

    Von der Prüfung aufgezeichnet
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: false; 63 bytes of the RFC signature: false
  • Post-Quanten-Algorithmen

    ML-KEMML-DSAX-Wing

    Kapselung und Entkapselung mit ML-KEM und X-Wing lieferten dasselbe Geheimnis, und ML-DSA-Signaturen bestanden die Prüfung; X-Wing funktionierte auch über HPKE.

    Von der Prüfung aufgezeichnet
    fact pqc = ML-KEM, ML-DSA, X-Wing
    evidence pqc: ML-KEM-768: public key 1184 bytes, ciphertext 1088 bytes, shared secrets match: yes
    evidence pqc: ML-KEM-1024: public key 1568 bytes, ciphertext 1568 bytes, shared secrets match: yes
    evidence pqc: ML-DSA-65: public key 1952 bytes, signature 3309 bytes, verifies: yes; ML-DSA-87: signature 4627 bytes, verifies: yes
    evidence pqc: X-Wing (XWingMLKEM768X25519): public key 1216 bytes, ciphertext 1120 bytes, shared secrets match: yes
    evidence pqc: HPKE with XWingMLKEM768X25519_SHA256_AES_GCM_256 opens its own message: yes
  • ML-KEM-Parametersätze

    ML-KEM-768ML-KEM-1024

    Mit MLKEM768 und MLKEM1024 lieferten Kapselung und Entkapselung dasselbe Geheimnis; die Quellen deklarieren keinen Typ für ML-KEM-512.

    Von der Prüfung aufgezeichnet
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • ML-DSA-Parametersätze

    ML-DSA-65ML-DSA-87

    Signieren und Prüfen funktionierten mit MLDSA65 und MLDSA87; die Quellen deklarieren keinen Typ für ML-DSA-44.

    Von der Prüfung aufgezeichnet
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • ML-DSA-Signaturmodus

    hedged

    Zwei ML-DSA-65-Signaturen derselben Nachricht unterschieden sich (Hedged-Signieren).

    Von der Prüfung aufgezeichnet
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: two ML-DSA-65 signatures of one message are different
  • ML-DSA-Kontext-String

    supported

    Eine Signatur mit dem Kontext invoice-v1 bestand die Prüfung mit diesem Kontext und scheiterte mit invoice-v2 und ohne Kontext.

    Von der Prüfung aufgezeichnet
    fact ml-dsa-context-string = supported
    evidence ml-dsa-context-string: signed with context "invoice-v1": verifies with it yes, with "invoice-v2" no, without a context no
  • Fehlerhafte ML-DSA-Signatur

    returns-false

    Eine gekürzte Signatur, 3 Byte und eine falsche Nachricht ergaben false.

    Von der Prüfung aufgezeichnet
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: isValidSignature: truncated signature false, 3 bytes false, wrong message false
  • Verändertes ML-KEM-Chiffrat

    implicit-rejection

    Mit einem gekippten Bit im Chiffrat lieferte decapsulate einen anderen Schlüssel als den des Absenders.

    Von der Prüfung aufgezeichnet
    fact ml-kem-modified-ciphertext = implicit-rejection
    evidence ml-kem-modified-ciphertext: ML-KEM-768 with one ciphertext bit flipped: decapsulate returned a key that differs from the sender's
  • Private Schlüssel für Post-Quanten-Verfahren

    seed-only

    seedRepresentation umfasst 64 Byte bei ML-KEM-768 und 32 bei ML-DSA-65, und daraus neu aufgebaute Schlüssel stimmten überein.

    Von der Prüfung aufgezeichnet
    fact pq-private-keys = seed-only
    evidence pq-private-keys: ML-KEM-768 seedRepresentation 64 bytes, integrityCheckedRepresentation 96 bytes; ML-DSA-65 seedRepresentation 32 bytes, integrityCheckedRepresentation 64 bytes
    evidence pq-private-keys: keys rebuilt from the seed alone have the same public key: ML-KEM-768 yes, ML-DSA-65 yes
  • Formate öffentlicher Schlüssel

    spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32

    SPKI als PEM und DER, SEC1 unkomprimiert und komprimiert sowie rohes x‖y für P-256, dazu 32 rohe Bytes für Ed25519 – alles geschrieben und wieder eingelesen.

    Von der Prüfung aufgezeichnet
    fact public-key-formats = spki-pem, spki-der, sec1-uncompressed, sec1-compressed, raw-xy, raw-32
    evidence public-key-formats: P-256 public key sizes: raw 64, x963 65, compressed 33, DER 91; PEM header -----BEGIN PUBLIC KEY-----; Ed25519 raw 32
    evidence public-key-formats: written and read back: spki-pem yes, spki-der yes, sec1-uncompressed yes, sec1-compressed yes, raw-xy yes, raw-32 yes
  • Formate privater Schlüssel

    pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32

    Liest PKCS#8 und SEC1, jeweils als PEM oder DER, den rohen P-256-Skalar und den Ed25519-Seed; schreibt PKCS#8; verschlüsseltes PKCS#8 wird abgelehnt.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-pem, pkcs8-der, sec1-pem, sec1-der, raw-scalar, seed-32
    evidence private-key-formats: P-256 private key written as: derRepresentation PKCS#8 (138 bytes), pemRepresentation -----BEGIN PRIVATE KEY-----, rawRepresentation 32 bytes, x963Representation 97 bytes
    evidence private-key-formats: OpenSSL-written P-256 keys read: pkcs8-pem yes, pkcs8-der yes, encrypted-pkcs8 no, sec1-pem yes, sec1-der yes; encrypted PKCS#8: threw CryptoKitASN1Error.invalidPEMDocument

Verwendung

Signiere und prüfe mit Ed25519 und führe unter Linux einen Schlüsselaustausch mit ML-KEM-768 durch.
import Crypto
import Foundation

let message = Data("order #1042 approved".utf8)

// Ed25519 signatures are deterministic here but randomized under Apple's CryptoKit:
// compare signatures by verifying them, never byte for byte.
let signingKey = Curve25519.Signing.PrivateKey()
let signature = try signingKey.signature(for: message)
print("Ed25519 verified: \(signingKey.publicKey.isValidSignature(signature, for: message))")

// ML-KEM-768: the receiver publishes a public key, the sender encapsulates a fresh secret to it.
let receiver = try MLKEM768.PrivateKey()
let sent = try receiver.publicKey.encapsulate()
let received = try receiver.decapsulate(sent.encapsulated)
print("ML-KEM-768 secrets match: \(received == sent.sharedSecret)")

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image swift:6.4.1-noble aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Befehl
swift run -c release usage
Ausgabe muss passen (regulärer Ausdruck)
Ed25519 verified: true\nML-KEM-768 secrets match: true

Bibliotheken