デジタル署名プロセスをプログラミングコードで解説 | ブロックチェーンで注目のECDSA署名のチュートリアルあり

デジタル署名とは、書面上のサインを代替する電子的なサインのことです。なかでも公開鍵・秘密鍵によるデジタル署名は多くのシステムで活用されている重要なセキュリティー技術です。しかし、デジタル署名の流れを理解している人は少ないでしょう。本記事ではデジタル署名の概要と、プログラミングコードを通じた署名プロセスを解説します。デジタル署名の仕組みの理解や、システム実装などの参考にしてください。

## デジタル署名とは
デジタル署名とは、書面による署名を電子的に代替する方法のひとつです。そのため電子署名とも呼ばれます。法的な効力については、[電子署名及び認証業務に関する法律](https://elaws.e-gov.go.jp/document?lawid=412AC0000000102)という法律で規定されています。近年注目されているスマートコントラクトにおいて重要な技術要素で、多くの電子署名サービスやブロックチェーンなどで活用されています。

## デジタル署名で利用される公開鍵・秘密鍵
一部のデジタル署名のプロセスでは、`公開鍵`, `秘密鍵`とよばれる不規則な英数文字列のデータが利用されています。秘密鍵によって署名データを作成し、公開鍵によって署名データを検証することが可能です。

秘密鍵は名前の通り、自分だけが保持している鍵です。秘密鍵を利用して署名データを作成することは、後述するデジタル署名プロセスの`署名`に該当します。一方で公開鍵は、公開して他人が利用する鍵です。他人は公開鍵によって「署名データが署名者本人によって作成されたデータであること」を確認します。他人によるデータ確認は、後述するデジタル署名プロセスの`検証`にあたります。公開鍵と秘密鍵はセットになっており、秘密鍵から公開鍵を作成します。

### ECDSAとは
`ECDSA`とは、デジタル署名で用いられているアルゴリズムのひとつで、`楕円曲線デジタル署名アルゴリズム`の略称です。その他のアルゴリズムには、`RSA`, `DSA`, `EdDSA`などがあります。デジタル署名のプロセスにおいて、公開鍵は秘密鍵から生成して他人に提供しています。他人に秘密鍵を知られてしまった場合、他人は自分の署名を自由に実施できる状態となるため、公開鍵からは秘密鍵を生成できないことが求められます。

ECDSAは、楕円曲線上の離散対数問題という数学的性質を利用したアルゴリズムです。この性質によって、現代のコンピュータ能力では、公開鍵から秘密鍵を生成することは事実上不可能と考えられており、デジタル署名プロセスに有用なアルゴリズムです。

## デジタル署名のおおまかな流れ
デジタル署名にでは、公開鍵・秘密鍵による署名と検証の仕組みが利用されていますが、具体的にはどのように署名が行われるのでしょうか。以下のケースを想定して具体的な流れを解説します。

**ケース**
トニーが「私はアイアンマンです」というデータに署名し、ナターシャへ送信する。ナターシャは「私はアイアンマンです」というデータが間違いなくトニーが作成したデータであることを検証する。

**流れ**
1.	トニーが秘密鍵と公開鍵を生成する。
2.	トニーは秘密鍵を厳重に保管し、公開鍵を外部に公開する。
3.	トニーは秘密鍵を用いて、「私はアイアンマンです」という生データから署名データを作成します。
4.	トニーはナターシャへ、生データ（私はアイアンマンです）と署名データ（不規則な文字列）を送ります。
5.	ナターシャは公開鍵を用いて、生データと署名データの整合性が取れていることを確認します。もし整合性が取れない場合、生データと署名データのいずれか、もしくは両方が悪意ある第三者によって改ざんされたと判断します。

[![Image from Gyazo](https://i.gyazo.com/d0edbce1d7f54844b27fc53bdbbb320d.png)](https://gyazo.com/d0edbce1d7f54844b27fc53bdbbb320d)

## デジタル署名のプログラミングコード
デジタル署名の大まかな流れを解説しました。実際にプログラミングを通じて、デジタル署名を行って理解を深めましょう。[Pure-Python ECDSA](https://github.com/tlsfuzzer/python-ecdsa)を利用して、ECDSAによる鍵生成、署名、検証の流れを再現します。Python3の実行環境が整っていることが前提条件です。

### STEP1 秘密鍵と公開鍵の生成
自分のPCに[Pure-Python ECDSA](https://github.com/tlsfuzzer/python-ecdsa)をインストールします。Terminalで以下のスクリプトを実行してください。どのディレクトリで実行しても問題ありません。

```Terminal
$ pip install ecdsa
```

次に、Pythonを実行できるコンソールを開いてください。まずは必要な[Pure-Python ECDSA](https://github.com/tlsfuzzer/python-ecdsa)の各パッケージをインポートします。以下のスクリプトを実行してください。

```Console
from ecdsa import SigningKey
from ecdsa import VerifyingKey
from ecdsa import SECP256k1
```

各パッケージについて補足します。`SiningKey`は署名するための鍵、つまり秘密鍵を生成に関わるパッケージです。`VerifyingKey`は証明用の鍵、つまり公開鍵生成に関わるパッケージです。`SECP256k1`は楕円曲線の種類を指定するパラメータです。楕円曲線にも様々な種類があり、`SECP256k1`はそのうちのひとつです。インポートすることによって秘密鍵の生成時にパラメータとして指定可能となります。`SECP256k1`はビットコインの取引データの暗号化にも用いられている有名なパラメータです。他にも`NIST192p`や`BRAINPOOLP160r1`など多数あり、これらを用いて鍵を生成することも可能です。さらに詳しく知りたい場合は、以下のリンクを参考にしてください。
https://github.com/tlsfuzzer/python-ecdsa#speed

`generate`メソッドで秘密鍵を生成します。以下のスクリプトをコンソールで実行してください。`generate`メソッドの引数として曲線の種類を指定できます。

```Console
secret_key = SigningKey.generate(curve=SECP256k1)
print(secret_key)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/fed1e21213b6c09c2d93153327de104d.png)](https://gyazo.com/fed1e21213b6c09c2d93153327de104d)
 
秘密鍵から公開鍵を生成します。以下のスクリプトをコンソールで実行してください。

```Console
public_key = secret_key.verifying_key
print(public_key)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/bf4f49fc8c9c392f253a11523b27ff34.png)](https://gyazo.com/bf4f49fc8c9c392f253a11523b27ff34)

#### 補足：鍵の出力形式の変換方法 (Keyオブジェクト⇔バイト形式⇔16進数文字列)
公開鍵と秘密鍵はKeyオブジェクトとして出力されています。Keyオブジェクトは、バイト形式や16進数文字列へ相互変換できます。署名や検証ではKeyオブジェクト形式の鍵を利用しますが、鍵情報の保管や他人とのやり取りは16進数文字列で行われるのが一般的です。署名プロセスから逸れますが、各形式の変換プロセスを試してみましょう。

`to_string`メソッドを用いることで、鍵情報をKeyオブジェクトからバイト形式へ変換できます。以下のスクリプトをコンソールで実行してください。

```Console
# Keyオブジェクト -> バイト形式
secret_key_byte = secret_key.to_string()
public_key_byte = public_key.to_string()
print(f'secret_key_byte: {secret_key_byte}')
print(f'public_key_byte: {public_key_byte}')
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/61b98823c3c71ded4948625fb2385659.png)](https://gyazo.com/61b98823c3c71ded4948625fb2385659)

`hex`メソッドを用いることで、鍵情報をバイト形式から16進数文字列へ変換できます。以下のスクリプトをコンソールで実行してください。

```Console
# バイト形式 -> 16進数文字列
secret_key_str = secret_key_byte.hex()
public_key_str = public_key_byte.hex()
print(f'secret_key_str: {secret_key_str}')
print(f'public_key_str: {public_key_str}')
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/cda4f2337734cd2c4d1acd9cdf64487b.png)](https://gyazo.com/cda4f2337734cd2c4d1acd9cdf64487b)

`binascii.unhexlify`メソッドを用いることで、鍵情報を16進数文字列からバイト形式へ変換できます。以下のスクリプトをコンソールで実行してください。`binascii.unhexlify`について詳細を知りたい場合は、以下のリンクを参考にしてください。
https://docs.python.org/ja/3/library/binascii.html#binascii.unhexlify

```Console
# 16進数文字列 -> バイト形式
import binascii
secret_key_byte = binascii.unhexlify(secret_key_str)
public_key_byte = binascii.unhexlify(public_key_str)
print(f'secret_key_byte: {secret_key_byte}')
print(f'public_key_byte: {public_key_byte}')
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/682be72de91cb9fa130f0d1a2ecdd190.png)](https://gyazo.com/682be72de91cb9fa130f0d1a2ecdd190)

`from_string`メソッドを用いることで、鍵情報をバイト形式からKeyオブジェクトへ変換できます。以下のスクリプトをコンソールで実行してください。また鍵生成時に利用した曲線の種類を`from_string`メソッドの引数に指定してください。

```Console
# バイト形式 -> Keyオブジェクト
secret_key = SigningKey.from_string(secret_key_byte, curve=SECP256k1)
public_key = VerifyingKey.from_string(public_key_byte, curve=SECP256k1)
print(f'secret_key: {secret_key}')
print(f'public_key: {public_key}')
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/94f781d1ed4e40ee798734b3ad340ab7.png)](https://gyazo.com/94f781d1ed4e40ee798734b3ad340ab7)

### STEP2 秘密鍵の厳重保管、公開鍵の外部公開
署名プロセスに戻ります。STEP2は特にプログラムによるプロセスはありません。秘密鍵のデータは安全な場所に保管してください。公開鍵は誰に教えても問題ありません。今回の場合は、ナターシャに伝える必要があります。

### STEP3 署名
「私はアイアンマンです」という生データに署名を行います。署名とは、秘密鍵と生データを用いて、署名データを作成することを意味します。このプロセスではバイト形式の生データを利用するため、まずは生データをバイト形式に変換します。バイト形式への変換する際には、変換文字コードを指定する必要があります。今回は`UTF-8`に指定します。以下のスクリプトをコンソールで実行してください。

```Console
deta = "私はアイアンマンです"
deta_bytes = bytes(deta, encoding = "utf-8")
print(deta_bytes)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/87ebc51e8ab72cd8a3613d5375fe783e.png)](https://gyazo.com/87ebc51e8ab72cd8a3613d5375fe783e)
 
次にバイト形式の生データと秘密鍵から署名データを生成します。`sign`メソッドで署名データを作成できます。以下のスクリプトをコンソールで実行してください。

```Console
signature = secret_key.sign(deta_bytes)
print(signature)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/bf1e0ae983471d4619274a24b89ae4bb.png)](https://gyazo.com/bf1e0ae983471d4619274a24b89ae4bb)

署名データはバイト形式で出力されますが、16進数文字列との相互変換も可能です。16進数文字列の場合、どのような出力となるか確認してみましょう。以下のスクリプトをコンソールで実行してください。

```Console
signatire_str = signature.hex()
signature = binascii.unhexlify(signatire_str)
print(signatire_str)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/3f2cc1bc176a211ce9ebb849ddb0257f.png)](https://gyazo.com/3f2cc1bc176a211ce9ebb849ddb0257f)

### STEP4 生データと署名データの送付
トニーがナターシャに生データと署名データを送付します。このSTEPはプログラムによる変換プロセスはありません。

### STEP5 検証
次はナターシャの立場となって、公開鍵を用いて、署名データ（バイト形式）と生データ（バイト形式）で整合性が取れているかを確認します。以下のスクリプトをコンソール実行してください。整合性が取れていればTrueが返されます。

```Console
public_key.verify(signature, deta_bytes)
```

以下のような出力結果が得られます。
[![Image from Gyazo](https://i.gyazo.com/01aa857d645a085ce4eccc73d6a81df7.png)](https://gyazo.com/01aa857d645a085ce4eccc73d6a81df7)

データに変更が加えられている場合は、エラーが出力されます。「私はアイアンマンです」ではなく「私はアンパンマンです」に改ざんされてしまったと仮定して、検証を実行してみましょう。以下のスクリプトを実行してください。

```
changed_deta = "私はアンパンマンです"
changed_deta_bytes = bytes(changed_deta, encoding = "utf-8")
public_key.verify(signature, changed_deta_bytes)
```

以下のようなエラーが出力されます。下の方`Signature verification failed`と表示され、署名データが不正であることを確認できます。
[![Image from Gyazo](https://i.gyazo.com/3aaf39f644a2228d3988179ac1d4b20e.png)](https://gyazo.com/3aaf39f644a2228d3988179ac1d4b20e)


## まとめ
デジタル署名は、法的効力もあるデータ署名技術です。多くのシステムで利用されており、近年注目されているスマートコントラクトでも活用されています。本記事で解説したデジタル署名の概要やプログラムコードを通じて、技術的背景の理解に役立ててください。

ブロックチェーンでも改ざん防止のためにデジタル署名が活用されています。署名プロセスを単独で学ぶよりも、システムの中で、どのように署名プロセスが価値を発揮しているか学習した方が楽しく習得できるでしょう。Pythonによるブロックチェーン開発教本（私が制作したもの）がおススメです。まずは無料公開の部分からお試しください。

日本語版

https://www.amazon.co.jp/dp/B0B55XSBNZ

英語版

https://www.amazon.co.jp/dp/B0B7BLLDWQ