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 нет.