Pular para o conteúdo
ecdsa.com
Swift

swift-crypto

O swift-crypto, a API do CryptoKit em código aberto da Apple para Linux e outras plataformas sem CryptoKit: os mesmos tipos Swift sobre uma cópia embutida do BoringSSL — com Ed25519 determinístico, ao passo que o CryptoKit no macOS usa aleatoriedade, e sem Secure Enclave.
Licença
Apache-2.0
Versão verificada
5.0.3 ·
Código-fonte
github.com/apple/swift-crypto
Documentação
swiftpackageindex.com/apple/swift-crypto/documentation/crypto

Visão geral da compatibilidade

ECDSAEdDSAPós-quântica

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

O swift-crypto permite que código Swift do lado do servidor e multiplataforma use a API do CryptoKit por meio de import Crypto. O README o descreve como uma implementação de uma parte substancial dessa API para Linux e Windows em ARM64; nas plataformas da Apple, o módulo reexporta o próprio CryptoKit (#if canImport(CryptoKit)), então os fatos desta página descrevem as outras plataformas. Eles foram conferidos com swift-crypto 5.0.3 e swift-asn1 1.7.3 no Linux, na imagem oficial do Swift 6.4.1, em que o código roda sobre uma cópia embutida do BoringSSL.

O mesmo código-fonte pode se comportar de forma diferente nas duas plataformas. Aqui, as assinaturas Ed25519 são determinísticas — a chave de teste da RFC 8032 reproduziu exatamente a assinatura da RFC —, enquanto o CryptoKit no macOS usa aleatoriedade nelas; os dois tipos foram verificados com o OpenSSL, então convém que os testes verifiquem as assinaturas em vez de comparar bytes. Os erros de parsing coincidiram: os mesmos bytes r‖s brutos passados a ECDSASignature(derRepresentation:) lançaram truncatedASN1Field nas duas plataformas, mas o caso de CryptoKitASN1Error que aparece depende dos bytes de entrada; por isso, trate qualquer falha, e não um caso de erro específico.

Todo o resto que foi medido coincidiu com o CryptoKit. As assinaturas ECDSA expõem rawRepresentation (r‖s de 64 bytes, como no JWS) e derRepresentation (como no X.509); converta com o conversor DER ⇄ bruto. Os nonces são aleatórios e o s não é normalizado; assinaturas high-S passam na verificação. signature(for: data) calcula o hash com o hash da curva, mas a API de digest aceitou SHA-256 em uma chave P-384 e truncou um digest SHA-512 na P-256, o que o OpenSSL confirmou. No Ed25519, um S não canônico é rejeitado, enquanto o ponto identidade é aceito como chave pública, junto com uma assinatura R = identidade, S = 0.

Os tipos pós-quânticos coincidem com os do CryptoKit 26: ML-KEM-768 e ML-KEM-1024, ML-DSA-65 e ML-DSA-87 e o KEM híbrido X-Wing, também como cipher suite do HPKE. A assinatura ML-DSA usa aleatoriedade adicional (hedged) e aceita strings de contexto da FIPS 204, o desencapsulamento ML-KEM de um texto cifrado danificado retorna uma chave diferente sem erro, e as chaves privadas são mantidas como sementes (64 bytes para ML-KEM, 32 para ML-DSA). Compare com a ferramenta de ML-KEM e a ferramenta de assinaturas pós-quânticas.

Não há SecureEnclave neste código-fonte, então código que usa chaves do Secure Enclave precisa ser compilado apenas para plataformas da Apple. Os formatos de chave coincidem com os do CryptoKit: chaves privadas P-256 são gravadas como PKCS#8 e lidas a partir de PKCS#8 ou SEC1, em PEM ou DER, mas não de PKCS#8 criptografado; chaves Ed25519 são 32 bytes brutos.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/swift-crypto/run.sh executou swift-crypto 5.0.3 na imagem de contêiner swift:6.4.1-noble em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Codificação da assinatura ECDSA

    structured

    ECDSASignature tem um rawRepresentation de 64 bytes e um derRepresentation; seus bytes (withUnsafeBytes) estão na forma bruta.

    Registrado pela verificação
    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 do ECDSA

    random

    Duas assinaturas de uma mesma mensagem foram diferentes, e a chave e a mensagem do A.2.5 da RFC 6979 não produziram o r da RFC.

    Registrado pela verificação
    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 ao assinar

    either

    O s não é normalizado: valores high-S apareceram entre 64 assinaturas.

    Registrado pela verificação
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • High-S ao verificar

    accepts-high-s

    Uma assinatura com s substituído por n − s foi verificada.

    Registrado pela verificação
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: isValidSignature with s replaced by n - s: true
  • Entrada do hash na chamada de assinatura

    hashes-message-or-digest

    signature(for: data) calcula o hash; signature(for: SHA256.hash(data: message)) foi verificada contra a mensagem.

    Registrado pela verificação
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: signature(for: SHA256.hash(data: message)) verifies with isValidSignature(_:for: message): yes
  • Hash aplicado às mensagens

    curve-matched

    Assinaturas sobre dados foram verificadas como assinaturas sobre SHA-256 na P-256, SHA-384 na P-384 e SHA-512 na P-521.

    Registrado pela verificação
    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 à curva

    none

    Uma chave P-384 assinou um digest SHA-256, e a assinatura foi verificada.

    Registrado pela verificação
    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 mais longo que a ordem da curva

    truncated

    Uma chave P-256 assinou um digest SHA-512; openssl pkeyutl verificou a assinatura sobre os primeiros 32 bytes do digest.

    Registrado pela verificação
    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
  • Assinatura ECDSA malformada

    throws

    init(derRepresentation:) lançou truncatedASN1Field para uma assinatura r‖s bruta e para 3 bytes; init(rawRepresentation:) lançou incorrectParameterSize para DER e para 3 bytes.

    Registrado pela verificação
    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

    Assinatura e verificação funcionaram com P-256, P-384 e P-521; o código-fonte não declara nenhuma outra curva ECDSA.

    Registrado pela verificação
    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); o código-fonte não declara nenhum tipo Ed448.

    Registrado pela verificação
    fact eddsa = Ed25519
  • Assinaturas Ed25519 determinísticas

    yes

    Duas assinaturas de uma mesma mensagem foram idênticas e iguais à assinatura do teste 1 da RFC 8032; o OpenSSL verificou uma assinatura do swift-crypto.

    Registrado pela verificação
    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
  • Assinatura Ed25519 com S não canônico

    rejected

    A assinatura do teste 1 da RFC 8032 com S substituído por S + L foi rejeitada.

    Registrado pela verificação
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: the RFC 8032 test 1 signature with S replaced by S + L
  • Chave pública Ed25519 de ordem pequena

    accepted

    O ponto identidade foi importado como chave pública, e a assinatura R = identidade com S = 0 foi verificada.

    Registrado pela verificação
    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
  • Assinatura Ed25519 malformada

    rejected

    64 bytes de 0xab e uma assinatura de 63 bytes retornaram false.

    Registrado pela verificação
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: false; 63 bytes of the RFC signature: false
  • Algoritmos pós-quânticos

    ML-KEMML-DSAX-Wing

    Os encapsulamentos ML-KEM e X-Wing fizeram o ciclo completo de ida e volta e as assinaturas ML-DSA foram verificadas; o X-Wing também funcionou por meio do HPKE.

    Registrado pela verificação
    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 do ML-KEM

    ML-KEM-768ML-KEM-1024

    MLKEM768 e MLKEM1024 fizeram o ciclo completo de ida e volta; o código-fonte não declara nenhum tipo ML-KEM-512.

    Registrado pela verificação
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • Conjuntos de parâmetros do ML-DSA

    ML-DSA-65ML-DSA-87

    Assinatura e verificação funcionaram com MLDSA65 e MLDSA87; o código-fonte não declara nenhum tipo ML-DSA-44.

    Registrado pela verificação
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • Modo de assinatura do ML-DSA

    hedged

    Duas assinaturas ML-DSA-65 de uma mesma mensagem foram diferentes (assinatura hedged).

    Registrado pela verificação
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: two ML-DSA-65 signatures of one message are different
  • String de contexto do ML-DSA

    supported

    Uma assinatura com o contexto invoice-v1 foi verificada com ele e falhou com invoice-v2 e sem contexto.

    Registrado pela verificação
    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
  • Assinatura ML-DSA malformada

    returns-false

    Uma assinatura truncada, 3 bytes e uma mensagem errada retornaram false.

    Registrado pela verificação
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: isValidSignature: truncated signature false, 3 bytes false, wrong message false
  • Texto cifrado ML-KEM modificado

    implicit-rejection

    Com um bit do texto cifrado invertido, decapsulate retornou uma chave diferente da do remetente.

    Registrado pela verificação
    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
  • Chaves privadas pós-quânticas

    seed-only

    seedRepresentation tem 64 bytes para ML-KEM-768 e 32 para ML-DSA-65, e as chaves reconstruídas a partir dele coincidiram.

    Registrado pela verificação
    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 chave pública

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

    SPKI PEM e DER, SEC1 não comprimido e comprimido e x‖y bruto para P-256, além de 32 bytes brutos para Ed25519, todos gravados e lidos de volta.

    Registrado pela verificação
    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 chave privada

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

    Lê PKCS#8 e SEC1, em PEM ou DER, o escalar bruto da P-256 e a semente Ed25519; grava PKCS#8; PKCS#8 criptografado é recusado.

    Registrado pela verificação
    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

Assine e verifique com Ed25519 e execute uma troca de chaves ML-KEM-768 no 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)")

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner swift:6.4.1-noble, e a execução falha se a saída não corresponder.

Comando
swift run -c release usage
A saída deve corresponder a (expressão regular)
Ed25519 verified: true\nML-KEM-768 secrets match: true

Bibliotecas