pythonで暗号演算 ~RSAによるデジタル署名~
今回は前回のDH(Diffie-Hellman)に続き、RSAによるデジタル署名を取り上げます。
RSAとは
公開鍵暗号の一種で、TLS通信においては主に以下役割を担います。
役割 | 概要 | その他 |
---|---|---|
データの秘匿 | 公開鍵でデータを暗号化し、秘密鍵で復号化する。 | 非推奨 (処理時間大) |
デジタル署名 | 秘密鍵で暗号化し、公開鍵で復号化する。 | – |
鍵交換(共通鍵の共有) | 公開鍵で共通鍵を暗号化し、秘密鍵で復号化する。 | 非推奨(DH法を推奨) |
公開鍵、秘密鍵、共通鍵などのキーワードについては以下わかりやすく解説してくれているので確認いただければと思います。
データの秘匿
通信において常時RSAでデータを暗号化してやり取りをし続けることはそうありません。
基本的には共通鍵を生成し、共通鍵によるAES暗号を行います。
これはRSAの演算速度が、AESの演算速度より大きいためです。
そのため、RSAは初回のデジタル署名などにのみよく用いられます。
鍵交換
RSAを使えば、以前取り上げたDH法と比べ、比較的簡単なロジックで共通鍵の交換を行うことが出来ます。
しかしながら、現在RSAによる共通鍵の共有は非推奨とされています。
これは、RSAによって共通鍵の交換をしてしまうと、前方秘匿性が失われてしまうためです。
前方秘匿性とは、将来的に通信に使われている鍵情報(秘密鍵等)が流出した際にも、過去の通信データの秘匿性が確保されることを指します。
そのため、セッションごとに一時的な秘密鍵と公開鍵を使用し破棄してしまうDHEやECDHEが共通鍵の共有では用いられます。
DH(Diffie-Hellman)法による鍵共有ついては以下過去記事にて取り上げているので興味があればぜひ。
デジタル署名
上記より、TLS通信においてRSAは主にデジタル署名のために用いられます。
デジタル署名とは、メッセージの真正性(メッセージの送信元が本人である)を保証するための方法です。
TLS通信では、認証局と連携して通信相手が確かに本人であることを、RSAの公開鍵暗号方式を用いたデジタル署名で確認しています。
以下はクライアントBがサーバA本人と通信していることを、デジタル署名によって確認する方法になります。
① まず初めにサーバAは自身の秘密鍵と公開鍵を認証局に発行してもらう。
② サーバAと通信したいクライアントBは信頼できる認証局から、サーバAの公開鍵を取得する。
③ クライアントBは通信開始時にサーバAに対しデジタル署名を要求する。このときハッシュ計算方法なども指定する。
④ サーバAは要求に応じてメッセージと署名値をクライアントBへ送信する。
⑤ クライアントBは取得した署名値と公開鍵からハッシュ値B’を、メッセージからハッシュ値B"を生成する。
⑥ 公開鍵で復号化可能なメッセージを送信できるのは、対応する秘密鍵を持つサーバA本人のみなので、両ハッシュ値が一致すれば通信相手がサーバAであることが保証される。
当然サーバAは秘密鍵が流出しないよう、厳重に管理する必要があります。
ソース
実際にRSAでデジタル署名を行うコードをpythonで実装しようと思ったのですが、DHに比べて本件に取り組んでいる方が多数いたので割愛します。
特に以下サイトの実装例が比較的わかりやすく、時間計測までしてくれています。
AESとの処理時間比較
上記ベースに、RSAとAESの処理時間比較用のコードを実装してみました。
#!/user/env python3
# -*- coding: utf-8 -*-
from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP
import Crypto.Cipher.AES as AES
import time
message = b"test data 16byte"
# RSAの鍵生成
random_func = Random.new().read
rsa = RSA.generate(2048, random_func)
private_pem = rsa.exportKey(format='PEM')
public_pem = rsa.publickey().exportKey()
# RSAによる暗号化時間計測
start = time.time()*1000000
cipher = PKCS1_OAEP.new(RSA.importKey(public_pem))
cipher_text = cipher.encrypt(message)
print("RSAによる暗号化", int(time.time()*1000000-start), 'μSec')
# RSAによる復号化時間計測
cipher = PKCS1_OAEP.new(RSA.importKey(private_pem))
dec_txt = cipher.decrypt(cipher_text)
print("RSAによる復号化", int(time.time()*1000000-start), 'μSec', dec_txt)
key = bytes.fromhex("00112233445566778899aabbccddeeff")
iv = bytes.fromhex("00112233445566778899aabbccddeeff")
nonce = bytes.fromhex("0011223344556677")
# AEC-CBCによる暗号化時間計測
cipher = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
enc_txt = cipher.encrypt(message)
print("CBCによる暗号化", int(time.time()*1000000-start), 'μSec', enc_txt )
# AEC-CBCによる復号化時間計測
decipher = AES.new(key=key, mode=AES.MODE_CBC, iv=iv)
dec_txt = decipher.decrypt(enc_txt)
print("CBCによる復号化", int(time.time()*1000000-start), 'μSec', dec_txt )
# AEC-CTRによる暗号化時間計測
cipher = AES.new(key=key, mode=AES.MODE_CTR , nonce=nonce)
enc_txt = cipher.encrypt(message)
print("CTRによる暗号化", int(time.time()*1000000-start), 'μSec', enc_txt )
# AEC-CTRによる復号化時間計測
decipher = AES.new(key=key, mode=AES.MODE_CTR , nonce=nonce)
dec_txt = decipher.decrypt(enc_txt)
print("CTRによる復号化", int(time.time()*1000000-start), 'μSec', dec_txt )
以下が実行結果となります。
RSAによる暗号化 1995 μSec
RSAによる復号化 44880 μSec b'test data 16byte'
CBCによる暗号化 45877 μSec b'@>\x0f\xd8\x98\x13V.!}D\xeehbr\xdc'
CBCによる復号化 45877 μSec b'test data 16byte'
CTRによる暗号化 46875 μSec b'e\xb6\xc5(\x99\xa7\x0fd3\x0f_\x0f8\xc9R\xf0'
CTRによる復号化 46875 μSec b'test data 16byte'
RSAの方がAESより処理に大きな時間を要することが分かります。
理由として、AESと同等のセキュリティ強度をRSAの暗号演算に持たせようとした場合の鍵長が、AESよりはるかに大きいことが挙げられます。
通信にて常時大量のデータをやり取りする場合、RSAによるデータの秘匿が適切でないことが分かります。