Pular para o conteúdo
ecdsa.com
Swift

Apple CryptoKit

O Apple CryptoKit, o framework de criptografia em Swift embutido no iOS e no macOS: assinaturas ECDSA nas formas bruta e DER, assinaturas Ed25519 com aleatoriedade, ML-KEM, ML-DSA e X-Wing desde as versões 26 e chaves do Secure Enclave que a API expõe só como identificadores opacos (handles).
Licença
Proprietary (part of Apple's operating systems)
Versão verificada
macOS 27.0.1 ·
Código-fonte
developer.apple.com/documentation/cryptokit
Documentação
developer.apple.com/documentation/cryptokit

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

O CryptoKit é o framework que os apps Swift nas plataformas da Apple usam para hash, assinaturas, acordo de chaves, AEAD e HPKE. Ele vem com o sistema operacional, então a versão dele é a do sistema; o SDK marca a P-256 como disponível a partir do iOS 13 e do macOS 10.15, e ML-KEM, ML-DSA e X-Wing a partir do iOS 26 e do macOS 26. Os fatos desta página foram conferidos no macOS 27.0.1 com Swift 6.4 em um Mac com Apple silicon, e os dois exemplos foram compilados e executados ali; o iOS não foi verificado. No Linux, a mesma API vem do swift-crypto.

Uma assinatura P-256 é um valor ECDSASignature com duas formas: rawRepresentation, o r‖s de 64 bytes usado pelo JWS (ES256) e pela WebCrypto, e derRepresentation, a forma DER que X.509, OpenSSL e Java esperam. Enviar a forma errada é um bug de interoperabilidade comum; o exemplo converte explicitamente, e o conversor DER ⇄ bruto mostra as duas. O parsing é estrito e lança erros: bytes brutos passados a init(derRepresentation:) lançam um CryptoKitASN1Error — truncatedASN1Field para a assinatura de teste da RFC 6979, embora o caso dependa dos bytes —, e DER passado a init(rawRepresentation:) lança incorrectParameterSize, enquanto isValidSignature simplesmente retorna false.

Os nonces são aleatórios — duas assinaturas de uma mesma mensagem foram diferentes, e a chave de teste da RFC 6979 não produziu o r da RFC — e o s não é normalizado: aparecem valores high-S, e uma assinatura com s substituído por n − s foi verificada. signature(for: data) calcula o hash com o hash que corresponde à curva (SHA-256, SHA-384, SHA-512), mas signature(for: digest) aceita qualquer tipo de digest: um digest SHA-256 foi assinado e verificado com uma chave P-384, e um digest SHA-512 na P-256 foi truncado para os primeiros 32 bytes, o que o Security.framework confirmou. Ao fazer o pré-hash, passe você mesmo o digest correspondente à curva.

As assinaturas Ed25519 (Curve25519.Signing) usam aleatoriedade: com a chave de teste da RFC 8032, a assinatura do CryptoKit sobre a mensagem de teste foi diferente da assinatura da RFC e da anterior do próprio CryptoKit, mas ambas foram verificadas, inclusive com o Node.js. Por isso, testes que comparam os bytes da assinatura com os vetores da RFC 8032 falham com o CryptoKit no macOS; verifique as assinaturas em vez disso. Valores de S não canônicos são rejeitados, mas o ponto identidade foi aceito como chave pública, e uma assinatura com R = identidade e S = 0 foi verificada para uma mensagem arbitrária. Rejeite você mesmo chaves de ordem pequena quando uma assinatura precisar estar vinculada a um signatário específico. As chaves Ed25519 existem apenas como 32 bytes brutos; não há forma PEM nem DER para elas.

Os tipos pós-quânticos são ML-KEM-768 e ML-KEM-1024, ML-DSA-65 e ML-DSA-87 — não há ML-KEM-512, ML-DSA-44 nem SLH-DSA — e o KEM híbrido X-Wing (XWingMLKEM768X25519), também oferecido como cipher suite do HPKE. A assinatura ML-DSA usa aleatoriedade adicional (hedged) e aceita uma string de contexto da FIPS 204, que o verificador precisa repetir. O desencapsulamento ML-KEM de um texto cifrado danificado retorna uma chave diferente em vez de lançar um erro. As chaves privadas são armazenadas apenas como sementes — 64 bytes para ML-KEM, 32 para ML-DSA — ou como a semente com uma verificação de integridade, então uma chave privada expandida exportada por outra biblioteca não pode ser importada. Experimente os esquemas na ferramenta de ML-KEM e na ferramenta de assinaturas pós-quânticas.

SecureEnclave.P256.Signing.PrivateKey() cria uma chave dentro do Secure Enclave, e, neste Mac, os tipos ML-KEM e ML-DSA sob SecureEnclave fizeram o mesmo. Esses tipos não expõem a chave privada em forma bruta, DER ou PEM — só dataRepresentation, um identificador (handle) que reabriu a chave —, enquanto a chave pública é uma P256.Signing.PublicKey ou MLDSA65.PublicKey comum. As chaves P-256 em software são exportadas como PKCS#8 (derRepresentation, pemRepresentation) e importadas de PKCS#8 e SEC1, em PEM ou DER, mas não de PKCS#8 criptografado, e o CryptoKit não oferece suporte a JWK.

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

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/apple-cryptokit/run.sh executou Apple CryptoKit macOS 27.0.1 na plataforma macOS 27.0.1 (Swift 6.4) 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; o Security.framework 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; Security.framework verifies the signature over the digest's first 32 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 SDK não declara nenhuma outra curva ECDSA.

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

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

    no

    Duas assinaturas de uma mesma mensagem com a mesma chave foram diferentes; ambas foram verificadas, inclusive com o node:crypto.

    Registrado pela verificação
    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
  • 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 SDK 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 SDK 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; o único outro inicializador de chave privada recebe a semente com verificação de integridade.

    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
    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:
  • Algoritmos do Secure Enclave

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

    Chaves de cada tipo foram geradas no Secure Enclave e usadas para assinar, verificar ou desencapsular neste Mac.

    Registrado pela verificação
    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
  • 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
    evidence public-key-formats: key representations declared on the Curve25519 types: rawRepresentation; lines mentioning JWK in the SDK interface: 0
  • Formatos de chave privada

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

    Lê PKCS#8 e SEC1, em PEM ou DER, o escalar bruto da P-256 e a semente Ed25519; grava PKCS#8; as chaves do Secure Enclave são identificadores (handles).

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

Uso

Assine com uma chave P-256, use a forma bruta para JOSE e a forma DER para sistemas que usam X.509 e verifique uma assinatura reconstruída a partir de 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])
Crie uma chave P-256 no Secure Enclave, reabra-a a partir do identificador (handle), assine e verifique com a chave pública comum.
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))")

Nenhuma imagem de contêiner oferece esta plataforma; os exemplos foram executados na plataforma macOS 27.0.1 (Swift 6.4) em 20 de outubro de 2026.

Bibliotecas