Aller au contenu
ecdsa.com
Swift

Apple CryptoKit

Apple CryptoKit, le framework cryptographique Swift intégré à iOS et macOS : des signatures ECDSA à la fois brutes et DER, des signatures Ed25519 aléatoires, ML-KEM, ML-DSA et X-Wing depuis les versions 26, et des clés Secure Enclave que l’API n’expose que sous forme de références opaques.
Licence
Proprietary (part of Apple's operating systems)
Version vérifiée
macOS 27.0.1 ·
Code source
developer.apple.com/documentation/cryptokit
Documentation
developer.apple.com/documentation/cryptokit

Compatibilité en un coup d’œil

ECDSAEdDSAPost-quantique

Courbes ECDSA
P-256P-384P-521
EdDSA
Ed25519
Algorithmes post-quantiques
ML-KEMML-DSAX-Wing
Formats de clé publique
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32
Formats de clé privée
pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32hardware-handle

CryptoKit est le framework qu’utilisent les applications Swift des plateformes Apple pour le hachage, les signatures, l’accord de clé, l’AEAD et HPKE. Il est livré avec le système d’exploitation, si bien que sa version est celle de l’OS ; le SDK indique P-256 comme disponible à partir d’iOS 13 et de macOS 10.15, et ML-KEM, ML-DSA et X-Wing à partir d’iOS 26 et de macOS 26. Les faits présentés ici ont été vérifiés sous macOS 27.0.1 avec Swift 6.4 sur un Mac avec puce Apple, où les deux exemples ont été compilés et exécutés ; iOS n’a pas été vérifié. Sous Linux, la même API est fournie par swift-crypto.

Une signature P-256 est une valeur ECDSASignature qui a deux formes : rawRepresentation, le r‖s de 64 octets qu’utilisent JWS (ES256) et WebCrypto, et derRepresentation, la forme DER qu’attendent X.509, OpenSSL et Java. Envoyer la mauvaise forme est un bug d’interopérabilité courant ; l’exemple convertit explicitement, et le convertisseur DER ⇄ brut affiche les deux. L’analyse est stricte et lève des erreurs : des octets bruts passés à init(derRepresentation:) lèvent une CryptoKitASN1Error (truncatedASN1Field pour la signature de test de la RFC 6979, mais le cas dépend des octets), et du DER passé à init(rawRepresentation:) lève incorrectParameterSize, tandis que isValidSignature renvoie simplement false.

Les nonces sont aléatoires (deux signatures d’un même message différaient, et la clé de test de la RFC 6979 n’a pas produit le r de la RFC), et s n’est pas normalisé : des valeurs high-S apparaissent, et une signature dont s a été remplacé par n − s a été vérifiée. signature(for: data) hache avec la fonction de hachage qui correspond à la courbe (SHA-256, SHA-384, SHA-512), mais signature(for: digest) accepte n’importe quel type de condensat : un condensat SHA-256 a été signé et vérifié avec une clé P-384, et un condensat SHA-512 sur P-256 a été tronqué à ses 32 premiers octets, ce que Security.framework a confirmé. En cas de pré-hachage, passez vous-même le condensat correspondant.

Les signatures Ed25519 (Curve25519.Signing) sont aléatoires : avec la clé de test de la RFC 8032, la signature du message de test produite par CryptoKit différait de celle de la RFC et de sa propre signature précédente, et pourtant les deux ont été vérifiées, y compris avec Node.js. Les tests qui comparent les octets de signature aux vecteurs de la RFC 8032 échouent donc avec CryptoKit sous macOS ; vérifiez plutôt les signatures. Les valeurs S non canoniques sont rejetées, mais l’élément neutre a été accepté comme clé publique, et une signature avec R = élément neutre et S = 0 a été vérifiée pour un message arbitraire. Rejetez vous-même les clés de petit ordre lorsqu’une signature doit être liée à un signataire précis. Les clés Ed25519 n’existent que sous forme de 32 octets bruts ; il n’en existe aucune forme PEM ou DER.

Les types post-quantiques sont ML-KEM-768 et ML-KEM-1024, ML-DSA-65 et ML-DSA-87 (il n’y a ni ML-KEM-512, ni ML-DSA-44, ni SLH-DSA), ainsi que le KEM hybride X-Wing (XWingMLKEM768X25519), également proposé comme suite de chiffrement HPKE. La signature ML-DSA est en mode hedged (avec aléa) et accepte une chaîne de contexte FIPS 204, que le vérificateur doit reprendre à l’identique. La décapsulation ML-KEM d’un texte chiffré endommagé renvoie une clé différente au lieu de lever une erreur. Les clés privées ne sont stockées que sous forme de graines (64 octets pour ML-KEM, 32 pour ML-DSA) ou de graine accompagnée d’un contrôle d’intégrité : une clé privée développée exportée par une autre bibliothèque ne peut donc pas être importée. Essayez ces schémas dans l’outil ML-KEM et l’outil de signatures post-quantiques.

SecureEnclave.P256.Signing.PrivateKey() crée une clé à l’intérieur de la Secure Enclave, et sur ce Mac, les types ML-KEM et ML-DSA placés sous SecureEnclave l’ont fait aussi. Ces types n’exposent aucune clé privée brute, DER ou PEM (seulement dataRepresentation, une référence qui a permis de rouvrir la clé), tandis que la clé publique est une P256.Signing.PublicKey ou une MLDSA65.PublicKey ordinaire. Les clés P-256 logicielles s’exportent en PKCS#8 (derRepresentation, pemRepresentation) et s’importent depuis PKCS#8 et SEC1, en PEM ou en DER, mais pas depuis du PKCS#8 chiffré, et CryptoKit ne prend pas en charge JWK.

Les faits et les vérifications qui les fondent

Rien de ce qui suit n’est copié d’une documentation. La vérification content/libraries/verify/apple-cryptokit/run.sh a exécuté Apple CryptoKit macOS 27.0.1 sur macOS 27.0.1 (Swift 6.4) le 20 octobre 2026 ; la page doit correspondre à ce que la vérification a affiché, et chaque fait cite les lignes en question.

  • Encodage des signatures ECDSA

    structured

    ECDSASignature a une rawRepresentation de 64 octets et une derRepresentation ; ses octets (withUnsafeBytes) sont la forme brute.

    Relevé par la vérification
    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
  • Nonce ECDSA

    random

    Deux signatures d’un même message différaient, et la clé et le message de l’annexe A.2.5 de la RFC 6979 n’ont pas produit le r de la RFC.

    Relevé par la vérification
    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 à la signature

    either

    s n’est pas normalisé : des valeurs high-S sont apparues parmi 64 signatures.

    Relevé par la vérification
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • High-S à la vérification

    accepts-high-s

    Une signature dont s a été remplacé par n − s a été vérifiée.

    Relevé par la vérification
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: isValidSignature with s replaced by n - s: true
  • Ce que l’appel de signature hache

    hashes-message-or-digest

    signature(for: data) hache les données ; la signature produite par signature(for: SHA256.hash(data: message)) a été vérifiée sur le message.

    Relevé par la vérification
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: signature(for: SHA256.hash(data: message)) verifies with isValidSignature(_:for: message): yes
  • Hachage appliqué aux messages

    curve-matched

    Les signatures sur des données ont été vérifiées comme des signatures du SHA-256 sur P-256, du SHA-384 sur P-384 et du SHA-512 sur P-521.

    Relevé par la vérification
    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
  • Hachage lié à la courbe

    none

    Une clé P-384 a signé un condensat SHA-256, et la signature a été vérifiée.

    Relevé par la vérification
    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
  • Condensat plus long que l’ordre de la courbe

    truncated

    Une clé P-256 a signé un condensat SHA-512 ; Security.framework a vérifié la signature sur les 32 premiers octets du condensat.

    Relevé par la vérification
    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
  • Signature ECDSA mal formée

    throws

    init(derRepresentation:) a levé truncatedASN1Field pour une signature r‖s brute et pour 3 octets ; init(rawRepresentation:) a levé incorrectParameterSize pour du DER et pour 3 octets.

    Relevé par la vérification
    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
  • Courbes ECDSA

    P-256P-384P-521

    Signature et vérification réussies avec P-256, P-384 et P-521 ; le SDK ne déclare aucune autre courbe ECDSA.

    Relevé par la vérification
    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) ; le SDK ne déclare aucun type Ed448.

    Relevé par la vérification
    fact eddsa = Ed25519
  • Signatures Ed25519 déterministes

    no

    Deux signatures d’un même message avec une même clé différaient ; les deux ont été vérifiées, y compris avec node:crypto.

    Relevé par la vérification
    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
  • Signature Ed25519 avec S non canonique

    rejected

    La signature du test 1 de la RFC 8032, avec S remplacé par S + L, a été rejetée.

    Relevé par la vérification
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: the RFC 8032 test 1 signature with S replaced by S + L
  • Clé publique Ed25519 de petit ordre

    accepted

    L’élément neutre s’est importé comme clé publique, et la signature R = élément neutre, S = 0 a été vérifiée.

    Relevé par la vérification
    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
  • Signature Ed25519 mal formée

    rejected

    64 octets à 0xab et une signature de 63 octets ont renvoyé false.

    Relevé par la vérification
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: false; 63 bytes of the RFC signature: false
  • Algorithmes post-quantiques

    ML-KEMML-DSAX-Wing

    Les encapsulations ML-KEM et X-Wing ont fonctionné en aller-retour et les signatures ML-DSA ont été vérifiées ; X-Wing a aussi fonctionné via HPKE.

    Relevé par la vérification
    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
  • Jeux de paramètres ML-KEM

    ML-KEM-768ML-KEM-1024

    MLKEM768 et MLKEM1024 ont fonctionné en aller-retour ; le SDK ne déclare aucun type ML-KEM-512.

    Relevé par la vérification
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • Jeux de paramètres ML-DSA

    ML-DSA-65ML-DSA-87

    Signature et vérification réussies avec MLDSA65 et MLDSA87 ; le SDK ne déclare aucun type ML-DSA-44.

    Relevé par la vérification
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • Mode de signature ML-DSA

    hedged

    Deux signatures ML-DSA-65 d’un même message différaient (signature en mode hedged).

    Relevé par la vérification
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: two ML-DSA-65 signatures of one message are different
  • Chaîne de contexte ML-DSA

    supported

    Une signature avec le contexte invoice-v1 a été vérifiée avec ce contexte et a échoué avec invoice-v2 et sans contexte.

    Relevé par la vérification
    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
  • Signature ML-DSA mal formée

    returns-false

    Une signature tronquée, 3 octets et un message erroné ont renvoyé false.

    Relevé par la vérification
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: isValidSignature: truncated signature false, 3 bytes false, wrong message false
  • Texte chiffré ML-KEM modifié

    implicit-rejection

    Avec un bit du texte chiffré inversé, decapsulate a renvoyé une clé différente de celle de l’expéditeur.

    Relevé par la vérification
    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
  • Clés privées post-quantiques

    seed-only

    seedRepresentation fait 64 octets pour ML-KEM-768 et 32 pour ML-DSA-65, et les clés reconstruites à partir de cette graine concordaient ; le seul autre initialiseur de clé privée prend la graine accompagnée de son contrôle d’intégrité.

    Relevé par la vérification
    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:
  • Algorithmes de la Secure Enclave

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

    Des clés de chaque type ont été générées dans la Secure Enclave, puis ont servi à signer, vérifier ou décapsuler sur ce Mac.

    Relevé par la vérification
    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
  • Formats de clé publique

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

    SPKI PEM et DER, SEC1 non compressé et compressé, et x‖y brut pour P-256, plus 32 octets bruts pour Ed25519, tous écrits puis relus.

    Relevé par la vérification
    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
  • Formats de clé privée

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

    Lit PKCS#8 et SEC1, en PEM ou en DER, le scalaire P-256 brut et la graine Ed25519 ; écrit du PKCS#8 ; les clés Secure Enclave sont des références (handles).

    Relevé par la vérification
    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

Utilisation

Signer avec une clé P-256, prendre la forme brute pour JOSE et la forme DER pour les pairs X.509, puis vérifier une signature reconstruite à partir du DER.
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])
Créer une clé P-256 dans la Secure Enclave, la rouvrir à partir de sa référence, signer, puis vérifier avec la clé publique ordinaire.
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))")

Aucune image de conteneur ne fournit cette plateforme ; les exemples ont été exécutés sur macOS 27.0.1 (Swift 6.4) le 20 octobre 2026.

Bibliothèques