技術メモ

役に立てる技術的な何か、時々自分用の覚書。幅広く色々なことに興味があります。

pythonでコードを書きながらデジタル署名を理解する

仮想通貨の仕組みについての勉強の続き。
仮想通貨にはなくてはならないデジタル署名を実装して勉強した。
デジタル署名自体は目新しいものではなく昔からある技術で、今でもネット決済など広く使われている。

デジタル署名とは

ザックリとした説明

デジタル署名とは、その名の通り文書に「署名」すること。(ここでいう文書とは文字列や数字列といったデータのこと)
では署名に必要な要件とは何か。

  • 第一に、署名をすることができるのは署名者(アリスとする)だけだが、署名を見た人は誰でもそれが有効であることが確認できなければいけない。
  • 第二に、署名と文書は密接に結びついており、別の文書に対するアリスの保証を示すためには使えない。つまり色々な署名からアリスの署名の仕方を推測すること(本当の署名でいうところの筆跡など真似に相当するような行為)、はできないということ。

という2つの要件が必要になってくる。

踏み込んだ説明

暗号理論を使えば上の条件が実現できる。
公開鍵暗号を用いる方法だ。(公開鍵暗号については RSA暗号を実装してみる(知識編) - 技術メモ
具体的にはデジタル署名には3つのスキームで成り立つ。

  • 公開鍵と秘密鍵のペアを生成する (generateKey)
  • 秘密鍵を使って文書に対する署名を生成する (sign)
  • 公開鍵・文書をつかって署名が正しいものかどうかを検証する (verify)

署名者(アリスとする)はgenerateKeyを使って秘密鍵skと公開鍵pkを生成し、公開鍵を公開する。
アリスは署名したい文書Dataをハッシュ関数にかけたうえで秘密鍵skを使って署名signatureを生成する。
承認者は公開されている公開鍵pk・文書Data・アリスから受け取った署名signatureを使って、pk,Data,signatureの組が正当であればTrueを返し違っていればFalseを返す。署名が間違っているか文書が書き換えられていれば承認がうまくいかないことになる。
f:id:swdrsker:20180124031957p:plain

普通の公開鍵暗号方式と違うところは、文書自体はそのまま(暗号化せずに)通信するというところだ。
だから、文書は誰でも見れるものでも良いという事。
文書を見れる人なら誰でも署名と公開鍵さえ渡されればそれがアリスによる署名だとわかる。

デジタル署名に対する攻撃

N通りの{文書、署名}および公開鍵の組み合わせから秘密鍵を見破ることができればデジタル署名は破られてしまう。(chosen message attack)
だが、現在一般に使われている署名方式ではこれを行うのはほぼ不可能に近い。

コード

python (python3.5)で書いた。RSAハッシュ関数(SHA256)など暗号技術諸々はpycryptoを使った。
ちなみに実際のビットコインでは公開鍵暗号方式RSAではなく楕円曲線暗号が使われている。

digitalsignature.pyという名前で保存

from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from base64 import b64decode, b64encode
import sys

def generate_key(keysize=2048, passphrase = None):
    new_key = RSA.generate(keysize)
    public_key = new_key.publickey().exportKey()
    secret_key = new_key.exportKey(passphrase = passphrase)
    return secret_key, public_key

def sign(secret_key, data, passphrase = None):
    try:
        rsakey = RSA.importKey(secret_key, passphrase = passphrase)
    except ValueError as e:
        print(e)
        sys.exit(1)
    signer = PKCS1_v1_5.new(rsakey)
    digest = SHA256.new()
    digest.update(b64decode(data))
    sign = signer.sign(digest)
    return b64encode(sign)

def verify(pub_key, signature, data):
    rsakey = RSA.importKey(pub_key)
    signer = PKCS1_v1_5.new(rsakey)
    digest = SHA256.new()
    digest.update(b64decode(data))
    if signer.verify(digest, b64decode(signature)):
        return True
    else:
        return False
テスト

digitalsignature.pyと同じフォルダ内で実行

from digitalsignature import *

# 秘密鍵と公開鍵を作る。(パスワードはなくても良い)
password = "password"
sk, pk = generate_key(passphrase = password)

# メッセージに署名する(署名者が行う)
message = "hoge"
sig = sign(sk, message, passphrase = password)

# 承認テスト(承認者が行う)
if verify(pk, sig, message):
    print("承認 OK")
else:
    print("承認 NG")

# メッセージの書き換えに対するテスト
changed_message = "hogehoge"
if verify(pk, sig, changed_message):
    print("書き換えテスト NG")
else:
    print("書き換えテスト OK") # 承認されなければOK

# 間違った秘密鍵の署名に対するテスト
sk2, pk2 = generate_key(passphrase = password)
sig2 = sign(sk2, message, passphrase = password)
if verify(pk, sig2, message):
    print("不正署名テスト NG")
else:
    print("不正署名テスト OK") # 承認されなければOK
  • 実行結果
承認 OK
書き換えテスト OK
不正署名テスト OK

参考にした本

仮想通貨の教科書

仮想通貨の教科書

楕円曲線暗号について勉強した