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 / 公開試料と固定時刻
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'
sig = signed(private,message)
print(sig.hex(),len(sig))
print(signed(private,message) == sig)e20d32cdce053ee59cc017a52641879231d7efb6f274dc3dde374f5acb3d2b0b56706c5621c64e728dd6354bc9f2ca17b521b324662a18dfcff44e124c420102 64 True
print(verify(public,sig,message))True
print(verify(public,sig,message.replace(b"release=7",b"release=8")))False
print(message.hex())
print(verify(public,sig,message.replace(b"\n",b"\r\n")))72656c656173653d370a7368613235363d6162630a False
bad_sig = bytes([sig[0]^1]) + sig[1:]
print(verify(public,bad_sig,message))False
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
演習完了