本文へスキップ
ecdsa.com
Swift

swift-crypto

swift-crypto は、CryptoKit のない Linux などのプラットフォーム向けに Apple がオープンソースで公開している CryptoKit API です。ソースに同梱された BoringSSL 上で同じ Swift の型を提供します。macOS の CryptoKit ではランダム化される Ed25519 署名がここでは決定的になり、Secure Enclave はありません。
ライセンス
Apache-2.0
確認したバージョン
5.0.3 ·
ソースコード
github.com/apple/swift-crypto
ドキュメント
swiftpackageindex.com/apple/swift-crypto/documentation/crypto

互換性の概要

ECDSAEdDSAポスト量子暗号

ECDSA の曲線
P-256P-384P-521
EdDSA
Ed25519
公開鍵の形式
spki-pemspki-dersec1-uncompressedsec1-compressedraw-xyraw-32
秘密鍵の形式
pkcs8-pempkcs8-dersec1-pemsec1-derraw-scalarseed-32

swift-crypto を使うと、サーバーサイドやクロスプラットフォームの Swift コードから import Crypto で CryptoKit の API を利用できます。README では、Linux と ARM64 版 Windows 向けにその API の大部分を実装したものと説明されています。Apple プラットフォームではこのモジュールが CryptoKit そのものを再エクスポートする(#if canImport(CryptoKit))ため、ここに挙げる事実はそれ以外のプラットフォームについてのものです。確認には、公式の Swift 6.4.1 イメージ上の Linux で swift-crypto 5.0.3 と swift-asn1 1.7.3 を使いました。この環境では、コードはソースに同梱された BoringSSL のコピー上で動作します。

同じソースコードでも、2 つのプラットフォームで動作が異なることがあります。swift-crypto の Ed25519 署名は決定的で(RFC 8032 のテスト鍵で RFC の署名がそのまま再現されました)、macOS の CryptoKit ではランダム化されます。どちらの署名も OpenSSL で検証できたため、テストではバイト列を比較するのではなく、署名を検証すべきです。解析エラーは一致しました。同じ生の r‖s バイト列を ECDSASignature(derRepresentation:) に渡すと、どちらでも truncatedASN1Field がスローされました。ただし、どの CryptoKitASN1Error のケースになるかは入力のバイト列によって変わるため、特定のエラーケースではなく、失敗したかどうかで判定してください。

それ以外に測定した項目は、すべて CryptoKit と一致しました。ECDSA 署名には rawRepresentation(JWS と同じ 64 バイトの r‖s)と derRepresentation(X.509 と同じ)があります。変換には DER ⇄ 生形式の変換ツールを使ってください。nonce はランダムで、s は正規化されず、high-S 署名も検証を通ります。signature(for: data) は曲線に対応するハッシュでハッシュしますが、ダイジェストを渡す API は P-384 の鍵で SHA-256 を受け入れ、P-256 では SHA-512 のダイジェストを切り詰めました。これは OpenSSL でも確認できました。Ed25519 では、正規形でない S は拒否される一方、単位元(identity point)が公開鍵として受け入れられ、R = 単位元、S = 0 の署名も検証を通ります。

ポスト量子暗号の型は CryptoKit 26 と同じで、ML-KEM-768 と ML-KEM-1024、ML-DSA-65 と ML-DSA-87、ハイブリッド KEM の X-Wing があり、X-Wing は HPKE の暗号スイートとしても使えます。ML-DSA の署名はヘッジ付きで FIPS 204 のコンテキスト文字列に対応し、ML-KEM で破損した暗号文をデカプセル化するとエラーにならずに別の鍵が返り、秘密鍵はシードとして保持されます(ML-KEM は 64 バイト、ML-DSA は 32 バイト)。ML-KEM ツールやポスト量子署名ツールと比較してください。

このソースには SecureEnclave がないため、Secure Enclave の鍵を使うコードは Apple プラットフォーム向けにだけコンパイルしなければなりません。鍵の形式は CryptoKit と同じです。P-256 の秘密鍵は PKCS#8 で書き出され、PKCS#8 または SEC1(PEM または DER)から読み込めますが、暗号化された PKCS#8 からは読み込めません。Ed25519 の鍵は 32 バイトの生データです。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/swift-crypto/run.sh がコンテナイメージ swift:6.4.1-noble 内で swift-crypto 5.0.3 を実行しました。このページの内容はその出力と一致していなければならず、各事実には該当する出力行を示しています。

  • ECDSA 署名のエンコーディング

    structured

    ECDSASignature には 64 バイトの rawRepresentation と derRepresentation があります。そのバイト列(withUnsafeBytes)は生形式です。

    チェックによる記録
    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
  • ECDSA の nonce

    random

    同じメッセージへの 2 つの署名は異なり、RFC 6979 A.2.5 の鍵とメッセージからは RFC に記載された r が得られませんでした。

    チェックによる記録
    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

    データに対する署名は、P-256 では SHA-256、P-384 では SHA-384、P-521 では SHA-512 のハッシュ値に対する署名として検証できました。

    チェックによる記録
    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 のダイジェストに署名できました。openssl pkeyutl では、ダイジェストの先頭 32 バイトに対する署名として検証できました。

    チェックによる記録
    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
  • 不正な形式の ECDSA 署名

    throws

    init(derRepresentation:) は生の r‖s 署名と 3 バイトのデータに対して truncatedASN1Field をスローし、init(rawRepresentation:) は DER と 3 バイトのデータに対して incorrectParameterSize をスローしました。

    チェックによる記録
    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 曲線は宣言されていません。

    チェックによる記録
    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)です。ソースには Ed448 の型が宣言されていません。

    チェックによる記録
    fact eddsa = Ed25519
  • 決定的な Ed25519 署名

    yes

    同じメッセージへの 2 つの署名は同一で、RFC 8032 のテスト 1 の署名と一致しました。swift-crypto の署名は OpenSSL で検証できました。

    チェックによる記録
    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
  • 非正規の S を含む Ed25519 署名

    rejected

    RFC 8032 のテスト 1 の署名で 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

    単位元(identity point)を公開鍵としてインポートでき、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

    0xab を 64 バイト並べた署名と 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 の型が宣言されていません。

    チェックによる記録
    fact ml-kem-parameter-sets = ML-KEM-768, ML-KEM-1024
  • ML-DSA のパラメーターセット

    ML-DSA-65ML-DSA-87

    MLDSA65 と MLDSA87 で署名と検証ができました。ソースには ML-DSA-44 の型が宣言されていません。

    チェックによる記録
    fact ml-dsa-parameter-sets = ML-DSA-65, ML-DSA-87
  • ML-DSA の署名モード

    hedged

    同じメッセージへの 2 つの 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

    暗号文の 1 ビットを反転させると、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 は ML-KEM-768 では 64 バイト、ML-DSA-65 では 32 バイトで、そこから復元した鍵は元の鍵と一致しました。

    チェックによる記録
    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
  • 公開鍵の形式

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

    P-256 の SPKI(PEM と DER)、SEC1 の非圧縮形式と圧縮形式、生の x‖y、さらに Ed25519 の 32 バイトの生データは、いずれも書き出しと読み戻しができました。

    チェックによる記録
    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
  • 秘密鍵の形式

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

    PKCS#8 と SEC1(PEM または DER)、P-256 の生のスカラー、Ed25519 のシードを読み込めます。書き出しは PKCS#8 で、暗号化された PKCS#8 は拒否されます。

    チェックによる記録
    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

使い方

Linux で Ed25519 による署名と検証を行い、ML-KEM-768 で鍵交換を実行します。
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)")

スニペットのテストハーネスは、すべてのサンプルをコンテナイメージ swift:6.4.1-noble 内でそのまま実行し、出力が一致しなければ実行は失敗します。

コマンド
swift run -c release usage
一致すべき出力(正規表現)
Ed25519 verified: true\nML-KEM-768 secrets match: true

ライブラリ