一覧SC-07-2 · 公開鍵と電子署名

Ed25519によるデジタル署名

署名の対象、検証鍵の信頼、本文の表現の違いを説明します。

説明

電子署名は秘密鍵で作り、対応する公開鍵で検証します。本文を秘密にする機能ではありません。また署名が正しくても、検証した公開鍵が誰のものかは別途確かめる必要があります。

公開試料のEd25519鍵で文書へ署名し、本文・署名・検証鍵を変更します。最後に公開鍵をすり替えた例と、固定しておいた鍵での検証を比較します。

署名の対象、検証鍵の信頼、本文の表現の違いを説明します。

1署名鍵と公開検証鍵を取得する

RFCの公開試料seedからEd25519鍵を作ります。seedは32バイト、公開鍵も32バイトです。

from sc07 import * private = signer()公開試料seedから鍵を用意する public = private.public_key() print(public.public_bytes_raw()32バイトの公開鍵を表示する.hex()) message = b"release=7\nsha256=abc\n" print(message)

この公開鍵を署名者のものとして別途確認済み、という条件で以降の検証を行います。

2文書へ署名する

Ed25519の署名長は64バイトです。この方式は同じ鍵と本文なら同じ署名になります。

sig = signed(private,message)文書へ署名する print(sig.hex(),len(sig)) print(signed(private,message) == sig同じ入力を再署名して比較する)

64バイトの署名ができました。本文は別に渡し、検証側は両方を使います。

3公開鍵だけで検証する

検証には秘密鍵を渡しません。

print(verify(public,sig,message)公開鍵と本文で署名を検証する)

Trueです。検証者は公開鍵を持てば照合できますが、この公開鍵だけでは新しい署名を作れません。

4文書中の版を変更する

release=7をrelease=8へ置き換えます。

print(verify(public,sig,message.replace(b"release=7",b"release=8")署名後に版番号を変更する))

Falseです。署名は特定の文書に結び付きます。

5改行の表現を変更する

LFをCRLFへ変更します。見た目が似ていても、署名対象のバイト列は異なります。

print(message.hex()元の改行を0aとして確認する) print(verify(public,sig,message.replace(b"\n",b"\r\n")改行を0d0aへ変更する))

Falseです。署名対象を正規化する仕様なら、その規則も送受信で一致させる必要があります。

6署名の1ビットを変更する

本文を保ったまま署名を変更します。

bad_sig = bytes([sig[0]^1署名の先頭1ビットを反転する]) + sig[1:] print(verify(public,bad_sig,message))

Falseです。本文と署名の両方が検証対象です。

7別の鍵と署名の組を持ち込む

別の公開試料鍵で同じ文書へ署名します。攻撃者が検証鍵まで渡せる場合を考えます。

other = ed25519.Ed25519PrivateKey.from_private_bytes(bytes(range(32))) other_sig = signed(other,message)別の鍵で文書へ署名する print(verify(other.public_key(),other_sig,message)) print(verify(public,other_sig,message)確認済みの鍵で検証する)

持ち込まれた鍵ではTrueですが、確認済みの鍵ではFalseです。署名検証を鍵の身元確認と組み合わせる必要があります。

まとめ

秘密鍵で署名し、公開鍵で検証します。

改行を含め、署名対象のバイト列を一致させます。

公開鍵のすり替えを防ぐ信頼の仕組みが別途必要です。

操作履歴と解説を振り返れます。

Python 3.15 / cryptography 50.0.2 / 公開試料と固定時刻

1
from sc07 import * private = signer() public = private.public_key() print(public.public_bytes_raw().hex()) message = b"release=7\nsha256=abc\n" print(message)
d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
b'release=7\nsha256=abc\n'
2
sig = signed(private,message) print(sig.hex(),len(sig)) print(signed(private,message) == sig)
e20d32cdce053ee59cc017a52641879231d7efb6f274dc3dde374f5acb3d2b0b56706c5621c64e728dd6354bc9f2ca17b521b324662a18dfcff44e124c420102 64
True
3
print(verify(public,sig,message))
True
4
print(verify(public,sig,message.replace(b"release=7",b"release=8")))
False
5
print(message.hex()) print(verify(public,sig,message.replace(b"\n",b"\r\n")))
72656c656173653d370a7368613235363d6162630a
False
6
bad_sig = bytes([sig[0]^1]) + sig[1:] print(verify(public,bad_sig,message))
False
7
other = ed25519.Ed25519PrivateKey.from_private_bytes(bytes(range(32))) other_sig = signed(other,message) print(verify(other.public_key(),other_sig,message)) print(verify(public,other_sig,message))
True
False

演習完了