本文へスキップ
ecdsa.com
Python

pyca/cryptography

Python Cryptographic Authority の cryptography パッケージは、Python で標準的に使われる暗号ライブラリです。OpenSSL をバックエンドとする ECDSA(署名は DER、RFC 6979 はオプトイン)、EdDSA、ML-KEM、ML-DSA を備えますが、JWK には対応していません。
ライセンス
Apache-2.0 OR BSD-3-Clause
確認したバージョン
50.0.2 ·
ソースコード
github.com/pyca/cryptography
ドキュメント
cryptography.io/en/latest/

互換性の概要

ECDSAEdDSAポスト量子暗号

ECDSA の曲線
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
公開鍵の形式
spki-pemspki-dersec1-uncompressedsec1-compressedopenssh
秘密鍵の形式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalaropenssh

PyJWT、Authlib、paramiko、certbot、AWS や Google の SDK など、ほとんどの Python コードは実際の暗号処理に cryptography を使っています。hazmat 層では鍵と署名を直接扱えます。wheel には独自の OpenSSL が同梱されているため、動作は OS ではなくパッケージのバージョンで決まります(50.0.2 には OpenSSL 4.0.3 が同梱されています)。

ECDSA 署名は ASN.1 DER で出力され、verify() も DER を前提とします。WebCrypto、JWS、ほとんどのハードウェアトークンは 64 バイトの生の r‖s 形式を使いますが、verify() はこれを偽造された署名と同じ InvalidSignature で拒否します。decode_dss_signature と encode_dss_signature は DER と整数のペアを相互に変換します。固定長のパディングは、サンプルのとおり自分で追加する必要があります。手作業で確認するには DER ⇄ 生形式の変換ツールを使ってください。

RFC 6979 を指定しない限り、nonce はランダムです。ec.ECDSA(hashes.SHA256(), deterministic_signing=True) は、RFC の P-256 テストベクトルを、s が high-S の値である点まで含めて正確に再現しました。パッケージには s を正規化する処理がなく、検証は s が n の上半分と下半分のどちらにあっても受け入れるためです。libsecp256k1 や @noble/curves のように low-S を要求する検証者に渡す署名は、先に s を n − s に置き換える必要があります。sign() と verify() はデータを自分でハッシュ化します。すでにダイジェストを持っている場合は Prehashed(hashes.SHA256()) を渡してください。そうしないと二重にハッシュ化されます。

50.0.2 の対応曲線は幅広く(P-224 から P-521 まで、secp256k1、Brainpool の r1 曲線)、さらに Ed25519、Ed448、ML-KEM-768/1024、ML-DSA-44/65/87 にも対応しています。鍵は PEM と DER(SPKI、PKCS#8、暗号化された PKCS#8、SEC1)、OpenSSH、SEC1 の点そのものから読み込めます。JWK を読み込む機能はないため、JWK は PyJWT、Authlib、jwcrypto、または JWK 変換ツールを経由させてください。

検索されることの多いエラーのほとんどは、署名ではなく鍵の読み込みで発生します。load_pem_private_key に公開鍵を渡した場合、暗号化された鍵をパスワードなしで読み込んだ場合、指定した名前付き曲線上にない点を渡した場合などです。別のスタックで作られた署名がどうしても検証を通らないときは、署名検証ツールとライブラリ相互運用マトリクスが役立ちます。

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

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

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

    der

    sign() は ASN.1 DER を返し、verify() は DER を前提とします。

    チェックによる記録
    fact ecdsa-signature-encoding = der
  • 署名時の low-S

    either

    s は正規化されません。64 個の署名の中に high-S の値が現れました。

    チェックによる記録
    fact ecdsa-low-s-sign = either
  • 検証時の high-S

    accepts-high-s

    s を n − s に置き換えた署名も検証を通りました。

    チェックによる記録
    fact ecdsa-low-s-verify = accepts-high-s
  • ECDSA の nonce

    rfc6979-opt-in

    デフォルトはランダムです。deterministic_signing=True を指定すると、RFC 6979 の付録 A.2.5 を、s が high-S の値である点まで含めて正確に再現しました。

    チェックによる記録
    fact ecdsa-nonce = rfc6979-opt-in
    evidence ecdsa-nonce: default repeats: False; deterministic_signing=True reproduces RFC 6979 A.2.5 (r and high s): True
  • 署名呼び出しがハッシュする対象

    hashes-message-or-digest

    sign() はデータをハッシュ化します。Prehashed(hashes.SHA256()) を指定すると自分で計算したダイジェストに署名し、その署名は元のメッセージに対して検証を通りました。

    チェックによる記録
    fact ecdsa-prehash = hashes-message-or-digest
  • 不正な形式の ECDSA 署名

    throws

    生の r‖s 署名、3 バイトのデータ、別のメッセージに対する署名は、いずれも InvalidSignature を送出します。例外からはどれが原因かわかりません。

    チェックによる記録
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s: InvalidSignature
    evidence ecdsa-malformed-signature: 3 bytes: InvalidSignature
    evidence ecdsa-malformed-signature: wrong message: InvalidSignature
  • ECDSA の曲線

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    P-224、P-256、P-384、P-521、secp256k1、Brainpool の r1 曲線で署名と検証ができました。

    チェックによる記録
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 と Ed448 です。

    チェックによる記録
    fact eddsa = Ed25519, Ed448
  • ポスト量子アルゴリズム

    ML-KEMML-DSA

    ML-KEM-768 と ML-KEM-1024 でカプセル化とデカプセル化、ML-DSA-44/65/87 で署名と検証ができました。ML-KEM-512 のクラスはなく、SLH-DSA もありません。

    チェックによる記録
    fact pqc = ML-KEM, ML-DSA
    evidence pqc: ML-KEM-512 class present: False; SLH-DSA module present: False
  • 公開鍵の形式

    spki-pemspki-dersec1-uncompressedsec1-compressedopenssh

    公開鍵は、SPKI の PEM と DER、OpenSSH、from_encoded_point による SEC1 の点(圧縮形式・非圧縮形式)から読み込めました。プレフィックスのない 64 バイトの x‖y は拒否され、JWK には対応していません。

    チェックによる記録
    fact public-key-formats = spki-pem, spki-der, sec1-uncompressed, sec1-compressed, openssh
    evidence public-key-formats: no JWK reader or writer in the package
  • 秘密鍵の形式

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pemsec1-derraw-scalaropenssh

    秘密鍵は、PKCS#8 の PEM と DER、暗号化された PKCS#8、SEC1 の PEM と DER、OpenSSH、derive_private_key による生のスカラーから読み込めました。

    チェックによる記録
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem, sec1-der, raw-scalar, openssh

使い方

P-256 で署名し、DER 署名を生の r‖s に変換して元に戻してから、検証します。
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature

key = ec.generate_private_key(ec.SECP256R1())
message = b"order #1042 approved"

# DER out of sign(); turn it into the 64-byte r||s that WebCrypto and JOSE expect.
der = key.sign(message, ec.ECDSA(hashes.SHA256()))
r, s = decode_dss_signature(der)
raw = r.to_bytes(32, "big") + s.to_bytes(32, "big")

# A raw signature from WebCrypto goes the other way before verify().
back = encode_dss_signature(int.from_bytes(raw[:32], "big"), int.from_bytes(raw[32:], "big"))
try:
    key.public_key().verify(back, message, ec.ECDSA(hashes.SHA256()))
    print("verified: True, raw bytes:", len(raw))
except InvalidSignature:
    print("verified: False")

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

セットアップ
pip install --quiet --root-user-action=ignore cryptography==50.0.2
コマンド
python usage.py
一致すべき出力(正規表現)
verified: True, raw bytes: 64

ライブラリ