Zum Inhalt springen
ecdsa.com
Swift

Apple CryptoKit

Apple CryptoKit, das in iOS und macOS integrierte Krypto-Framework für Swift: ECDSA-Signaturen, die zugleich roh und in DER vorliegen, randomisierte Ed25519-Signaturen, ML-KEM, ML-DSA und X-Wing seit Version 26 der Betriebssysteme sowie Schlüssel in der Secure Enclave, die die API nur als opake Handles herausgibt.
Lizenz
Proprietary (part of Apple's operating systems)
Geprüfte Version
macOS 27.0.1 ·
Quellcode
developer.apple.com/documentation/cryptokit
Dokumentation
developer.apple.com/documentation/cryptokit

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-32hardware-handle

CryptoKit ist das Framework, das Swift-Apps auf Apple-Plattformen für Hashing, Signaturen, Schlüsseleinigung, AEAD und HPKE verwenden. Es wird mit dem Betriebssystem ausgeliefert, seine Version ist also die des Betriebssystems; das SDK weist P-256 ab iOS 13 und macOS 10.15 als verfügbar aus, ML-KEM, ML-DSA und X-Wing ab iOS 26 und macOS 26. Die Fakten auf dieser Seite wurden unter macOS 27.0.1 mit Swift 6.4 auf einem Mac mit Apple Silicon geprüft, und beide Beispiele wurden dort kompiliert und ausgeführt; iOS wurde nicht geprüft. Unter Linux stellt swift-crypto dieselbe API bereit.

Eine P-256-Signatur ist ein Wert vom Typ ECDSASignature mit zwei Formen: rawRepresentation, das 64 Byte lange r‖s, das JWS (ES256) und WebCrypto verwenden, und derRepresentation, die DER-Form, die X.509, OpenSSL und Java erwarten. Die falsche Form zu senden ist ein häufiger Interoperabilitätsfehler; das Beispiel konvertiert explizit, und der Konverter DER ⇄ Raw zeigt beide. Das Parsen ist streng und wirft Fehler: Rohe Bytes in init(derRepresentation:) lösen einen CryptoKitASN1Error aus – truncatedASN1Field bei der Testsignatur aus RFC 6979, wobei der konkrete Fall von den Bytes abhängt –, und DER in init(rawRepresentation:) löst incorrectParameterSize aus, während isValidSignature einfach false zurückgibt.

Die Nonces sind zufällig – zwei Signaturen derselben Nachricht unterschieden sich, und der Testschlüssel aus RFC 6979 ergab nicht das r aus dem RFC –, und s wird nicht normalisiert: High-S-Werte kommen vor, und eine Signatur, in der s durch n − s ersetzt war, bestand die Prüfung. signature(for: data) hasht mit dem zur Kurve passenden Hash (SHA-256, SHA-384, SHA-512), signature(for: digest) akzeptiert dagegen jeden Digest-Typ: Ein SHA-256-Digest ließ sich mit einem P-384-Schlüssel signieren und prüfen, und ein SHA-512-Digest wurde bei P-256 auf seine ersten 32 Byte gekürzt, was Security.framework bestätigte. Übergib beim Pre-Hashing selbst den passenden Digest.

Ed25519-Signaturen (Curve25519.Signing) sind randomisiert: Mit dem Testschlüssel aus RFC 8032 unterschied sich die Signatur, die CryptoKit für die Testnachricht erzeugte, von der aus dem RFC und von der eigenen vorherigen, dennoch bestanden beide die Prüfung, auch mit Node.js. Tests, die Signaturbytes mit den Vektoren aus RFC 8032 vergleichen, schlagen mit CryptoKit unter macOS deshalb fehl; prüfe stattdessen die Signaturen. Nicht kanonische S-Werte werden abgelehnt, aber das neutrale Element wurde als öffentlicher Schlüssel akzeptiert, und eine Signatur mit R = neutrales Element und S = 0 bestand die Prüfung für eine beliebige Nachricht. Lehne Schlüssel kleiner Ordnung selbst ab, wenn eine Signatur an einen bestimmten Signierer gebunden sein muss. Ed25519-Schlüssel gibt es nur als 32 rohe Bytes; eine PEM- oder DER-Form existiert für sie nicht.

Die Post-Quanten-Typen sind ML-KEM-768 und ML-KEM-1024, ML-DSA-65 und ML-DSA-87 – ML-KEM-512, ML-DSA-44 und SLH-DSA fehlen – sowie das hybride X-Wing-KEM (XWingMLKEM768X25519), das auch als HPKE-Cipher-Suite angeboten wird. ML-DSA signiert im Hedged-Modus und akzeptiert einen Kontext-String nach FIPS 204, den der Verifizierer wiederholen muss. Die ML-KEM-Entkapselung eines beschädigten Chiffrats gibt einen anderen Schlüssel zurück, statt einen Fehler zu werfen. Private Schlüssel werden nur als Seeds gespeichert – 64 Byte für ML-KEM, 32 für ML-DSA – oder als Seed mit Integritätsprüfung; ein expandierter privater Schlüssel, den eine andere Bibliothek exportiert hat, lässt sich daher nicht importieren. Probiere die Verfahren im ML-KEM-Tool und im Tool für Post-Quanten-Signaturen aus.

SecureEnclave.P256.Signing.PrivateKey() erzeugt einen Schlüssel in der Secure Enclave, und auf diesem Mac taten das auch die ML-KEM- und ML-DSA-Typen unter SecureEnclave. Diese Typen geben keinen privaten Schlüssel in roher Form, als DER oder PEM heraus – nur dataRepresentation, ein Handle, mit dem sich der Schlüssel wieder öffnen ließ –, während der öffentliche Schlüssel ein gewöhnlicher P256.Signing.PublicKey oder MLDSA65.PublicKey ist. Software-Schlüssel für P-256 lassen sich als PKCS#8 exportieren (derRepresentation, pemRepresentation) und aus PKCS#8 und SEC1 importieren, jeweils als PEM oder DER, nicht aber aus verschlüsseltem PKCS#8; JWK unterstützt CryptoKit nicht.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/apple-cryptokit/run.sh hat Apple CryptoKit macOS 27.0.1 am 20. Oktober 2026 auf macOS 27.0.1 (Swift 6.4) 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; Security.framework 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; Security.framework verifies the signature over the digest's first 32 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; das SDK deklariert keine weitere ECDSA-Kurve.

    Von der Prüfung aufgezeichnet
    fact ecdsa-curves = P-256, P-384, P-521
    evidence ecdsa-curves: SDK 27.0 CryptoKit.swiftinterface top-level key types: Curve25519 MLDSA65 MLDSA87 MLKEM1024 MLKEM768 P256 P384 P521 XWingMLKEM768X25519
  • EdDSA

    Ed25519

    Curve25519.Signing (Ed25519); das SDK deklariert keinen Typ für Ed448.

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

    no

    Zwei Signaturen derselben Nachricht mit demselben Schlüssel unterschieden sich; beide bestanden die Prüfung, auch mit node:crypto.

    Von der Prüfung aufgezeichnet
    fact ed25519-deterministic = no
    evidence ed25519-deterministic: RFC 8032 test 1: the derived public key matches: yes, the RFC signature verifies: yes
    evidence ed25519-deterministic: CryptoKit's signature with the same key and message equals the RFC signature: no; two CryptoKit signatures are identical: no; both verify: yes
    evidence ed25519-deterministic: node:crypto (OpenSSL) verifies the CryptoKit signature: yes
  • 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; das SDK deklariert 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; das SDK deklariert 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; der einzige andere Initialisierer für private Schlüssel nimmt den Seed mit Integritätsprüfung entgegen.

    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
    evidence pq-private-keys: MLKEM768.PrivateKey initializers in the SDK: init() init<D>(integrityCheckedRepresentation: init<D>(seedRepresentation:
    evidence pq-private-keys: MLDSA65.PrivateKey initializers in the SDK: init() init<D>(integrityCheckedRepresentation: init<D>(seedRepresentation:
  • Algorithmen der Secure Enclave

    P-256ML-KEM-768ML-KEM-1024ML-DSA-65ML-DSA-87

    Auf diesem Mac wurden Schlüssel jedes Typs in der Secure Enclave erzeugt und zum Signieren, Prüfen oder Entkapseln verwendet.

    Von der Prüfung aufgezeichnet
    fact secure-enclave-algorithms = P-256, ML-KEM-768, ML-KEM-1024, ML-DSA-65, ML-DSA-87
    evidence secure-enclave-algorithms: SecureEnclave.isAvailable: true
    evidence secure-enclave-algorithms: P-256: signature verifies with the public key: yes, two signatures differ: yes, a key re-created from dataRepresentation signs for the same public key: yes
    evidence secure-enclave-algorithms: SecureEnclave key types in the SDK: MLDSA65 MLDSA87 MLKEM1024 MLKEM768 P256
  • 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
    evidence public-key-formats: key representations declared on the Curve25519 types: rawRepresentation; lines mentioning JWK in the SDK interface: 0
  • Formate privater Schlüssel

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

    Liest PKCS#8 und SEC1, jeweils als PEM oder DER, den rohen P-256-Skalar und den Ed25519-Seed; schreibt PKCS#8; Schlüssel in der Secure Enclave sind Handles.

    Von der Prüfung aufgezeichnet
    fact private-key-formats = pkcs8-pem, pkcs8-der, sec1-pem, sec1-der, raw-scalar, seed-32, hardware-handle
    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
    evidence private-key-formats: key representations declared inside the SecureEnclave types: dataRepresentation

Verwendung

Signiere mit einem P-256-Schlüssel, nimm die rohe Form für JOSE und die DER-Form für X.509-Gegenstellen und prüfe eine aus DER neu aufgebaute Signatur.
import CryptoKit
import Foundation

let key = P256.Signing.PrivateKey()
let message = Data("order #1042 approved".utf8)
let signature = try key.signature(for: message) // SHA-256 for P-256, random nonce

// JWS (ES256) and WebCrypto use the 64-byte r||s form; X.509, OpenSSL and Java use DER.
let jose = signature.rawRepresentation
let der = signature.derRepresentation

// Rebuild the signature from the form that arrived, then verify it.
let received = try P256.Signing.ECDSASignature(derRepresentation: der)
let valid = key.publicKey.isValidSignature(received, for: message)
print("raw bytes: \(jose.count), DER tag: 0x\(String(der[0], radix: 16)), verified: \(valid)")
// Other platforms import the public key as SPKI PEM.
print(key.publicKey.pemRepresentation.split(separator: "\n")[0])
Erzeuge einen P-256-Schlüssel in der Secure Enclave, öffne ihn über sein Handle erneut, signiere und prüfe mit dem gewöhnlichen öffentlichen Schlüssel.
import CryptoKit
import Foundation

guard SecureEnclave.isAvailable else { fatalError("this device has no Secure Enclave") }
// The private key is generated inside the Secure Enclave; dataRepresentation is
// an opaque handle to store and reopen it, not the key itself.
let key = try SecureEnclave.P256.Signing.PrivateKey()
let reopened = try SecureEnclave.P256.Signing.PrivateKey(dataRepresentation: key.dataRepresentation)

let message = Data("order #1042 approved".utf8)
let signature = try reopened.signature(for: message)
// Verifiers need only the ordinary P-256 public key.
let verifier = try P256.Signing.PublicKey(derRepresentation: key.publicKey.derRepresentation)
print("Secure Enclave signature verified: \(verifier.isValidSignature(signature, for: message))")

Für diese Plattform gibt es kein Container-Image; die Beispiele wurden am 20. Oktober 2026 auf macOS 27.0.1 (Swift 6.4) ausgeführt.

Bibliotheken