Aller au contenu
ecdsa.com
Swift

swift-crypto

swift-crypto, l’API CryptoKit open source d’Apple pour Linux et les autres plateformes dépourvues de CryptoKit : les mêmes types Swift sur une copie embarquée de BoringSSL, avec un Ed25519 déterministe là où CryptoKit sur macOS le rend aléatoire, et sans Secure Enclave.
Licence
Apache-2.0
Version vérifiée
5.0.3 ·
Code source
github.com/apple/swift-crypto
Documentation
swiftpackageindex.com/apple/swift-crypto/documentation/crypto

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

swift-crypto permet au code Swift côté serveur et multiplateforme d’utiliser l’API de CryptoKit via import Crypto. Son README la présente comme une implémentation d’une part substantielle de cette API pour Linux et Windows sur ARM64 ; sur les plateformes Apple, le module réexporte CryptoKit lui-même (#if canImport(CryptoKit)), si bien que les faits présentés ici décrivent les autres plateformes. Ils ont été vérifiés avec swift-crypto 5.0.3 et swift-asn1 1.7.3 sous Linux, dans l’image officielle Swift 6.4.1, où le code s’exécute sur une copie embarquée de BoringSSL.

Un même code source peut se comporter différemment sur les deux plateformes. Les signatures Ed25519 sont ici déterministes (la clé de test de la RFC 8032 a reproduit exactement la signature de la RFC), alors que CryptoKit sur macOS les rend aléatoires ; les deux variantes ont été vérifiées avec OpenSSL, si bien que les tests devraient vérifier les signatures plutôt que de comparer des octets. Les erreurs d’analyse concordaient : les mêmes octets r‖s bruts passés à ECDSASignature(derRepresentation:) ont levé truncatedASN1Field sur les deux plateformes, mais le cas de CryptoKitASN1Error qui apparaît dépend des octets d’entrée. Testez donc l’échec lui-même, pas un cas d’erreur particulier.

Pour tout le reste, les mesures concordaient avec CryptoKit. Les signatures ECDSA exposent rawRepresentation (r‖s de 64 octets, comme dans JWS) et derRepresentation (comme dans X.509) ; convertissez avec le convertisseur DER ⇄ brut. Les nonces sont aléatoires et s n’est pas normalisé ; les signatures high-S passent la vérification. signature(for: data) hache avec la fonction de hachage de la courbe, mais l’API qui signe un condensat a accepté SHA-256 sur une clé P-384 et tronqué un condensat SHA-512 sur P-256, ce qu’OpenSSL a confirmé. Pour Ed25519, un S non canonique est rejeté, tandis que l’élément neutre est accepté comme clé publique, de même qu’une signature avec R = élément neutre et S = 0.

Les types post-quantiques correspondent à ceux de CryptoKit 26 : ML-KEM-768 et ML-KEM-1024, ML-DSA-65 et ML-DSA-87, ainsi que le KEM hybride X-Wing, également sous forme de suite de chiffrement HPKE. La signature ML-DSA est en mode hedged (avec aléa) et prend en charge les chaînes de contexte de FIPS 204, la décapsulation ML-KEM d’un texte chiffré endommagé renvoie une clé différente sans erreur, et les clés privées sont conservées sous forme de graines (64 octets pour ML-KEM, 32 pour ML-DSA). Comparez avec l’outil ML-KEM et l’outil de signatures post-quantiques.

Ces sources ne contiennent pas de SecureEnclave : le code qui utilise des clés Secure Enclave doit donc être compilé uniquement pour les plateformes Apple. Les formats de clés correspondent à ceux de CryptoKit : les clés privées P-256 s’écrivent en PKCS#8 et se lisent depuis PKCS#8 ou SEC1, en PEM ou en DER, mais pas depuis du PKCS#8 chiffré ; les clés Ed25519 font 32 octets bruts.

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/swift-crypto/run.sh a exécuté swift-crypto 5.0.3 dans l’image de conteneur swift:6.4.1-noble 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 ; openssl pkeyutl 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 without an error
    evidence ecdsa-long-input: openssl pkeyutl verifies that signature over the first 32 digest 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 ; les sources ne déclarent aucune autre courbe ECDSA.

    Relevé par la vérification
    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) ; les sources ne déclarent aucun type Ed448.

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

    yes

    Deux signatures d’un même message étaient identiques et égales à la signature du test 1 de la RFC 8032 ; OpenSSL a vérifié une signature produite par swift-crypto.

    Relevé par la vérification
    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
  • 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 ; les sources ne déclarent 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 ; les sources ne déclarent 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.

    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
  • 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
  • Formats de clé privée

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

    Lit PKCS#8 et SEC1, en PEM ou en DER, le scalaire P-256 brut et la graine Ed25519 ; écrit du PKCS#8 ; le PKCS#8 chiffré est refusé.

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

Utilisation

Signer et vérifier avec Ed25519, puis effectuer un échange de clés ML-KEM-768 sous Linux.
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)")

Le banc de test des extraits de code exécute chaque exemple tel quel dans l’image de conteneur swift:6.4.1-noble, et l’exécution échoue si la sortie ne correspond pas.

Commande
swift run -c release usage
La sortie doit correspondre à cette expression régulière
Ed25519 verified: true\nML-KEM-768 secrets match: true

Bibliothèques