Saltar al contenido
ecdsa.com
Swift

swift-crypto

swift-crypto, la API de CryptoKit de código abierto de Apple para Linux y otras plataformas sin CryptoKit: los mismos tipos de Swift sobre una copia incluida de BoringSSL, con Ed25519 determinista, mientras que CryptoKit en macOS lo aleatoriza, y sin Secure Enclave.
Licencia
Apache-2.0
Versión comprobada
5.0.3 ·
Código fuente
github.com/apple/swift-crypto
Documentación
swiftpackageindex.com/apple/swift-crypto/documentation/crypto

Compatibilidad de un vistazo

ECDSAEdDSACriptografía poscuántica

Curvas ECDSA
P-256P-384P-521
EdDSA
Ed25519
Algoritmos poscuánticos
ML-KEMML-DSAX-Wing
Formatos de clave pública
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32
Formatos de clave privada
pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32

swift-crypto permite que el código Swift del lado del servidor y multiplataforma use la API de CryptoKit mediante import Crypto. Su README la describe como una implementación de una parte sustancial de esa API para Linux y Windows en ARM64; en las plataformas de Apple, el módulo reexporta el propio CryptoKit (#if canImport(CryptoKit)), así que los datos de esta página describen las demás plataformas. Se comprobaron con swift-crypto 5.0.3 y swift-asn1 1.7.3 en Linux, en la imagen oficial de Swift 6.4.1, donde el código se ejecuta sobre una copia incluida (vendored) de BoringSSL.

El mismo código fuente puede comportarse de forma distinta en las dos plataformas. Aquí las firmas Ed25519 son deterministas —la clave de prueba del RFC 8032 reprodujo exactamente la firma del RFC—, mientras que CryptoKit en macOS las aleatoriza; ambos tipos de firma se verificaron con OpenSSL, así que las pruebas deben verificar las firmas en lugar de comparar bytes. Los errores de análisis coincidieron: los mismos bytes r‖s en bruto pasados a ECDSASignature(derRepresentation:) lanzaron truncatedASN1Field en ambas plataformas, pero el caso de CryptoKitASN1Error que aparece depende de los bytes de entrada, así que comprueba que se produce un fallo, no un caso de error concreto.

Todo lo demás que se midió coincidió con CryptoKit. Las firmas ECDSA exponen rawRepresentation (r‖s de 64 bytes, como en JWS) y derRepresentation (como en X.509); convierte entre ellas con el conversor DER ⇄ en bruto. Los nonces son aleatorios y s no se normaliza; las firmas high-S se verifican. signature(for: data) calcula el hash con la función hash de la curva, pero la API de resúmenes aceptó SHA-256 con una clave P-384 y truncó un resumen SHA-512 con P-256, lo que confirmó OpenSSL. En Ed25519, se rechaza una S no canónica, mientras que el punto identidad se acepta como clave pública, junto con una firma R = identidad, S = 0.

Los tipos poscuánticos coinciden con los de CryptoKit 26: ML-KEM-768 y ML-KEM-1024, ML-DSA-65 y ML-DSA-87, y el KEM híbrido X-Wing, también como conjunto de cifrado de HPKE. La firma ML-DSA es hedged (reforzada con aleatoriedad) y admite cadenas de contexto de FIPS 204, la desencapsulación ML-KEM de un texto cifrado dañado devuelve una clave distinta sin ningún error, y las claves privadas se guardan como semillas (64 bytes para ML-KEM, 32 para ML-DSA). Compara con la herramienta de ML-KEM y la herramienta de firmas poscuánticas.

En este código fuente no existe SecureEnclave, así que el código que usa claves del Secure Enclave debe compilarse solo para las plataformas de Apple. Los formatos de clave coinciden con CryptoKit: las claves privadas P-256 se escriben como PKCS#8 y se leen desde PKCS#8 o SEC1, en PEM o DER, pero no desde PKCS#8 cifrado; las claves Ed25519 son 32 bytes en bruto.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/swift-crypto/run.sh ejecutó swift-crypto 5.0.3 en la imagen de contenedor swift:6.4.1-noble el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Codificación de la firma ECDSA

    structured

    ECDSASignature tiene un rawRepresentation de 64 bytes y un derRepresentation; sus bytes (withUnsafeBytes) son la forma en bruto.

    Registrado por la comprobación
    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 de ECDSA

    random

    Dos firmas de un mismo mensaje fueron distintas, y la clave y el mensaje de A.2.5 del RFC 6979 no produjeron la r del RFC.

    Registrado por la comprobación
    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 al firmar

    either

    s no se normaliza: aparecieron valores high-S entre 64 firmas.

    Registrado por la comprobación
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • High-S al verificar

    accepts-high-s

    Se verificó una firma con s sustituido por n − s.

    Registrado por la comprobación
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: isValidSignature with s replaced by n - s: true
  • De qué calcula el hash la llamada de firma

    hashes-message-or-digest

    signature(for: data) calcula el hash; signature(for: SHA256.hash(data: message)) se verificó contra el mensaje.

    Registrado por la comprobación
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: signature(for: SHA256.hash(data: message)) verifies with isValidSignature(_:for: message): yes
  • Hash aplicado a los mensajes

    curve-matched

    Las firmas sobre datos se verificaron como firmas sobre SHA-256 con P-256, SHA-384 con P-384 y SHA-512 con P-521.

    Registrado por la comprobación
    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
  • Hash vinculado a la curva

    none

    Una clave P-384 firmó un resumen SHA-256, y la firma se verificó.

    Registrado por la comprobación
    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
  • Resumen más largo que el orden de la curva

    truncated

    Una clave P-256 firmó un resumen SHA-512; openssl pkeyutl verificó la firma sobre los primeros 32 bytes del resumen.

    Registrado por la comprobación
    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
  • Firma ECDSA mal formada

    throws

    init(derRepresentation:) lanzó truncatedASN1Field con una firma r‖s en bruto y con 3 bytes; init(rawRepresentation:) lanzó incorrectParameterSize con DER y con 3 bytes.

    Registrado por la comprobación
    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
  • Curvas ECDSA

    P-256P-384P-521

    Con P-256, P-384 y P-521 se firmó y se verificó; el código fuente no declara ninguna otra curva ECDSA.

    Registrado por la comprobación
    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); el código fuente no declara ningún tipo Ed448.

    Registrado por la comprobación
    fact eddsa = Ed25519
  • Firmas Ed25519 deterministas

    yes

    Dos firmas de un mismo mensaje fueron idénticas e iguales a la firma de la prueba 1 del RFC 8032; OpenSSL verificó una firma de swift-crypto.

    Registrado por la comprobación
    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
  • Firma Ed25519 con S no canónico

    rejected

    Se rechazó la firma de la prueba 1 del RFC 8032 con S sustituido por S + L.

    Registrado por la comprobación
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: the RFC 8032 test 1 signature with S replaced by S + L
  • Clave pública Ed25519 de orden pequeño

    accepted

    El punto identidad se importó como clave pública, y una firma con R = identidad y S = 0 se verificó.

    Registrado por la comprobación
    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
  • Firma Ed25519 mal formada

    rejected

    64 bytes de 0xab y una firma de 63 bytes devolvieron false.

    Registrado por la comprobación
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: false; 63 bytes of the RFC signature: false
  • Algoritmos poscuánticos

    ML-KEMML-DSAX-Wing

    Las encapsulaciones ML-KEM y X-Wing superaron la prueba de ida y vuelta y las firmas ML-DSA se verificaron; X-Wing también funcionó a través de HPKE.

    Registrado por la comprobación
    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
  • Conjuntos de parámetros de ML-KEM

    ML-KEM-768ML-KEM-1024

    MLKEM768 y MLKEM1024 superaron la prueba de ida y vuelta; el código fuente no declara ningún tipo ML-KEM-512.

    Registrado por la comprobación
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • Conjuntos de parámetros de ML-DSA

    ML-DSA-65ML-DSA-87

    Con MLDSA65 y MLDSA87 se firmó y se verificó; el código fuente no declara ningún tipo ML-DSA-44.

    Registrado por la comprobación
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • Modo de firma de ML-DSA

    hedged

    Dos firmas ML-DSA-65 de un mismo mensaje fueron distintas (firma hedged).

    Registrado por la comprobación
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: two ML-DSA-65 signatures of one message are different
  • Cadena de contexto de ML-DSA

    supported

    Una firma con el contexto invoice-v1 se verificó con él y falló con invoice-v2 y sin contexto.

    Registrado por la comprobación
    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
  • Firma ML-DSA mal formada

    returns-false

    Una firma truncada, 3 bytes y un mensaje incorrecto devolvieron false.

    Registrado por la comprobación
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: isValidSignature: truncated signature false, 3 bytes false, wrong message false
  • Texto cifrado de ML-KEM modificado

    implicit-rejection

    Con un bit del texto cifrado invertido, decapsulate devolvió una clave distinta de la del remitente.

    Registrado por la comprobación
    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
  • Claves privadas poscuánticas

    seed-only

    seedRepresentation ocupa 64 bytes en ML-KEM-768 y 32 en ML-DSA-65, y las claves reconstruidas a partir de él coincidieron.

    Registrado por la comprobación
    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
  • Formatos de clave pública

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

    SPKI PEM y DER, SEC1 sin comprimir y comprimido, y x‖y en bruto para P-256, además de 32 bytes en bruto para Ed25519; todos se escribieron y se volvieron a leer.

    Registrado por la comprobación
    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
  • Formatos de clave privada

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

    Lee PKCS#8 y SEC1, en PEM o DER, el escalar P-256 en bruto y la semilla Ed25519; escribe PKCS#8; rechaza el PKCS#8 cifrado.

    Registrado por la comprobación
    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

Uso

Firma y verifica con Ed25519, y ejecuta un intercambio de claves ML-KEM-768 en 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)")

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor swift:6.4.1-noble, y la ejecución falla si la salida no coincide.

Comando
swift run -c release usage
La salida debe coincidir (expresión regular)
Ed25519 verified: true\nML-KEM-768 secrets match: true

Bibliotecas