このページの主要な見どころ。 気になる項目から読み始めてください。
🍰 まずはやさしく
ネット上のデジタルな印鑑のようなものです。
送った人が本物であることを証明するために使います。
スマホで電子契約を結ぶときなどに使われています。
ここでは電子署名の仕組みと役割を学びます。
電子署名 = メッセージのハッシュを送信者の秘密鍵で暗号化したもの。 受信者は公開鍵で復号し、 本文のハッシュと一致するかを検証する。
🍰 まずはやさしく
データの正しさを証明する仕組みのことです。
ファイルが書き換えられていないか確かめるために使います。
学校で配られたデータが本物か確認する場面に似ています。
ここでは実際にプログラムで検証する方法を読みます。
論文・実務レポート・公的統計の解説で、 こんな場面に出会ったはずです。
この「ハッシュ + 公開鍵で本人性を担保する」発想が電子署名の核です。 本ページでは SSDSE-B-2026.csv を実例として、 署名生成・検証・改ざん検出を Python で実装します。 配布元としても受信者としても、 数学的に「途中で誰かが書き換えていない」と確信できる仕組みを身につけられます。
🍰 まずはやさしく
手紙に付ける封蝋(ふうろう)のデジタル版です。
本人が書いたことと、中身が変わっていないことを示します。
実印と印鑑証明書をセットで使う感覚に近いです。
暗号化との違いに注目して、直感的な仕組みを読みます。
「封蝋 (sealing wax) のデジタル版」です。 中世の手紙では書いた人だけが持つ印章を蝋に押し付け、 ① 「本人が書いた」、 ② 「開封されていない」 を示しました。 電子署名は同じ役割をデジタルで担います。
身近なアナロジー:
暗号化との対比 (超重要):
| 場面 | 使う鍵 | 目的 |
|---|---|---|
| 暗号化(送る側) | 受信者の公開鍵 | 受信者しか復号できない(秘密性) |
| 復号(受ける側) | 受信者の秘密鍵 | 中身を読む |
| 署名(送る側) | 送信者の秘密鍵 | 本人にしか作れない印 |
| 検証(受ける側) | 送信者の公開鍵 | 本人が作ったか確認 |
暗号化は「受信者の鍵ペア」、 署名は「送信者の鍵ペア」と覚えてください。 鍵の方向が逆という点が初学者が必ずつまずくポイントです。
3 つの保証を分解: 本人性 (誰がそれを送ったか)、 完全性 (改ざんされていないか)、 否認防止 (送ったことを後から否定できない)。 これら 3 つを 1 つの仕組みで同時に満たすため、 電子契約・電子納税・ブロックチェーンの全てが電子署名に依存します。
SSDSE のような公的統計データでも、 署名付きで配布されると「これは確かに統計局が作ったものですよ」「途中で誰かが書き換えていませんよ」と数学的に保証できます。 1 ビット書き換えればハッシュは全く別物になる「雪崩効果」のおかげです。 これがあるからこそ、 研究者はネットワーク経由で受け取った CSV を疑わずに分析を始められます。
電子署名と紙の判子の違い:紙の印鑑は同じ印影を何度押しても同じになりますが、 電子署名は対象データが変われば全く別の値になります。 これにより「この署名はこの文書専用」になり、 他文書への流用が物理的に不可能になります。
処理の流れ図 (ASCII):
[送信者]
m (SSDSE-B-2026.csv)
│
├─ H(m) = SHA-256 ハッシュ
│
├─ Sign(sk, H(m)) = σ (署名値)
│
└─ 送る: {{m, σ, 公開鍵証明書}}
│
▼
[受信者]
① 証明書を CA で検証
② H(m) を再計算
③ Verify(pk, m, σ) == 1 か確認
└─ OK → 信頼して分析開始
└─ NG → 改ざん or 鍵不正
電子署名は「秘密鍵で署名 / 公開鍵で検証」する公開鍵暗号の応用。 ここでは「署名と検証の流れ」「ハッシュ関数の役割」「PKI 信頼チェーン」を視覚化する。
封蝋 (指紋) のデジタル版を手で動かして体感します。 「メッセージ→簡易ハッシュを計算→秘密鍵で署名→受信側が公開鍵で検証」という流れを追い、 受信メッセージを1 文字でも改ざんするとハッシュが激変し検証が失敗する様子を確かめられます。
--未署名受信メッセージを編集すると「通信路での改ざん」を再現できます。 1 文字変えるだけでハッシュがどう変わるか観察しましょう。
--検証が成功すると、 電子署名の 3 つの性質 が同時に満たされます:
仕組みの直感は封蝋 + 指紋: ハッシュが「文書の指紋」、 秘密鍵での署名が「本人だけが押せる封蝋」です。 公開鍵で公開鍵基盤 (PKI) の信頼チェーン (図 C)を辿れば、 その公開鍵が本物かまで確かめられます。 なお実際の鍵は公開鍵から秘密鍵を逆算できませんが、 この玩具は分かりやすさ優先で加法の対にしてあります。 発展は RSA-PSS / ECDSA / EdDSA・タイムスタンプ・ブロックチェーン を参照。 落とし穴は鍵の秘匿・ハッシュ衝突・証明書の信頼連鎖です。
🍰 まずはやさしく
数学的なルールで決まった署名の仕組みです。
鍵を使って、誰でも正しさを判定できるようにします。
パスポートのスタンプのように、特別な鍵で印を付けます。
ここでは署名を作るための計算式について読みます。
電子署名スキームは 3 つのアルゴリズム の組 $(\mathsf{KeyGen}, \mathsf{Sign}, \mathsf{Verify})$ で定義されます。
① 鍵生成:
$\lambda$ はセキュリティパラメータ(鍵長)。 $pk$ は公開鍵、 $sk$ は秘密鍵。
② 署名生成:
$m$ はメッセージ、 $H(\cdot)$ は SHA-256 などの暗号学的ハッシュ関数、 $\sigma$ は署名値。
③ 検証:
RSA 署名の具体例:
$N = pq$ は大きな合成数、 $(e, N)$ が公開鍵、 $d$ が秘密鍵で $ed \equiv 1 \pmod{\phi(N)}$。
ECDSA (楕円曲線署名):
$G$ は曲線の基点、 $d$ は秘密鍵、 $k$ は使い捨て乱数。 $(r, s)$ が署名値。
EdDSA (Ed25519):
決定的に $r$ を生成する。 サイドチャネル攻撃に強い。
セキュリティ要件 (EUF-CMA):
適応的選択メッセージ攻撃下で、 鍵を持たない攻撃者が新しい署名 $(m^*, \sigma^*)$ を偽造できる確率は無視できる。
ハッシュ関数の必要性質:
SHA-256 はいずれも満たしますが、 MD5・SHA-1 は ③ が破られているため電子署名には使えません。
RSA 署名の数学を一段深掘りします。 鍵生成は次の手順で:
署名・検証の正しさは Euler の定理 $a^{\phi(n)} \equiv 1 \pmod n$ から導かれます: $\sigma^e = (H(m)^d)^e = H(m)^{ed} = H(m)^{k\phi(n)+1} = H(m) \cdot 1^k = H(m) \pmod n$。 つまり $\sigma^e \mod n = H(m)$ で検証が成立。 セキュリティは「$n$ を素因数分解する困難さ」に依存します。 $n = 2048$ ビットなら現在の古典コンピュータでは現実時間内に解けない、 とされます。
電子署名は単独のバイナリではなく、 文書フォーマット内に埋め込まれて運用されることが多い:
PDF/A-3 や PAdES (PDF Advanced Electronic Signatures, ETSI 規格) で電子署名を埋め込み。 Adobe Acrobat が読むと「署名済み」 と表示。 改ざんがあれば即座に検知。
電子メール (S/MIME) や任意ファイルの署名で使われる共通形式。 「データ + 署名 + 証明書チェーン」 を 1 つのバイナリに包装。 OpenSSL の cms コマンドで操作可能。
XML 文書の特定要素に署名する。 SAML 認証や SOAP セキュリティで使われる。 ETSI XAdES が拡張仕様。
JWT (JSON Web Token) の基盤。 認証・認可情報を電子署名で改ざん防止。 OAuth 2.0 / OpenID Connect の核心。
CMS Advanced Electronic Signatures。 PKCS#7/CMS の拡張で、 長期署名 (LTV) を含む。 政府文書で使われる。
| ツール | 言語 | 用途 |
|---|---|---|
| OpenSSL | CLI / C | 汎用、 鍵生成・署名・検証・X.509 |
| cryptography (Python) | Python | 高水準 API、 PSS, Ed25519 等対応 |
| PyCryptodome | Python | レガシー、 学習用にも良い |
| GPG / GnuPG | CLI | OpenPGP、 メール暗号、 ファイル署名 |
| pyHanko | Python | PDF 署名 (PAdES) 専用 |
| Java JCA/JCE | Java | エンタープライズ向け |
| Bouncy Castle | Java/C# | 独立系の包括的ライブラリ |
| go-crypto | Go | クラウドネイティブで人気 |
| Web Crypto API | JavaScript | ブラウザ標準 |
| YubiKey / HSM | ハードウェア | 秘密鍵を物理的に守る |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | # RSA 鍵ペア生成 openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem # Ed25519 鍵ペア生成 openssl genpkey -algorithm ed25519 -out ed_private.pem openssl pkey -in ed_private.pem -pubout -out ed_public.pem # ファイル署名 (RSA-PSS + SHA-256) openssl dgst -sha256 -sign private.pem -sigopt rsa_padding_mode:pss \\ -out sig.bin data/raw/SSDSE-B-2026.csv # 検証 openssl dgst -sha256 -verify public.pem -sigopt rsa_padding_mode:pss \\ -signature sig.bin data/raw/SSDSE-B-2026.csv # 自己署名 X.509 証明書 openssl req -x509 -new -key private.pem -days 365 -out cert.pem \\ -subj '/C=JP/O=SSDSE Test/CN=test.example.jp' # 証明書の中身を確認 openssl x509 -in cert.pem -text -noout |
2016 年運用開始のマイナンバーカードは IC チップに 2 つの RSA 2048 ビット鍵を内蔵。 (1) 署名用 (e-Tax 等での電子署名)、 (2) 利用者証明用 (本人確認)。 公的個人認証サービス JPKI として、 民間サービスでも利用可能。
2014 年に欧州議会が採択。 「Advanced Electronic Signature (AES)」 「Qualified Electronic Signature (QES)」 を法的に定義。 加盟国間で相互認証。 民間契約でも紙の押印と同等の効力。
2000 年に成立。 電子署名と紙の署名を同等扱い。 DocuSign、 Adobe Sign が普及の中心。 連邦政府文書でも採用。
電子政府の先進国。 全国民が ID カードを持ち、 投票・税申告・医療記録すべて電子署名で運用。 X-Road という ID 連携基盤を構築。
国家暗号管理局が認証局を統括。 SM2/SM3/SM9 という独自の暗号アルゴリズム。 アリババ・テンセントが民間サービスを展開。
量子コンピュータが大規模化すると、 Shor のアルゴリズムで RSA / ECDSA が破られます。 NIST は 2022 年に Dilithium (格子問題ベース)、 Falcon (格子問題ベース、 署名サイズ小)、 SPHINCS+ (ハッシュベース、 最も安全だが遅い) を標準化候補に選定。 2024 年に FIPS 204/205/206 として正式公開予定。
既存システムの移行は 2025-2035 年に進行中。 「暗号アジリティ (Cryptographic Agility)」 つまり「アルゴリズムを柔軟に差し替えられる設計」 が重要キーワードになっています。 SSDSE-B-2026 のようなデータに 30 年後にも検証可能な署名を付けたいなら、 今からポスト量子の準備をしておくのが賢明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization import hashlib # 0. 公開機関の鍵ペア (実際は HSM 等で保護) sk = rsa.generate_private_key(65537, 2048) pk = sk.public_key() # 1. SHA-256 ハッシュを計算 with open('data/raw/SSDSE-B-2026.csv','rb') as f: data = f.read() hash_hex = hashlib.sha256(data).hexdigest() print(f'SHA-256: {hash_hex}') # 2. 署名生成 sig = sk.sign(data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256()) # 3. 配布パッケージ作成 (CSV + ハッシュ + 署名 + 公開鍵) import zipfile with zipfile.ZipFile('outputs/SSDSE-B-2026-signed.zip','w') as zf: zf.write('data/raw/SSDSE-B-2026.csv', 'data/raw/SSDSE-B-2026.csv') zf.writestr('SHA256SUM', hash_hex + ' SSDSE-B-2026.csv') zf.writestr('signature.bin', sig) pem = pk.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo) zf.writestr('public_key.pem', pem) print('配布パッケージ完成') print('受信者は public_key.pem + signature.bin + SSDSE-B-2026.csv で検証可能') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from cryptography.hazmat.primitives.serialization import load_pem_public_key with open('public_key.pem','rb') as f: pk = load_pem_public_key(f.read()) with open('SSDSE-B-2026.csv','rb') as f: data = f.read() with open('signature.bin','rb') as f: sig = f.read() try: pk.verify(sig, data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256()) print('✅ 署名検証成功 - データは公的機関が発行した正規版です') except Exception: print('❌ 署名検証失敗 - データが改ざんされているか、 鍵が間違っています') |
この仕組みは Linux ディストリビューションの ISO ファイル、 Apple のアプリ署名、 e-Tax の申告書など、 ありとあらゆる場面で同じパターンが使われている。
Web API の認証で使われる JWT は、 電子署名を使った最も身近な例です。 「ヘッダ.ペイロード.署名」 の 3 つを . でつなげた文字列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import jwt # pip install PyJWT from cryptography.hazmat.primitives.asymmetric import rsa sk = rsa.generate_private_key(65537, 2048) pk = sk.public_key() # 1. ペイロード (主張) payload = { 'user_id': 'user_123', 'email': 'test@example.com', 'role': 'admin', 'iat': 1700000000, 'exp': 1700003600, # 1時間有効 } # 2. 署名付き JWT 生成 (RS256 アルゴリズム) token = jwt.encode(payload, sk, algorithm='RS256') print(token[:80] + '...') # 3. 検証 try: decoded = jwt.decode(token, pk, algorithms=['RS256']) print('✅ JWT 検証成功:', decoded) except jwt.ExpiredSignatureError: print('❌ 期限切れ') except jwt.InvalidSignatureError: print('❌ 署名不正 (改ざん)') |
OAuth 2.0 / OpenID Connect は JWT を中核として動作。 改ざんできないため、 サーバー側でセッション情報を保存しなくても安全に認証情報を渡せる。
ルート CA (Root Certificate) │ (自己署名) │ ▼ 中間 CA (Intermediate CA) │ (ルート CA が署名) │ ▼ エンドエンティティ証明書 (Server/User Certificate) │ (中間 CA が署名) │ ▼ あなたの公開鍵 検証の流れ: 1. エンド証明書の署名を中間 CA の公開鍵で検証 → OK 2. 中間 CA 証明書の署名をルート CA の公開鍵で検証 → OK 3. ルート CA は OS/ブラウザの信頼ストアに登録済み → OK 4. 失効リスト (CRL/OCSP) を確認 → 失効していない → OK → 全 OK なら「公開鍵は真正」 と確認できる
この階層構造のおかげで、 個別の公開鍵を一つ一つ信頼する必要なく、 「信頼できるルート CA から派生したもの」 として一括検証できる。
PKCS#1 v1.5 パディングの脆弱性を利用したパディングオラクル攻撃。 SSL/TLS サーバーへの数百万回のクエリで秘密鍵を抽出可能。 防御: RSA-PSS への移行、 一定時間応答 (timing attack 対策)。
Google が SHA-1 の衝突を初めて実証。 同じ SHA-1 値を持つ異なる PDF を作成し、 SHA-1 署名なら同じ署名値で 2 つの内容を「同一」と認証可能。 防御: SHA-256 以上を使う。
Shor のアルゴリズムが大規模量子計算機で実用化されると RSA / ECDSA が崩壊。 2030-2040 年に深刻化と予想。 防御: ポスト量子暗号 (Dilithium 等) への計画的移行。
消費電力、 電磁波、 処理時間などの「副次的情報」 から秘密鍵を推定する攻撃。 スマートカードでも問題に。 防御: コンスタント時間アルゴリズム、 ノイズ注入、 ハードウェア対策。
攻撃者が公開鍵を入れ替えて受信者に渡す。 「これが本物の公開鍵」 と偽装。 防御: X.509 証明書、 Certificate Transparency、 鍵ピンニング。
1 2 3 4 5 6 7 8 9 10 11 | # Apple Developer 証明書でアプリに署名 codesign --force --deep --sign 'Developer ID Application: Your Name' MyApp.app # 公証 (Notarization) — Apple の中央サーバーに送信 xcrun notarytool submit MyApp.zip --apple-id user@example.com --team-id ABC123 --wait # 公証スタンプを貼る xcrun stapler staple MyApp.app # 検証 spctl --assess --verbose=4 MyApp.app |
1 2 3 4 5 | # SignTool で署名 (Windows SDK) signtool sign /fd SHA256 /a /tr http://timestamp.digicert.com /td SHA256 MyApp.exe # 検証 signtool verify /pa /v MyApp.exe |
1 2 3 4 5 6 7 8 9 10 11 12 | # GPG 鍵生成 gpg --gen-key # Git に鍵を登録 git config --global user.signingkey YOUR_KEY_ID git config --global commit.gpgsign true # コミット (自動的に署名される) git commit -m 'feat: add SSDSE analysis' # GitHub では 'Verified' バッジが付く git log --show-signature |
1 2 3 4 | # cosign で OCI 互換のコンテナ署名 cosign generate-key-pair # 鍵ペア生成 cosign sign --key cosign.key registry.io/myimg:v1 cosign verify --key cosign.pub registry.io/myimg:v1 |
1 2 3 | # メール本文に S/MIME 署名を付ける openssl smime -sign -in mail.txt -signer cert.pem -inkey key.pem -out signed.eml # Outlook / Apple Mail で 'verified' マークが付く |
日本の電子署名法 (正式名「電子署名及び認証業務に関する法律」、 2001 年施行) は、 一定の要件を満たす電子署名に押印と同等の法的効力を与えています。 主要ポイント:
これにより、 紙の契約書を作らずに、 クラウドサインや DocuSign で締結した契約も法的効力を持ちます。 ただし、 不動産登記、 公正証書など一部の文書は依然紙が必要。 SSDSE-B のような公開データに署名を付けることに法的義務はありませんが、 オープンデータの信頼性を高める手段としては有効です。
電子署名は「信頼を数学に置き換える技術」 です。 紙の印鑑は人間の目で見て「これは山田さんの印鑑だ」 と判断していました。 電子署名はその判断を、 公開鍵暗号という数学的構造に委ねます。 大規模合成数の素因数分解の困難性、 楕円曲線上の離散対数の困難性 ─ これらの「計算量的困難性」 が信頼の基盤になっているのです。
SSDSE-B-2026 のような公的データに署名する習慣を持てば、 「このデータは確かに公的機関が発行した正規版である」 ことを暗号学的に証明できます。 オープンデータ時代の信頼基盤として、 電子署名は今後ますます重要になります。 公開鍵暗号の数学を学び、 OpenSSL や Python の cryptography ライブラリで実際に手を動かす ─ この経験が、 デジタル社会を理解する基礎体力になります。
PKI (Public Key Infrastructure) は電子署名・暗号化の社会的基盤です。 構成要素:
日本の代表的な認証局: 日本電子認証 (NDN)、 セコムトラストシステムズ、 GMOグローバルサイン、 JPRS、 サイバートラスト。 政府関連は GPKI (政府認証基盤)、 LGPKI (地方公共団体認証基盤) が運用されています。
PDF への電子署名は ISO 32000-2 と PAdES (ETSI EN 319 142) で規格化されています。 内部構造:
これにより、 数十年経って CA が消滅しても、 PDF 単体で署名検証が可能になります。 公文書のアーカイブで重要な特性。
マイナンバーカードに搭載される RSA 2048 鍵を使った認証基盤。 「署名用電子証明書」 (5 年有効、 引っ越しで失効) と「利用者証明用電子証明書」 (本人確認用) の 2 種類。 民間サービスでも利用可能 (例: クラウドサイン、 LIFULL HOME'S 不動産)。
政府機関の職員が公文書に電子署名するための基盤。 中央府省、 独立行政法人で運用。
都道府県・市区町村の職員向け認証基盤。 e-Tax / eLTAX で住民税・固定資産税の電子申告に使用。
セコムトラストシステムズ、 GMOグローバルサイン、 サイバートラスト、 日本電子認証 (NDN) など。 法人向けの SSL/TLS 証明書、 コードサイニング証明書を発行。 Adobe Approved Trust List (AATL) や Microsoft Root Certificate Program に登録された CA も多い。
本セクションでは、 公開機関の立場で SSDSE-B-2026 を電子署名付きで配布する場合の完全フローを示します。 そのまま教育・研究用途で活用可能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | """ SSDSE-B-2026 配布パッケージ生成スクリプト - データに RSA-PSS-SHA256 署名を付与 - X.509 自己署名証明書を同梱 - SHA-256 ハッシュも個別に生成 - ZIP に全部まとめる """ import os, hashlib, zipfile, datetime from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization from cryptography import x509 from cryptography.x509.oid import NameOID from datetime import timedelta OUT_DIR = 'outputs/ssdse-signed-package' os.makedirs(OUT_DIR, exist_ok=True) # 1. 鍵ペア生成 sk = rsa.generate_private_key(65537, 2048) pk = sk.public_key() # 2. 自己署名 X.509 証明書 (発行者は仮想公的機関) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, 'JP'), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, 'Tokyo'), x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'SSDSE Distribution Authority (Demo)'), x509.NameAttribute(NameOID.COMMON_NAME, 'ssdse-distribution-demo'), ]) cert = (x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(pk) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.datetime.utcnow()) .not_valid_after(datetime.datetime.utcnow() + timedelta(days=3650)) # 10年 .add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True) .add_extension(x509.KeyUsage( digital_signature=True, content_commitment=True, key_encipherment=False, data_encipherment=False, key_agreement=False, key_cert_sign=True, crl_sign=True, encipher_only=False, decipher_only=False), critical=True) .sign(sk, hashes.SHA256())) # 3. データ読み込み with open('data/raw/SSDSE-B-2026.csv','rb') as f: data = f.read() # 4. SHA-256 ハッシュ sha256_hex = hashlib.sha256(data).hexdigest() # 5. RSA-PSS 署名 signature = sk.sign( data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256(), ) # 6. パッケージ書き出し with open(f'{OUT_DIR}/SSDSE-B-2026.csv','wb') as f: f.write(data) with open(f'{OUT_DIR}/SHA256SUMS','w') as f: f.write(f'{sha256_hex} SSDSE-B-2026.csv\n') with open(f'{OUT_DIR}/signature.bin','wb') as f: f.write(signature) with open(f'{OUT_DIR}/cert.pem','wb') as f: f.write(cert.public_bytes(serialization.Encoding.PEM)) with open(f'{OUT_DIR}/public_key.pem','wb') as f: f.write(pk.public_bytes(serialization.Encoding.PEM, serialization.PublicFormat.SubjectPublicKeyInfo)) # 7. 検証スクリプトも同梱 verify_script = ''' import hashlib from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes from cryptography.x509 import load_pem_x509_certificate with open('data/raw/SSDSE-B-2026.csv','rb') as f: data = f.read() with open('signature.bin','rb') as f: sig = f.read() with open('cert.pem','rb') as f: cert = load_pem_x509_certificate(f.read()) assert hashlib.sha256(data).hexdigest() == open('SHA256SUMS').read().split()[0] cert.public_key().verify(sig, data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256()) print('OK: 検証成功') ''' with open(f'{OUT_DIR}/verify.py','w') as f: f.write(verify_script) # 8. ZIP に圧縮 with zipfile.ZipFile(f'{OUT_DIR}.zip','w', zipfile.ZIP_DEFLATED) as z: for fname in os.listdir(OUT_DIR): z.write(f'{OUT_DIR}/{fname}', fname) print('完成:', f'{OUT_DIR}.zip') print('受信者は ZIP を展開し python verify.py で検証可能') |
この一連のスクリプトを応用すれば、 自分のチームのレポート・データセットにも署名を付けて配布できる。 ジャーナル投稿の補足資料、 学位論文のデータ、 自治体オープンデータ、 etc.
電子署名は、 現代のデジタル社会を支える「見えないインフラ」 です。 普段意識しなくても、 ブラウザのアドレスバーの錠前アイコン、 マイナンバーカードの IC、 銀行アプリの認証、 自動アップデートの正当性検証、 ブロックチェーンの取引 ─ あらゆる場面で電子署名が黙々と働いています。
SSDSE-B-2026 のような公開データに電子署名を付ける習慣を持てば、 オープンデータの信頼性が一段上がります。 公開鍵暗号、 ハッシュ関数、 X.509 証明書、 PKI ─ これらの技術スタックを学び、 OpenSSL や Python の cryptography ライブラリで実際に手を動かす経験は、 セキュリティ感覚を養う最良の手段です。
本ページの内容をすべて実践すれば、 電子署名に関する基礎知識と実装能力が一通り身につきます。 さらに専門領域に進みたければ、 IPA の暗号関連ガイドライン、 NIST 標準文書、 そしてポスト量子暗号の最新動向を追ってください。 デジタル社会の信頼を守る、 重要な役割を担うエンジニア / 研究者になれます。
NIST が 2022 年に選定したポスト量子署名アルゴリズム:
| アルゴリズム | 基盤問題 | 鍵サイズ | 署名サイズ | 速度 |
|---|---|---|---|---|
| Dilithium | Module Lattice | 1.5 KB | 2.4 KB | 速い |
| Falcon | NTRU Lattice | 1.3 KB | 0.7 KB | 中 |
| SPHINCS+ | ハッシュベース | 32 B | 8-50 KB | 遅い |
| 比較: RSA-2048 | 素因数分解 | 256 B | 256 B | 中 |
| 比較: Ed25519 | 楕円曲線離散対数 | 32 B | 64 B | 最速 |
ポスト量子の鍵・署名サイズは現行より大きく、 通信量・ストレージへの影響を考慮した設計が必要。 ハイブリッド (ECDSA + Dilithium 両方で署名) で移行期を乗り切るアプローチが現実的。
電子署名は概念だけ学んでも身につきません。 実際に鍵を生成し、 ファイルに署名し、 検証する経験を通じて初めて、 PKI のありがたみと注意点が体感できます。 まずは 1 つ動かしてみましょう。
各地域の電子署名関連法制度を整理:
| 国・地域 | 主要法令 | 特徴 |
|---|---|---|
| 日本 | 電子署名法 (2001) | マイナンバーカード普及中、 事業者署名型OK |
| EU | eIDAS 規則 (2014) | AES/QES の階層、 加盟国相互認証 |
| 米国 | ESIGN Act (2000) | 州ごとに UETA、 民間活発 |
| エストニア | 電子身分証法 | 完全電子政府、 全国民 ID カード |
| 中国 | 電子署名法 (2005) | 独自暗号 SM2/SM3、 国家統制 |
| インド | IT Act (2000) | Aadhaar との連動、 eSign Service |
| 韓国 | 電子署名法 (2020 全面改正) | 公認認証制度廃止、 民間多様化 |
電子署名は「数学が人類社会の信頼を支える」 という、 20 世紀後半の偉大な発明の 1 つです。 1976 年の Diffie-Hellman 論文から半世紀、 私たちはインターネット、 ブロックチェーン、 マイナンバー、 クラウド契約 ─ あらゆる場面で公開鍵暗号の恩恵を受けています。 SSDSE-B-2026 のようなオープンデータも、 電子署名が支える信頼の生態系の一部です。 この技術を学び、 自分のプロジェクトに組み込むことで、 デジタル社会の信頼構築に貢献できます。 まずは小さなファイルに署名を付けることから、 始めてみてください。
== を使うとタイミング攻撃に晒される。 hmac.compare_digest を使う。random.random() は擬似乱数で暗号には NG。 secrets や os.urandom を使う。-aes256)。本ページの全内容を踏まえた「実プロジェクトで電子署名を導入する際の確認事項」 を 1 ページにまとめます:
これらを 1 つずつクリアしていけば、 セキュアな電子署名運用が実現できます。 一気に全部やる必要はなく、 段階的に整備するのが現実的です。
| 記号 | 読み方 | 意味・例 |
|---|---|---|
| $m$ | message | 署名対象データ。 SSDSE-B-2026.csv の全バイト列 |
| $H(m)$ | hash | SHA-256 等で得られる 256 bit の指紋 |
| $sk, pk$ | secret/public key | $sk$ は本人だけが保管、 $pk$ は配布可 |
| $\sigma$ | signature | 署名値。 RSA-2048 なら 256 bytes |
| $N, e, d$ | RSA modulus / public / private exp. | $(e, N)$ を公開、 $d$ は厳重保管 |
| $\phi(N)$ | Euler's totient | $\phi(pq) = (p-1)(q-1)$ |
| $G, n$ | 基点 / 位数 | 楕円曲線の点とその位数。 secp256r1 など |
| $k$ | nonce | 署名ごとに新しく取る乱数。 再利用厳禁 |
| $\ell$ | order (EdDSA) | 群の位数 |
| $\mathsf{negl}(\lambda)$ | negligible | $\lambda$ について多項式逆数より速く 0 へ |
数式の核心は 「秘密鍵でしか作れない値だが、 公開鍵で誰でも検証できる」 という非対称性です。 これが本人性と否認防止を支える数学的根拠です。
$\sigma^e \equiv H(m) \pmod N$ という式は、 RSA の世界で「秘密鍵 $d$ で作った署名は、 公開鍵 $e$ で戻すと元のハッシュに戻る」という関係を表しています。 もし $\sigma$ を秘密鍵なしに作ろうとすると、 大きな数 $N$ を素因数分解する必要があり、 2048 bit RSA であれば現実時間では不可能です。
同様に ECDSA では、 楕円曲線上の離散対数問題が困難であることを安全性の根拠としています。 ECDSA の方が鍵長が短いのに同等の安全性を持つため、 モバイル・IoT で広く使われます。
"確率 $\mathsf{negl}(\lambda)$" という表現は暗号理論で頻出します。 これは「鍵長を増やせば、 攻撃成功確率は急速に 0 に近づく」ことを意味し、 セキュリティパラメータ $\lambda$ を大きくすれば計算量上現実的に破られない、 という保証になります。
署名検証の数式を「言葉」で読むと:
もし攻撃者が $m$ を改ざんしたら、 ステップ 1 で計算するハッシュが変わり、 ステップ 3 で不一致になる。 もし攻撃者が $\sigma$ を改ざんしたら、 ステップ 2 の結果がランダムになり、 やはり不一致になる。 だから両方守られます。
電子署名 (Digital Signature) は「送信者だけが作れて、 誰でも検証できる」 という非対称性を持つ暗号技術です。 RSA 署名の最も基本的な形は:$$\sigma = H(m)^d \mod n, \quad \text{検証}: \sigma^e \equiv H(m) \pmod n$$ ここで $m$ はメッセージ (例: 文書)、 $H$ はハッシュ関数 (SHA-256 等)、 $(e, n)$ は公開鍵、 $d$ は秘密鍵、 $\sigma$ が署名値です。 署名者は秘密鍵 $d$ で $H(m)^d$ を計算し署名 $\sigma$ を生成。 受信者は公開鍵 $(e, n)$ で $\sigma^e$ を計算し $H(m)$ と一致するか確認。 一致すれば「秘密鍵保有者が確かに $m$ に署名した」 と検証成功となります。
$H$ はメッセージ $m$ を固定長 (256 ビット = 32 バイト等) のダイジェスト値に圧縮する関数。 SHA-256、 SHA-3-256、 BLAKE2 などが現代的選択肢。 重要性質は (a) 衝突困難性 ($H(m_1) = H(m_2)$ となる $m_1, m_2$ を見つけるのが計算上困難)、 (b) 第二原像困難性 (与えられた $H(m)$ から $m'$ を逆算困難)、 (c) 一方向性 ($H$ から $m$ を逆算困難)。 なぜハッシュを介すのか? → 直接 $m^d \mod n$ にすると大きすぎる文書も小さすぎる文書も問題になるため。 $H$ を介すことで常に固定サイズで処理でき、 セキュリティも向上します。
秘密鍵 $d$ は署名者だけが持つ「印鑑のような存在」。 RSA では $d$ は $ed \equiv 1 \pmod{\phi(n)}$ を満たす整数 ($\phi$ はオイラー関数)。 $d$ から $n = pq$ の素因数 $p, q$ を計算的に逆算可能で、 そこから他の秘密情報も漏れます。 そのため秘密鍵の保護がセキュリティの要 ─ ハードウェアセキュリティモジュール (HSM)、 スマートカード、 マイナンバーカード IC チップに格納するのが定石。 もし $d$ が漏洩したら、 攻撃者は任意の文書に「あなたの名前」 で署名できてしまう (なりすまし)。
公開鍵 $(e, n)$ は誰にでも公開して構わない情報。 $e = 65537$ (= $2^{16}+1$) が標準的な選択。 $n$ は 2048 ビット (256 バイト) 程度の合成数。 公開鍵は公開鍵証明書 (X.509 形式)として CA (認証局) に登録され、 「この公開鍵は確かに山田太郎のもの」 と裏付けを得ます。 受信者は X.509 証明書チェーンを辿って CA の署名を確認することで、 公開鍵の真正性を保証。 これが PKI (Public Key Infrastructure) の根幹です。
$\sigma$ は数値 (バイナリ列) ですが、 単独で意味を持つわけではなく、 元メッセージ $m$ と公開鍵 $(e, n)$ の3 つ揃って初めて検証できます。 PDF, S/MIME, JSON Web Signature (JWS) などのフォーマットは「文書 + 署名値 + 証明書」 をまとめてパッケージ化します。 検証は (1) $\sigma^e \mod n$ を計算、 (2) $H(m)$ を計算、 (3) 一致を確認、 の 3 ステップ。 検証側は秘密鍵を持っていなくても、 公開情報だけで「本物の署名か」 を確認できる ─ これが「非対称性」の本質です。
SSDSE-B-2026 を配布する立場で、 ファイルが配布途中で改ざんされていないことを保証するシナリオを考えます。
RSA-1024 の小さな手計算例 ($p=61, q=53$):
$N = 61 \times 53 = 3233$, $\phi(N) = 60 \times 52 = 3120$, 公開指数 $e = 17$, 秘密指数 $d = 2753$ (拡張ユークリッドより)。
仮にハッシュ値が $H(m) = 123$ だったとすると:
検証側は $\sigma^{e} = 2746^{17} \bmod 3233 = 123$ を計算し、 これがハッシュと一致するので OK。
| ファイル | 想定 SHA-256 (例) | 検証結果 |
|---|---|---|
| SSDSE-B-2026.csv (正規) | 4f3a...c12e | ✅ 一致 → 改ざんなし |
| SSDSE-B-2026.csv (北海道人口 +1) | 9b71...8e02 | ❌ 不一致 → 改ざん検出 |
| SSDSE-B-2026.csv (BOM 追加のみ) | 2c44...11ab | ❌ 不一致 (BOM もデータ) |
| SSDSE-B-2026.csv (改行を CRLF→LF) | 87bc...4f29 | ❌ 不一致 (改行もバイト) |
「1 ビット変えてもハッシュは大幅に変わる」性質を 雪崩効果 (avalanche effect) と呼びます。
| 鍵長 / アルゴリズム | 想定セキュリティ | 署名サイズ | 署名速度 (1 ファイル) | 用途 |
|---|---|---|---|---|
| RSA-2048 | 112 bit 相当 | 256 B | ~5 ms | TLS 証明書・電子契約 |
| RSA-3072 | 128 bit 相当 | 384 B | ~15 ms | 長期保管 |
| RSA-4096 | 140 bit 相当 | 512 B | ~40 ms | 政府機関の長期署名 |
| ECDSA-P256 | 128 bit 相当 | ~72 B | ~1 ms | モバイル・IoT |
| EdDSA (Ed25519) | 128 bit 相当 | 64 B | ~0.5 ms | SSH / Tor |
| Dilithium2 | ポスト量子 NIST L2 | 2.4 KB | ~3 ms | 長期 (量子計算機対策) |
SSDSE-B-2026 は数 MB のファイルなので、 SHA-256 計算は 100 ms 以下、 署名生成も 10 ms 以下で済みます。 通信オーバヘッドは無視できる軽さです。
SSDSE-B-2026 の都道府県別 「行ハッシュ」 でデータ品質保証する例:47 都道府県 × 12 年 = 564 行に対して、 各行をシリアライズしてハッシュ化すれば、 「どの都道府県のどの年度が書き換わったか」 まで特定できます。 配布時に row_fingerprint.csv を添付しておくと、 監査時にとても便利です。
data/raw/SSDSE-B-2026.csv (約 200 KB の統計データ) + 自前生成の RSA 鍵ペア。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization # 1. RSA 鍵ペア生成 (2048 bit) private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() # 2. ファイル読み込み with open('data/raw/SSDSE-B-2026.csv','rb') as f: data = f.read() print(f'ファイルサイズ: {len(data)/1024:.1f} KB') # 3. 署名生成 (PSS パディング + SHA-256) signature = private_key.sign( data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256(), ) print(f'署名サイズ: {len(signature)} バイト') # 256 バイト (2048 ビット鍵で) # 4. 公開鍵で検証 try: public_key.verify( signature, data, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256(), ) print('✅ 検証成功 - 改ざんなし') except Exception as e: print(f'❌ 検証失敗: {e}') # 5. 改ざんを試してみる tampered = data[:-10] + b'TAMPERED!!' try: public_key.verify(signature, tampered, padding.PSS(...), hashes.SHA256()) except Exception: print('改ざんデータは検証失敗 (期待通り)') |
| 業界 | 電子署名の役割 | 技術 |
|---|---|---|
| 金融 (証券取引) | 電子契約・取引指図書に署名。 改ざん不能で監査に耐える。 SWIFT / 金融機関の中核。 | RSA-PSS, ECDSA |
| 政府・公文書 | マイナンバーカードでの電子申請。 e-Tax, e-Gov の本人確認。 | RSA, PKI |
| ソフトウェア配布 | アプリのコード署名。 macOS の Notarization、 Windows の Authenticode、 Linux の deb/rpm 署名。 | RSA, GPG |
| Web (HTTPS) | TLS 証明書で Web サイトの真正性を保証。 全インターネット通信の基盤。 | ECDSA, RSA |
| ブロックチェーン | Bitcoin / Ethereum のトランザクション署名。 「鍵がなければ送金できない」 の核心。 | ECDSA (secp256k1) |
| 公的統計 | SSDSE 等の公開データに署名を付与すれば、 「正規データ」 を暗号学的に証明可能。 | RSA-PSS, GPG |
| アルゴリズム | 鍵長 | 署名サイズ | 速度 | 用途 |
|---|---|---|---|---|
| RSA-PSS (2048) | 2048 | 256 B | 中 | 汎用、 標準 |
| RSA-PKCS#1 v1.5 | 2048 | 256 B | 中 | レガシー (推奨せず) |
| RSA-PSS (4096) | 4096 | 512 B | 遅い | 高セキュリティ |
| ECDSA (P-256) | 256 | 64 B | 速い | TLS, モバイル |
| ECDSA (secp256k1) | 256 | 64 B | 速い | Bitcoin, Ethereum |
| Ed25519 | 256 | 64 B | 最速 | SSH, 現代標準 |
| DSA | 2048 | ~50 B | 中 | 非推奨 |
| Dilithium (PQC) | 数 KB | ~2 KB | 速い | 耐量子標準候補 |
現代の新規システムは Ed25519 か ECDSA P-256 を推奨。 RSA は互換性のために残る。 将来は Dilithium 等のポスト量子暗号へ移行予定。
openssl dgst -sha256 -sign private.pem -out sig.bin data.csv でコマンドライン版も試せ。private.pem をうっかり public リポジトリにコミット。 数分以内に GitHub Secret Scanner で検知され、 SSH キーなら 1 時間以内に攻撃が来る。 対策: .gitignore 必須、 git-secrets / pre-commit hook で防止。
古い PKCS#1 v1.5 パディングは攻撃手法 (Bleichenbacher 攻撃) があり、 秘密鍵が漏れる可能性。 対策: RSA-PSS に移行、 既存システムは段階的に。
2017 年に SHA-1 衝突が実証 (SHAttered)。 SHA-1 ベースの署名は信頼できない。 対策: SHA-256 以上に切り替え。 多くの CA は 2017 年に強制移行済み。
「これが署名者の公開鍵だよ」 と渡された鍵を盲信すると、 中間者攻撃者の鍵かもしれない。 対策: X.509 証明書チェーン、 Certificate Transparency、 ピンニング。
10 年後に「この署名はいつ作られた?」 が分からないと法的有効性に疑問。 対策: 時刻認証局 (TSA) のタイムスタンプを併用 (RFC 3161)。
電子署名の理論的基礎は 1976 年の Diffie-Hellman 論文「New Directions in Cryptography」 に始まります。 ここで「公開鍵暗号」 という革命的アイデアが世に出ました。 翌 1977 年に Rivest, Shamir, Adleman の 3 人が RSA を発表し、 実用的な実装が可能に。 1980 年代には DSA (Digital Signature Algorithm) が NIST 標準化、 1990 年代に X.509 証明書と PKI が広く採用されました。
日本では 2001 年に電子署名法が施行され、 電子署名が紙の押印と同等の法的効力を持つようになりました。 マイナンバーカード (2016 年〜) は IC チップに RSA 鍵を内蔵し、 e-Tax / e-Gov での本人認証に使えます。 2018 年からは民間でも本格的に電子契約 (クラウドサイン、 DocuSign、 GMOサイン等) が普及し始め、 紙の契約書を駆逐しつつあります。
2020 年代の課題は「量子計算機への耐性」。 大規模な量子コンピュータが完成すると、 Shor のアルゴリズムで RSA / ECDSA が破られます (2030-2040 年代と予想)。 NIST は 2022 年に Dilithium / Falcon / SPHINCS+ をポスト量子署名標準として選定。 2030 年代には主要システムが順次移行する見込みです。
1 2 3 4 5 6 7 8 9 10 11 12 13 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey sk = Ed25519PrivateKey.generate() pk = sk.public_key() with open('data/raw/SSDSE-B-2026.csv','rb') as f: data = f.read() sig = sk.sign(data) print(f'Ed25519 署名サイズ: {len(sig)} バイト') # 64 バイト pk.verify(sig, data) # 成功なら何も起こらず, 失敗で例外 print('検証成功') |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | from cryptography import x509 from cryptography.x509.oid import NameOID from datetime import datetime, timedelta sk = rsa.generate_private_key(65537, 2048) subject = issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, 'JP'), x509.NameAttribute(NameOID.ORGANIZATION_NAME, 'SSDSE Test'), x509.NameAttribute(NameOID.COMMON_NAME, 'test.example.jp'), ]) cert = (x509.CertificateBuilder() .subject_name(subject) .issuer_name(issuer) .public_key(sk.public_key()) .serial_number(x509.random_serial_number()) .not_valid_before(datetime.utcnow()) .not_valid_after(datetime.utcnow() + timedelta(days=365)) .sign(sk, hashes.SHA256())) with open('outputs/cert.pem','wb') as f: f.write(cert.public_bytes(serialization.Encoding.PEM)) |
1 2 3 4 5 6 7 8 | from pyhanko.sign import signers, fields from pyhanko.pdf_utils.incremental_writer import IncrementalPdfFileWriter with open('report.pdf','rb') as f, open('report_signed.pdf','wb') as out: w = IncrementalPdfFileWriter(f) fields.append_signature_field(w, sig_field_spec=fields.SigFieldSpec('Signature1')) signer = signers.SimpleSigner.load_pkcs12('cert.p12', passphrase=b'secret') signers.sign_pdf(w, signers.PdfSignatureMetadata(field_name='Signature1'), signer=signer, output=out) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import os import rfc3161ng os.makedirs('outputs', exist_ok=True) # 保存先が無いと書けない # 公開 TSA を利用 (例: Sectigo) tsa = rfc3161ng.RemoteTimestamper('http://timestamp.sectigo.com', hashname='sha256') with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: data = f.read() # ブラウザ版 Python は外部のサーバーに接続できない。 # つながらないときは、何が起きたのかを日本語で示して終える。 try: tst = tsa(data=data) # タイムスタンプトークン with open('outputs/timestamp.tst', 'wb') as f: f.write(tst) print('タイムスタンプ取得成功 (法的時効を防ぐ)') except Exception as e: print('外部のタイムスタンプ局に接続できませんでした:', type(e).__name__) print('ブラウザ版 Python は外部通信ができないためです。' 'お手元の Python で実行すれば、そのまま取得できます。') |
1 2 3 4 5 6 7 8 9 10 | # 鍵生成 gpg --gen-key # ファイルに署名 (デタッチド形式) gpg --detach-sign --armor data/raw/SSDSE-B-2026.csv # → SSDSE-B-2026.csv.asc が生成される # 検証 gpg --verify SSDSE-B-2026.csv.asc SSDSE-B-2026.csv # → "Good signature from ..." と出れば成功 |
合成データで RSA 鍵長別の署名検証時間 [ms] を計算する。
| 鍵長 [bit] | 署名 [ms] | 検証 [ms] |
|---|---|---|
| 1024 | 1.2 | 0.05 |
| 2048 | 8.0 | 0.20 |
| 3072 | 20.0 | 0.40 |
| 4096 | 40.0 | 0.70 |
1 2 3 4 5 6 | import numpy as np bits = np.array([1024, 2048, 3072, 4096]) sign = np.array([1.2, 8.0, 20.0, 40.0]) verify = np.array([0.05, 0.20, 0.40, 0.70]) print(f"署名比 2048→4096: {sign[3]/sign[1]:.2f}") print(f"検証比 2048→4096: {verify[3]/verify[1]:.2f}") |
💬 手計算 (Step 2) 5 倍 / 3.5 倍と Python 出力が完全一致。
① SHA-256 ハッシュを計算
1 2 3 4 5 6 7 8 9 10 11 12 13 | import hashlib with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: raw_bytes = f.read() digest = hashlib.sha256(raw_bytes).hexdigest() print('SHA-256:', digest) print('ファイルサイズ:', len(raw_bytes), 'bytes') # 1 文字変えるだけで雪崩効果でハッシュは全く別物に tampered = raw_bytes.replace(b'\xe5\x8c\x97\xe6\xb5\xb7\xe9\x81\x93', b'\xe5\x8c\x97\xe6\xb5\xb7\xe9\x81\x95') print('tampered:', hashlib.sha256(tampered).hexdigest()) |
② 鍵ペアの生成 (RSA-2048)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048) public_key = private_key.public_key() with open('private.pem', 'wb') as f: f.write(private_key.private_bytes( encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.PKCS8, encryption_algorithm=serialization.NoEncryption())) with open('public.pem', 'wb') as f: f.write(public_key.public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo)) |
③ SSDSE-B-2026.csv に署名する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from cryptography.hazmat.primitives.asymmetric import rsa, padding from cryptography.hazmat.primitives import hashes, serialization # ↑ このコードは cryptography ライブラリが必要 with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: message = f.read() signature = private_key.sign( message, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256() ) with open('data/raw/SSDSE-B-2026.csv.sig', 'wb') as f: f.write(signature) print('署名長:', len(signature), 'bytes') # RSA-2048 なら 256 bytes |
④ 受信者側で検証する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | from cryptography.exceptions import InvalidSignature with open('public.pem', 'rb') as f: pub = serialization.load_pem_public_key(f.read()) with open('data/raw/SSDSE-B-2026.csv', 'rb') as f: received_msg = f.read() with open('data/raw/SSDSE-B-2026.csv.sig', 'rb') as f: received_sig = f.read() try: pub.verify(received_sig, received_msg, padding.PSS(mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH), hashes.SHA256()) print('OK: 本物の SSDSE-B-2026 です') except InvalidSignature: print('NG: ファイルが改ざんされているか鍵が違います') |
⑤ SSDSE-B-2026 の都道府県別行ハッシュ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import os os.makedirs('data', exist_ok=True) # 書き出し先を先に作る import os os.makedirs('data/raw', exist_ok=True) # 保存先のフォルダを作っておく import pandas as pd, hashlib # 英字の項目コード(Prefecture)を使うので skiprows=[1] で読む df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df.rename(columns={'SSDSE-B-2026': 'Year'}) # 年度の列名を分かりやすく def row_fingerprint(row): serial = '|'.join(str(v) for v in row.values) return hashlib.sha256(serial.encode('utf-8')).hexdigest()[:16] df['fingerprint'] = df.apply(row_fingerprint, axis=1) print(df[['Prefecture','Year','fingerprint']].head(10)) # 後日同じ df を読み直してハッシュ比較すれば差分検知ができる df[['Prefecture','Year','fingerprint']].to_csv( 'data/raw/SSDSE-B-2026.row_fingerprints.csv', index=False) |
⑥ ECDSA も併記
1 2 3 4 5 6 7 8 9 10 | from cryptography.hazmat.primitives.asymmetric import ec ec_priv = ec.generate_private_key(ec.SECP256R1()) ec_pub = ec_priv.public_key() ec_sig = ec_priv.sign(message, ec.ECDSA(hashes.SHA256())) print('ECDSA 署名長:', len(ec_sig), 'bytes') ec_pub.verify(ec_sig, message, ec.ECDSA(hashes.SHA256())) print('ECDSA 検証 OK') |
⑦ EdDSA (Ed25519) を使う
1 2 3 4 5 6 7 8 9 10 | from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey ed_priv = Ed25519PrivateKey.generate() ed_pub = ed_priv.public_key() ed_sig = ed_priv.sign(message) print('Ed25519 署名長:', len(ed_sig), 'bytes') # 64 ed_pub.verify(ed_sig, message) print('Ed25519 検証 OK') |
┌──────────────────┐
│ 暗号技術全体 │
└────────┬─────────┘
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 共通鍵暗号 │ │公開鍵暗号 │ │ ハッシュ │
│AES等 │ │RSA, ECC │ │SHA-256等 │
└─────────┘ └────┬────┘ └─────────┘
│
┌─────────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 鍵交換 │ │ 暗号化 │ │ 電子署名 │
│ DH, ECDH │ │ RSA encr │ │RSA, ECDSA│
└──────────┘ └──────────┘ └────┬─────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ PKI │ │タイムスタンプ│ │PDF/CMS署名│
│ X.509, CA │ │TSA (RFC3161)│ │S/MIME │
└──────────┘ └──────────┘ └──────────┘
▼
┌────────────┐
│ HTTPS, JWT │
│ ブロックチェーン│
└────────────┘
「電子署名」は単独で完結する手法ではなく、 隣接領域と連携することで真価を発揮する。 具体的には次の 3 方向と密接につながる:
電子署名は完全性・認証・否認防止を一体で実現する暗号応用の核心。
「電子署名」を実際に使うとき、 何をどう選ぶかを順に判断する。 上から順に答えていくと、 使うべき手法と評価の仕方が決まる。
電子署名は「誰が」「何に」同意したかを、 あとから第三者が確かめられるようにする仕組み。 データの配布では、 ハッシュ値の公開だけでも改ざん検知には役立つ。
電子署名を実データ運用に持ち込むと、 本文で学んだ「署名 = 封蝋」の比喩に 1 つ重要な但し書きが付きます。 署名が固定するのはファイルの「バイト列」そのものであって、 その中身が表す「意味 (データの値)」ではないという点です。 実際に data/raw/SSDSE-B-2026.csv で測ってみると:
| 対象 (すべて実測) | サイズ | SHA-256 (実測値) |
|---|---|---|
| 配布された CSV そのまま (cp932・CRLF 改行) | 359,821 バイト | 0fdbe5f603bb8e1e...046ea6f463 |
| 先頭 1 バイトの最下位 1 ビットだけ反転 (メモリ上で計算) | 359,821 バイト | 934c484a1a425631...4106e9471 の形に激変 |
pandas で読み込み → to_csv() で保存し直し (UTF-8・LF 改行) | 358,876 バイト | 2a43aab790c1320b...59420e22 |
1 ビットの反転で 256 ビット中 133 ビット (52%)、 64 桁の 16 進表記では 59 桁が変化しました (実測)。 これが雪崩効果の定量的な姿です。 一方 3 行目に注目してください。 pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) で読み込んで保存し直した CSV は、 564 行 × 112 列のデータ値が元と完全に一致する (df.equals() が True) のに、 文字コードが cp932 → UTF-8、 改行が CRLF (実測 566 行分) → LF に変わるためハッシュは全くの別物になります。 つまり「意味は同一・バイト列は別物」という状態が、 悪意ゼロでも日常的に発生するのです。
① 「検証失敗 = 悪意の改ざん」と即断しない。 上の実測が示す通り、 Excel で開いて上書き保存、 git の autocrlf 設定、 エディタの文字コード自動変換、 pandas での再保存——どれもバイト列を変えて検証を失敗させます。 検証はあくまで「配布されたバイト列と違う」ことしか教えてくれません。 原因の切り分け (再エンコードか改ざんか) は人間の仕事です。 対策は単純で、 署名付き原本は読み取り専用で保管し、 分析はコピーに対して行うこと。
② 逆向きも成り立たない:「検証成功 = 内容が正しい」ではない。 署名が保証するのは「署名者が署名した時点のバイト列のまま」という真正性・完全性だけで、 データそのものの正確さは 1 ミリも保証しません。 署名者が誤った集計値を署名すれば、 誤りが「正規品」として流通します (garbage in, signed garbage out)。 統計データの品質検証 (外れ値・欠測・定義変更の確認) は署名検証とは独立に必要です。
③ 「何をシリアライズして署名したか」の取り決めがないと検証不能。 行単位の指紋を作る場合、 「列の順序・区切り文字・数値の書式・文字コード」を 1 つでも取り決め忘れると、 正しいデータからでもハッシュが再現できません。 署名は正規化 (canonicalization) の規約とセットで初めて機能します。
正規化署名:この問題への正攻法が「バイト列ではなく正規形に署名する」設計で、 XML C14N (正規化 XML) や JSON の JCS (RFC 8785) が代表です。 CSV には広く合意された正規形がないため、 実務では「配布バイト列そのものを不変の原本とする」運用が主流です。
行単位フィンガープリント (実測):本文の「行ハッシュ」案を実際に計算してみます。 教材用の簡易規約として「年,県名,総人口 を UTF-8 でつないで SHA-256」と決めると (2023 年の実データ A1101 列):
| 行 (2023 年・実測値) | シリアライズ例 | SHA-256 先頭 16 桁 (実測) |
|---|---|---|
| 北海道 総人口 5,092,000 人 | 2023,北海道,5092000 | 0d2b1cf615689d40 |
| 東京都 総人口 14,086,000 人 | 2023,東京都,14086000 | 78111d8a8271473f |
| 広島県 総人口 2,738,000 人 | 2023,広島県,2738000 | fcd0e661b0396561 |
| 北海道の値を 1 だけ増やした場合 (改ざん想定) | 2023,北海道,5092001 | 059f749a194c4b0b → ❌ 不一致で行単位検出 |
これをさらにマークル木 (Merkle tree) に組むと、 SSDSE-B-2026 の全 564 行のうち「北海道 2023 の行だけが原本通り」であることを、 全行を渡さずに約 10 個 (log₂ 564 ≈ 9.1 段) の中間ハッシュだけで証明できます。 ルートハッシュ 1 つに署名すれば全行が守られる——ブロックチェーンや Certificate Transparency がこの構造です。 さらに 透明性ログ (CT ログ、 Sigstore の Rekor) は「署名の存在自体を追記専用ログに公開して、 署名者による後出し・差し替えも検知する」仕組みで、 否認防止を運用面から補強します。 データ分析の文脈では、 DVC などのデータバージョン管理ツールが「コンテンツハッシュでデータを指す」という同じ思想の上に立っています。