Pythonロボティクスコース レッスン 41

テーマ.10-4 認証システムを搭載したゲートの制作

データを安全に扱うための暗号化やハッシュ化の技術を学ぼう

このレッスンで学ぶこと

このレッスンでは、ネットワーク上での情報のやり取りを安全に行うための技術である、データの「暗号化/復号」と「ハッシュ化」について学習します。

データの暗号化と復号

データの暗号化と複号は、ネットワーク上で、送信者と受信者の間で安全にデータをやり取りするために欠かせない情報技術のひとつです。

例えば、スマートフォンのアプリを使って誰かにメッセージや写真を送る場面では、これらのデータがネットワーク(インターネット回線)を通って相手の所へ届けられます。このときのメッセージや写真などのデータは、テーマ.10-2で学習したように「0」と「1」で表されるビット単位(またはバイト単位)のデータに変換されてネットワーク上を流れています。

ネットワークはクモの巣のように、様々な地点をつなぐようにして回線が張り巡らされています。各地点には、ネットワーク上の住所にあたる「IPアドレス」が割り当てられていて、特定の誰かにデータを送るときは、自分のIPアドレスと相手先のIPアドレスをもとに、ネットワークの中から最適なルートが探索されて、複数の中継地点を通りながら情報が運ばれる仕組みになっています。

※ IP・・・Internet Protocolの略。インターネットにおける主要な通信規格。

そのため、ネットワークには常に誰かにデータを盗聴されてしまう危険性が付きまといます。バイト列に変換された数値や文字列のデータを簡単に元の形式に戻すことができたように、ネットワークへそのままデータを流してしまうと、盗まれたときに容易く解析されてしまいます。これによって、自分が送ったメッセージや写真を全く知らない人に見られてしまったり、盗まれた情報がネットワーク上に流されてしまったりする事態も起きてしまいます。

そこで、これらの問題を避けるために使われているのが暗号化と複号の技術です。送信者は、ネットワークへデータを流す前に、特定の鍵を使ってデータを暗号化します。暗号化されたデータは、鍵なしでは解読が困難なため、仮に盗まれてしまっても、元のデータを知られることがありません。そして、本来の受信者だけが同じく鍵を使ってデータの復号を行い、元のデータを確認することができます。

2. 1 暗号化と複号の方式

暗号化と復号の方式には、共通鍵暗号方式公開鍵暗号方式の2つがあり、それぞれ次のような特徴があります。

■ 共通鍵暗号方式

  • 暗号化と複合に同じ鍵(共通鍵)を使用する。
  • 鍵交換は盗聴されないように安全な方法で行う必要がある。

■ 公開鍵暗号方式

  • 暗号化用の鍵(公開鍵)と複合用の鍵(秘密鍵)を分けて使用する。
  • 公開鍵で暗号化されたデータは、受信側のみがもつ秘密鍵でしか復号ができず、公開鍵のみを知る第三者がデータを復号することはできない。

2. 2 MicroPythonでのデータの暗号化と復号

Micropythonでは、ucryptolibモジュールで暗号化と復号化のための機能が提供されています。ucryptolibは、共通鍵暗号方式に対応していて、次の3つの暗号利用モードが選択できます。

  • Electronic Codebook (ECB)
  • Cipher Block Chaining (CBC)
  • Counter (CTR)

暗号利用モードは簡単に言うと、「暗号化するための仕組み」のことです。暗号利用モードによって、セキュリティの高さや計算に掛かるコストが異なります。それぞれの暗号利用モードについて詳しく説明すると内容が長くなってしまうので、ここでは「Electronic Codebook (ECB)」と「Cipher Block Chaining (CBC)」の特徴を簡単に紹介します。

■ Electronic Codebook (ECB)

暗号利用モードの中でも最もシンプルなモードで、ブロックと呼ばれる単位でデータが暗号化される。セキュリティ能力は低い。

■ Cipher Block Chaining (CBC)

ECBよりもセキュリティ能力が高いモード。前のブロックを暗号化した結果を次のブロックの暗号化にも利用するため、複数のブロックが鎖(chain)のようにつながった形で表される。

暗号利用モードについて詳しく知りたい人は、以下のwikipediaのページを参考にしてください。

暗号利用モード(Wikipedia)

2. 3 データの暗号化と復号の実践

上で紹介した2つの暗号利用モードを比較すると「CBC」の方がより堅牢な暗号化処理だと言えます。そこでここからは、「CBC」を利用してデータの暗号化と複合を実践してみましょう。プログラムは、実際のネットワーク上でのデータの流れを意識して、送信側と受信側、そしてネットワークに分けてコードを書いていきます。

【 プログラムで定義する関数 】
関数名内容
encrypt_processターミナルから入力された16字以下のメッセージを暗号化して送る送信側のプロセス
decrypt_process受け取ったメッセージを復号してターミナルに表示する受信側のプロセス
network_processメッセージの運搬を行うネットワークのプロセス

■ 送信側のプロセスの作成

ucryptolibモジュールのCBCモードで暗号化を行うためには、16バイトの共通鍵と初期ベクトルが必要です。初期ベクトルは、データ内で最初に暗号化するブロックに適用されるものです。最初のブロックは、2つめ以降のブロックと違い、前のブロックの暗号化されたデータが存在していないため、その代わりとして使われます。

共通鍵と初期ベクトルは、データ量が16バイトであれば、それぞれどんな文字列でも構いません。ここでは適当に決めた16字(1字につき1バイト)の英数字を共通鍵と初期ベクトルとして利用しましょう。

次に暗号化・復号用のオブジェクトを作成します。これには、ucryptolibモジュールのaes()関数を使います。

aes(共通鍵、暗号利用モード、初期ベクトル※CBCの場合のみ)

この関数の第2引数には、使用する暗号利用モードによって以下の数値を指定します。

暗号利用モード第2引数に指定する数値
Electronic Code Book (ECB)1
Cipher Block Chaining (CBC)2
Counter (CTR)6

この関数が返す暗号化・復号用オブジェクトがもつencrypt()メソッドやdecrypt()メソッドで暗号化(encrypt)と複号(decrypt)を行います。一度どちらかのメソッドを実行した暗号化・復号用オブジェクトは、もう一方のメソッドを実行することができません。つまり、暗号化・復号用オブジェクトは作成方法は共通していますが、どちらか一方の用途でしか使用できないオブジェクトとなっています。

ここまでを踏まえた上で、以下のようにencrypt_process()関数を定義しましょう。

追加【7行目~13行目】

暗号化するメッセージも、共通鍵や初期ベクトルと同じ16バイトに合わせなければなりません。そこで、16字に満たない場合は「*」で16字になるまで埋めておきます(13行目)。この処理をパディングといいます。

そして、暗号化したメッセージは次に定義するnetwork_processに渡します(15行目)。

■ ネットワークのプロセスの作成

ネットワークのプロセスは、いたってシンプルです。暗号化されたメッセージの内容が確認できるようにprint文で出力して、受信側のプロセスに渡します。

追加【16行目~18行目】

■ 受信側のプロセスの作成

最後に受信側のプロセスを作成します。受け取ったメッセージを復号用オブジェクトのdecrypt()メソッドで復号し、元のメッセージを表示しましょう。

追加【20行目~25行目】

decrypt()メソッドで復号したメッセージは、バイト列になっています。そのため、decode()メソッドで文字列に変換し、さらにパディングで追加した「*」をstrip()メソッドで削除します(23・24行目)。

■ プログラムの実行

完成した以下のプログラムを実行して、ターミナルからencrypt_process()関数を呼び出してみましょう。適当なメッセージを入力すると、残りのプロセスが実行されて、結果が表示されます。

【 サンプルコード 2-3-1 】
(プログラムの実行例)

上の例では、「hello」というメッセージを送っています。これをそのままバイト列に変換すると、「b'\x68\x65\x6c\x6c\x6f'」となります。ターミナルの表示と比較すると、メッセージが暗号化されて解読できなくなっていることが分かります。

データを復号できない形式へ変換するハッシュ化

ここで紹介する「ハッシュ化」は、暗号化と同じくデータを解読できない形に変換する技術です。暗号化との大きな違いは、ハッシュ化されたデータは複合ができないという点です。

元に戻せないということは、メッセージなど相手が確認することを前提としたデータの保護にハッシュ化を利用することはまずありません。では一体どういった場合にハッシュ化を使うのでしょうか。

3. 1 ハッシュ化を使うケース

ハッシュ化はデータベースにパスワードを保管するときに使われることがあります。

パスワードは、データベース上に保管されている情報の中でも、最も守られなければならない情報のひとつです。データベースからパスワードが流出してしまうと、会員になりすました第三者にサービスを悪用されてしまうでしょう。

一方で、パスワードがハッシュ化されていれば、元のパスワードを特定することができないため、万が一、流出した場合でも被害を最小限に抑えることができます。

3. 2 MicroPythonでのハッシュ化の実践

MicroPythonでは、uhashlibモジュールでハッシュ化の機能が提供されています。このモジュールでは、次の3つのハッシュ化のアルゴリズムがサポートされています。

  • SHA256
  • SHA1
  • MD5

それぞれのアルゴリズムの詳細な説明を行うと難しくなるので、ここでは「MD5 < SHA1 < SHA256」の順に、セキュリティ能力が高いということを覚えておきましょう。

それでは、前のテーマ.10-3で作成した認証システムと同じケースを想定して、データベースに保管するパスワードをハッシュ化してみましょう。ハッシュ化のアルゴリズムには「SHA256」を採用します。

■ パスワードをハッシュ化して保管したデータベースの準備

プログラムを簡単にするために、btreeモジュールの代わりに、辞書型をデータベースとして使います。ユーザーのIDをキーにして、uhashlibモジュールのsha256()関数でハッシュ化し、さらにdigest()関数でバイト列化したパスワードを値に設定しましょう。

■ ログイン関数の作成

ユーザーが入力したIDとパスワードでログイン認証を行う関数を定義します。入力されたID情報からデータベース上に保管されているパスワードを取得し、入力されたパスワードをハッシュ化とバイト列化して比較することで認証を行います。

追加【9行目~19行目】

■ データベースからの情報漏洩を想定した関数の作成

また、情報漏洩が起きてしまったケースを想定して、データベースに保管されている情報の一覧が表示される関数を作成しておきましょう。これでプログラムの完成です。

【 サンプルコード 3-2-1 】
追加【20行目~23行目】

■ プログラムの実行

完成したプログラムを実行します。まずは、login()関数を呼び出して、正しいIDとパスワードの組み合わせと、誤った組み合わせを入力した場合の結果を確認してみましょう。

(プログラムの実行例)

ハッシュ化されたパスワードどうしを比較しているため、問題なくログイン認証を行えています。

今度は、leak()関数を実行して、データベースに保管されているパスワードを確認してみましょう。

(プログラムの実行例)

バイト列になっているため、少し分かりにくいのですが、明らかに元の単純な6桁の数字のパスワードよりも複雑なデータになっていることが分かります。仮にこのデータが覗かれてしまったとしても、元のパスワードが推測されてしまうのは現実的ではありません。

ゲートの組立て

レッスンの後半からは、ここまでで学習した「暗号化/復号」と「ハッシュ化」の技術を応用して、セキュリティ能力の高い認証システムを搭載したゲートを制作します。まずは、ブロックでゲートの形を組み立てましょう。

4. 1 組み立てに必要なパーツ

【 パーツ一覧 】
  • Studuino:bit×1
  • ロボット拡張ユニット×1
  • 電池ボックス×1
  • サーボモーター×1
  • ブロック基本四角(グレー)×2
  • ブロック基本四角(赤)×4
  • ブロックハーフA(グレー)×3
  • ブロックハーフB(黒)×1
  • ブロックハーフC(白)×12
  • ブロックハーフD(白)×8
  • ステー×7
  • ギヤ小×1
  • ラック×1
【 アーテックブロックの形状 】

4. 2 組立説明書

以下のリンク先から組立説明書を確認しましょう。

ゲートの組立説明書

認証システムを搭載したゲートの制作

インターネットは誰もが気軽にアクセスして利用できるという高い利便性がある一方で、悪意をもった利用者がウィルス攻撃やフィッシングなどを行い、流出した情報を使って他人へのなりすましを行うなどの危険性も多く潜んでいます。

このような危険を回避する方法として、メールやSNS、ブログ、ネットゲーム、ネットショッピングなのサービス提供者は、利用者へ個別のIDとパスワードを付与したアカウントを発行しています。

最近では、電話回線を利用したショートメッセージによるワンタイムパスワードの発行を組み合わせた2段階認証や、個人の指紋や顔の形を利用した生体認証など、より安全性の高い認証システムが積極的に使われるようになってきています。

もちろんセキュリティの問題は、インターネット上だけの話ではありません。例えば、オフィスやマンションでは、部外者が無断で侵入できないように出入口に暗証番号やカードで開くドアを設置しています。

そこで今回は、IDとパスワードによる認証で入場を制限するゲートシステムを作成します。例として、このゲートを学校の教室や校門に設置することを考えて、各生徒には生徒番号(1~999)と4桁の数字からなるパスワードをアカウントとして配布します。

ゲートシステムは、ここまでに学習した暗号化と復号の技術(ucryptolibモジュール)とハッシュ化の技術(uhashlibモジュール)、それから前回のレッスンで学習したデータベースの技術(btreeモジュール)を使い、以下の構成で構築します。

【 ゲートシステムの構成 】

そして、上のシステムを構築するために、プログラムでは以下の関数を作成します。

【 作成する関数の一覧 】
関数名処理内容
register_students()データベースへアカウント情報を登録する関数
show_db()データベース上のアカウント情報の一覧を表示する関数
gate_process()ゲートのプロセスを行う関数
auth_process()認証のプロセスを行う関数
main()他の関数を実行するメイン関数

それでは、次の手順に沿って5つの関数を作成し、プログラムを完成させましょう。

5. 1 プログラムの作成

作成するプログラムは、少し複雑な仕組みで動作するため、途中で実行結果を確認しながらコーディングを進めていきましょう。

■ 生徒のアカウント情報を登録する処理(register_students関数)

この関数では、引数に渡された生徒のアカウント情報をデータベースに登録する処理を行います。生徒のアカウント情報は以下の3つで構成され、データベースへは、生徒番号をキーとし、生徒の名前と4桁の数字で表したパスワードを値として登録します。

【 生徒のアカウント情報 】
  • student_number: 生徒番号(キー)
  • name: 生徒の名前(値)
  • password: 4桁の数字で表したパスワード(値)

そして、この関数へは複数のアカウント情報(辞書)をまとめたリストを渡すようにします。

【 渡すリストの例 】
students_list = [  # 登録する生徒のアカウント情報
    {"student_number": 1, "name": "Tanaka", "password": "2312"},
    {"student_number": 2, "name": "Ogura", "password": "4512"},
    {"student_number": 3, "name": "Hatanaka", "password": "7543"},
    {"student_number": 4 , "name": "Morita", "password": "0324"},
]
register_students(students_list)  # 引数に渡して登録を行う

そして、このリストから順番にアカウント情報を取り出して、データベースへの登録を行います。このときにパスワードはSHA256でハッシュ化して記録します。

それではまず、データベースを開く処理から書きましょう。データベースとして使用するファイル名は自由に決めてください。

次に、引数に渡されたリストから順番にアカウント情報を取り出して、バイト列化とハッシュ化を行います。名前とパスワードはひとつの値として記録するため、2つのデータの間に「,」を加えて結合します。

データベースへすべてのアカウント情報が一時的に記憶できたら、flush()メソッド実行して、ファイルへの書き込みを行います。最後にデータベースとファイルを閉じるのを忘れないようにしましょう。

追加【2行目、13行目~21行目】

これで、データベースへ登録する関数ができたので、テスト用のアカウント情報を用意して実行してみましょう。

エラーが発生しなければ、無事にデータベースに登録できていますが、登録情報を直接確認できていないので少し不安です。そこで、データベース上の情報の一覧を表示する関数を作成して確かめてみましょう。

■ データベース上の情報の一覧を表示する処理(show_db関数)

show_db()関数をregister_students()関数の下に定義します。関数の始めに、データベースを開く処理を書きましょう。この関数では、データベースへの書き込みは発生しないため、ファイルは読み込み専用のバイナリモード("rb")で開きます。

追加【24行目~30行目】

開いたデータベースの情報をターミナルに表示します。データベースも辞書と同じくitems()メソッドを持っているため、キーと値を順番に取り出すときは、「for key, val in db.items():」のように書くことができました。また、値は名前とパスワードを「,」で結合したものにしていたため、文字列のsplit()メソッドで分割します。ただし、分割文字の「,」を「b","」とバイト列で指定している点に注意してください(33行目)。

追加【31行目~38行目】

では、テスト用のアカウント情報を登録していた処理をコメント化して、その下にshow_db()関数を実行するコードを追加して、ここまでのプログラムを実行してみましょう。

変更・追加【40行目~49行目】
(実行結果)

上と同じような実行結果が得られれば成功です。パスワードは複雑にハッシュ化されているため、元の4桁の数字を推測することは困難でしょう。

■ ゲートで入力されたアカウント情報を認証のプロセスへ送る処理(gate_process関数)

テスト用のアカウント情報が登録できたので、ここからはそれを使って認証を行い、ゲートを開閉する処理を書いていきます。

まずは、ゲート側のプロセスでアカウント情報の入力を受け付けて、その情報を暗号化して送るまでのところを作りましょう。アカウント情報はキューを介してゲート側のプロセスから認証側のプロセスへ渡します。

追加【3行目、6~8行目、42行目~50行目】
※ 暗号化・復号用の共通鍵と初期ベクトルは16字の英数字であれば、何でも構いません。

■ アカウント情報を受け取って復号する処理(auth_process関数)

次は、認証のプロセス側で暗号化されたアカウント情報を受け取って、復号する処理を作成します。認証を行うアカウント情報はキュー(que_gate_to_auth)に追加されていくため、一定時間おきにチェックするコードを書き、情報があった場合は取り出して復号します。

そして、送信情報は16字(byte)に満たない場合、暗号化する前に「*」で埋めていたため、文字列のstrip()メソッドでこれを削除します。また、生徒番号はバイト列から整数値に戻します。

追加【4行目、54行目~66行目】

では、ここまでに追加した処理の動作を確認しましょう。auth_process()関数をスレッドを立ち上げて実行するコードを追加します。

変更・追加【5行目、78行目~80行目】

このプログラムを実行し、ターミナルからgate_process()関数を呼び出して、生徒番号とパスワードを入力します。直後にauth_process()関数から情報を受け取ったことを示すメッセージが表示されれば成功です。

>>> gate_process()
Input your student number. >>>1
Input your password. >>>1234
Input your student number. >>>Now received 1 b'1234'

■ アカウント情報の認証を行う処理(auth_process関数)

これで認証に必要なアカウント情報の受け渡しができるようになったので、データベース上の情報と照合して、認証を行う処理を作成していきます。

まずは、auth_process()関数の始めにデータベースを開くためのコードを追加しましょう。また、74行目のprint文は不要になったので、コメント化するか削除しておきましょう。

【58行目~63行目、74行目】

受け取ったアカウント情報から生徒番号を使って、データベース上で検索を掛けます。もし、生徒番号が存在しなかった場合は、ゲート側のプロセスに認証に失敗したことを伝えます。このときの情報の受け渡しにもキューを使いますので、新しいキューを用意しましょう。また、送る情報は(認証の成否を表すbool値, メッセージの文字列)のタプルとし、受け取ったゲート側のプロセスがTrueまたはFalseで成否を判断できるようにしておきましょう。

追加【11行目、78行目~82行目】

生徒番号が検索にヒットした場合は、データベースから値を読み込んで、結合していた名前とパスワードを分割して取り出します。この手順はshow_db()関数と同様です。送られてきたパスワードは、データベースのパスワードと照合するために、ハッシュ化して比較します。2つが一致した場合は、認証に成功したものとして、Trueと生徒の名前を含めたメッセージをゲート側のプロセスに送ります。不一致の場合は、Falseとパスワードが正しくなかったことを伝えるメッセージを送ります。

追加【83行目~89行目】

■ 認証結果を受けてゲートを開閉する処理(gate_process関数)

続けて、ゲート側のプロセスで認証結果を受け取って、ゲートを開閉する処理を作成していきます。

ゲート側では、ゲートを開閉するためのサーボモーターや、認証結果を知らせるためのLEDディスプレイやブザーを使用します。プログラムの先頭で必要なオブジェクトやクラスをインポートして、インスタンスを作成しましょう。

追加【6・7行目、14行目】

ターミナルから入力されたアカウント情報を認証プロセス側に送ったあとは、認証プロセス側から認証結果が返って来るまで(キューにデータが格納されるまで)待機します。

追加【60行目~62行目】

認証結果を受け取ったら、内容を確認します。メッセージを表示して、LEDディスプレイへとブザー音で成功か失敗かを知らせましょう。また、成功の場合はサーボモーターを動かして、ゲートを5秒間開きます。

追加【63行目~80行目】

■ main関数に処理をまとめる

これで必要な機能が揃いました。最後にコメント化していたところも含めて、main()関数にまとめて実行できるようにしましょう。

追加【117行目~128行目】

5. 2 プログラムの動作確認

完成したプログラムを実行して、正しいアカウント情報を入力した場合と誤ったアカウント情報を入力した場合の両方で動作を確かめましょう。

【 入力例 】

もし、問題が改善できない場合は、以下のサンプルコードと見比べて誤りがないか1行ずつ確かめましょう。

【 サンプルコード 4-3-1 】

課題:正規表現を使って入力されたアカウント情報を事前にチェックする

前のレッスンでは、正規表現とマッチングについて学習し、簡単な認証システムにおいて、ターミナルから入力されたメールアドレスやパスワードの書式に誤りがないかをチェックする機能を作成しました。

そこでこの課題では、復習も兼ねて【 サンプルコード 4-3-1 】へ、ユーザーが入力した生徒番号やパスワードの書式に誤りがある場合は再入力を求める機能の追加に取り組みます。

入力する情報書式
生徒番号1~999までの3桁の番号
パスワード0~9の数字を組み合わせた4桁の文字列

正規表現の書き方やマッチングの方法を忘れたという場合は、先に前回のレッスンを見直しましょう。

6. 1 プログラムの変更手順

正規表現を扱うので、ureモジュールをインポートします。

追加【6行目】

生徒番号とパスワードのそれぞれの正規表現を定義して、マッチングのための正規表現オブジェクトを作成します。

追加【54行目~57行目】

ターミナルから入力された生徒番号とパスワードに対してマッチングを行います。どちらか一方でもマッチングに失敗した場合は、continue文を実行して再入力を促すようにしましょう。

追加【62行目~69行目】

6. 2 プログラムの動作確認

完成したプログラムを実行して、様々なパターンを入力し、動作を確認しましょう。自分で作成したプログラムがうまく動作しない場合は、以下のサンプルコードを参考にして、もう一度プログラムを見直しましょう。

【 サンプルコード 5-2-1 】

おわりに

7. 1 このレッスンのまとめ

このレッスンでは、ネットワーク上での情報のやり取りを安全に行うための技術として、「データの暗号化/復号」と「データのハッシュ化」について学習しました。

様々な情報が自由に行き交うインターネットの世界では、セキュリティの問題は常に付きまといます。今回紹介した技術以外にも、皆さんが安全にインターネットを利用できるように、セキュリティを専門にするエンジニアの活躍によって、今もなお新たな技術開発が続けられています。

テーマ10を通して、様々なデータ処理の技術を学んできました。これまでの全テーマの中で最も難しかったのではないでしょうか。コンピュータやプログラミング、インターネットの世界はもっと奥が深く、学べば学ぶほど高度な技術によって支えられていることが分かります。

早速、次のテーマ11からはインターネットの世界について少し詳しく紹介していきます。残すところあと2テーマですが、最後まで頑張って学習に取り組みましょう。

7. 2 次のレッスンについて

テーマ11では、Studuino:bitをインターネットに接続して、ネットワーク上にある情報を取得して、Studuino:bitを制御するための方法を学習します。レッスンを進める前に、教室に接続可能なwifi環境があることを先生に確認しておきましょう。

TOP