Перейти к содержимому
ecdsa.com
Swift

Apple CryptoKit

Apple CryptoKit, криптографический фреймворк Swift, встроенный в iOS и macOS: подписи ECDSA одновременно в форматах raw и DER, рандомизированные подписи Ed25519, ML-KEM, ML-DSA и X-Wing начиная с версий 26 и ключи Secure Enclave, доступные через API только как непрозрачные дескрипторы.
Лицензия
Proprietary (part of Apple's operating systems)
Проверенная версия
macOS 27.0.1 ·
Исходный код
developer.apple.com/documentation/cryptokit
Документация
developer.apple.com/documentation/cryptokit

Совместимость вкратце

ECDSAEdDSAПостквантовая криптография

Кривые ECDSA
P-256P-384P-521
EdDSA
Ed25519
Форматы открытых ключей
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32
Форматы закрытых ключей
pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32hardware-handle

CryptoKit — фреймворк, который приложения на Swift для платформ Apple используют для хеширования, подписей, согласования ключей, AEAD и HPKE. Он поставляется вместе с операционной системой, поэтому его версия совпадает с версией ОС; в SDK P-256 помечен как доступный начиная с iOS 13 и macOS 10.15, а ML-KEM, ML-DSA и X-Wing — начиная с iOS 26 и macOS 26. Факты на этой странице проверены в macOS 27.0.1 со Swift 6.4 на Mac с Apple silicon, и оба примера там же скомпилированы и запущены; на iOS проверка не проводилась. В Linux тот же API доступен в swift-crypto.

Подпись P-256 — значение ECDSASignature в двух формах: rawRepresentation — 64-байтовое значение r‖s, которое используют JWS (ES256) и WebCrypto, и derRepresentation — форма DER, которую ожидают X.509, OpenSSL и Java. Передать не ту форму — частая ошибка совместимости; пример выполняет преобразование явно, а конвертер DER ⇄ raw показывает обе формы. Разбор строгий и выбрасывает ошибки: байты raw, переданные в init(derRepresentation:), вызывают CryptoKitASN1Error (для тестовой подписи из RFC 6979 — truncatedASN1Field, хотя конкретный вариант ошибки зависит от байтов), а DER, переданный в init(rawRepresentation:), вызывает incorrectParameterSize, тогда как isValidSignature просто возвращает false.

Nonce выбираются случайно (две подписи одного сообщения различались, а тестовый ключ из RFC 6979 не дал значения r из RFC), и s не нормализуется: встречаются значения high-S, а подпись, в которой s заменили на n − s, прошла проверку. signature(for: data) хеширует данные хеш-функцией, соответствующей кривой (SHA-256, SHA-384, SHA-512), но signature(for: digest) принимает дайджест любого типа: дайджест SHA-256 был подписан и проверен на ключе P-384, а дайджест SHA-512 на P-256 был усечён до первых 32 байт, и Security.framework это подтвердил. При предварительном хешировании сами передавайте дайджест, соответствующий кривой.

Подписи Ed25519 (Curve25519.Signing) рандомизированы: с тестовым ключом из RFC 8032 подпись тестового сообщения в CryptoKit отличалась и от подписи из RFC, и от собственной предыдущей, но обе подписи CryptoKit прошли проверку, в том числе в Node.js. Поэтому тесты, которые сравнивают байты подписи с векторами RFC 8032, с CryptoKit в macOS не проходят; вместо этого проверяйте подписи. Неканонические значения S отклоняются, но нейтральная точка была принята как открытый ключ, а подпись с R, равным нейтральной точке, и S = 0 прошла проверку для произвольного сообщения. Если подпись должна быть привязана к одному конкретному подписанту, отклоняйте ключи малого порядка сами. Ключи Ed25519 существуют только в виде 32 байт в формате raw; формы PEM или DER для них нет.

Постквантовые типы — ML-KEM-768 и ML-KEM-1024, ML-DSA-65 и ML-DSA-87 (ML-KEM-512, ML-DSA-44 и SLH-DSA нет), а также гибридный KEM X-Wing (XWingMLKEM768X25519), который доступен и как набор шифров HPKE. Подписание ML-DSA выполняется с подмешанной случайностью (hedged) и принимает строку контекста по FIPS 204; проверяющая сторона должна передать ту же строку. Декапсуляция повреждённого шифртекста ML-KEM возвращает другой ключ, а не выбрасывает ошибку. Закрытые ключи хранятся только как seed (64 байта для ML-KEM, 32 для ML-DSA) или как seed с проверкой целостности, поэтому развёрнутый закрытый ключ, экспортированный другой библиотекой, импортировать нельзя. Попробуйте эти схемы в инструменте ML-KEM и инструменте постквантовых подписей.

SecureEnclave.P256.Signing.PrivateKey() создаёт ключ внутри Secure Enclave, и на этом Mac так же сработали типы ML-KEM и ML-DSA из SecureEnclave. Эти типы не дают доступа к закрытому ключу в виде raw, DER или PEM, а только к dataRepresentation — дескриптору, по которому ключ удалось открыть снова; открытый ключ при этом — обычный P256.Signing.PublicKey или MLDSA65.PublicKey. Программные ключи P-256 экспортируются в PKCS#8 (derRepresentation, pemRepresentation) и импортируются из PKCS#8 и SEC1 в PEM или DER, но не из зашифрованного PKCS#8, а поддержки JWK в CryptoKit нет.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/apple-cryptokit/run.sh запустила Apple CryptoKit macOS 27.0.1 на платформе macOS 27.0.1 (Swift 6.4); содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Кодировка подписи ECDSA

    structured

    У ECDSASignature есть 64-байтовое представление rawRepresentation и представление derRepresentation; байты самого значения (withUnsafeBytes) — это форма raw.

    Что записала проверка
    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

    Две подписи одного сообщения различались, а ключ и сообщение из раздела A.2.5 RFC 6979 не дали значения r, приведённого в RFC.

    Что записала проверка
    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 при подписи

    either

    s не нормализуется: среди 64 подписей встретились значения high-S.

    Что записала проверка
    fact ecdsa-low-s-sign = either
    evidence ecdsa-low-s-sign: signatures with s > n/2 among 64: yes
  • High-S при проверке

    accepts-high-s

    Подпись, в которой s заменили на n − s, прошла проверку.

    Что записала проверка
    fact ecdsa-low-s-verify = accepts-high-s
    evidence ecdsa-low-s-verify: isValidSignature with s replaced by n - s: true
  • Что хеширует вызов подписи

    hashes-message-or-digest

    signature(for: data) хеширует данные; подпись signature(for: SHA256.hash(data: message)) прошла проверку по сообщению.

    Что записала проверка
    fact ecdsa-prehash = hashes-message-or-digest
    evidence ecdsa-prehash: signature(for: SHA256.hash(data: message)) verifies with isValidSignature(_:for: message): yes
  • Хеш-функция для сообщений

    curve-matched

    Подписи данных прошли проверку как подписи хеша SHA-256 на P-256, SHA-384 на P-384 и SHA-512 на P-521.

    Что записала проверка
    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
  • Хеш-функция, привязанная к кривой

    none

    Ключом P-384 подписан дайджест SHA-256, и подпись прошла проверку.

    Что записала проверка
    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
  • Дайджест длиннее порядка кривой

    truncated

    Ключом P-256 подписан дайджест SHA-512; Security.framework успешно проверил подпись по первым 32 байтам дайджеста.

    Что записала проверка
    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
  • Некорректная подпись ECDSA

    throws

    init(derRepresentation:) выбросил truncatedASN1Field для подписи raw r‖s и для 3 байт; init(rawRepresentation:) выбросил incorrectParameterSize для DER и для 3 байт.

    Что записала проверка
    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
  • Кривые ECDSA

    P-256P-384P-521

    P-256, P-384 и P-521: подпись и проверка прошли успешно; других кривых ECDSA в SDK не объявлено.

    Что записала проверка
    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); типа для Ed448 в SDK не объявлено.

    Что записала проверка
    fact eddsa = Ed25519
  • Детерминированные подписи Ed25519

    no

    Две подписи одного сообщения одним ключом различались; обе прошли проверку, в том числе в node:crypto.

    Что записала проверка
    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
  • Подпись Ed25519 с неканоническим S

    rejected

    Подпись из теста 1 RFC 8032, в которой S заменили на S + L, была отклонена.

    Что записала проверка
    fact ed25519-non-canonical-s = rejected
    evidence ed25519-non-canonical-s: the RFC 8032 test 1 signature with S replaced by S + L
  • Открытый ключ Ed25519 малого порядка

    accepted

    Нейтральная точка импортировалась как открытый ключ, а подпись с R, равным нейтральной точке, и S = 0 прошла проверку.

    Что записала проверка
    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
  • Некорректная подпись Ed25519

    rejected

    Для 64 байт со значением 0xab и для 63-байтовой подписи проверка вернула false.

    Что записала проверка
    fact ed25519-malformed-signature = rejected
    evidence ed25519-malformed-signature: 64 bytes of 0xab: false; 63 bytes of the RFC signature: false
  • Постквантовые алгоритмы

    ML-KEMML-DSAX-Wing

    Инкапсуляции ML-KEM и X-Wing прошли полный цикл с декапсуляцией, а подписи ML-DSA прошли проверку; X-Wing также сработал через HPKE.

    Что записала проверка
    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
  • Наборы параметров ML-KEM

    ML-KEM-768ML-KEM-1024

    MLKEM768 и MLKEM1024 прошли полный цикл инкапсуляции и декапсуляции; типа для ML-KEM-512 в SDK не объявлено.

    Что записала проверка
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • Наборы параметров ML-DSA

    ML-DSA-65ML-DSA-87

    MLDSA65 и MLDSA87: подпись и проверка прошли успешно; типа для ML-DSA-44 в SDK не объявлено.

    Что записала проверка
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • Режим подписи ML-DSA

    hedged

    Две подписи ML-DSA-65 одного сообщения различались (подписание с подмешанной случайностью).

    Что записала проверка
    fact ml-dsa-signing = hedged
    evidence ml-dsa-signing: two ML-DSA-65 signatures of one message are different
  • Строка контекста ML-DSA

    supported

    Подпись с контекстом invoice-v1 прошла проверку с этим контекстом и не прошла с invoice-v2 и без контекста.

    Что записала проверка
    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
  • Некорректная подпись ML-DSA

    returns-false

    Для усечённой подписи, 3 байт и неверного сообщения проверка вернула false.

    Что записала проверка
    fact ml-dsa-malformed-signature = returns-false
    evidence ml-dsa-malformed-signature: isValidSignature: truncated signature false, 3 bytes false, wrong message false
  • Изменённый шифртекст ML-KEM

    implicit-rejection

    После инвертирования одного бита шифртекста decapsulate вернул ключ, отличный от ключа отправителя.

    Что записала проверка
    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
  • Постквантовые закрытые ключи

    seed-only

    seedRepresentation занимает 64 байта для ML-KEM-768 и 32 для ML-DSA-65, и восстановленные из него ключи совпали с исходными; единственный другой инициализатор закрытого ключа принимает seed с проверкой целостности.

    Что записала проверка
    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:
  • Алгоритмы Secure Enclave

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

    Ключи каждого типа были сгенерированы в Secure Enclave и на этом Mac использованы для подписи, проверки или декапсуляции.

    Что записала проверка
    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
  • Форматы открытых ключей

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

    Для P-256 — SPKI в PEM и DER, SEC1 в несжатой и сжатой форме и raw x‖y, для Ed25519 — 32 байта raw; все форматы записаны и прочитаны обратно.

    Что записала проверка
    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
  • Форматы закрытых ключей

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

    Читает PKCS#8 и SEC1 в PEM или DER, скаляр P-256 в формате raw и seed Ed25519; записывает PKCS#8; ключи Secure Enclave — дескрипторы.

    Что записала проверка
    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

Использование

Подпишите ключом P-256, возьмите форму raw для JOSE и форму DER для сторон, работающих с X.509, и проверьте подпись, восстановленную из 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])
Создайте ключ P-256 в Secure Enclave, откройте его снова по дескриптору, подпишите данные и проверьте подпись обычным открытым ключом.
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))")

Для этой платформы нет образа контейнера, поэтому примеры запускались на платформе macOS 27.0.1 (Swift 6.4) 20 октября 2026 г..

Библиотеки