Slide 1

Slide 1 text

RSAを使った暗号化/復号を Python3のpow関数でやってみる PyFukuoka #8 発表者:井上 信之 2019/12/12(Thr)

Slide 2

Slide 2 text

自己紹介  なまえ 井上 信之  仕事 ネットワーク/インフラ系エンジニア。Cisco歴25年。 昔は電子回路やPLC、組み込み系もやっていた。 N-88BASICやMicrosoft C/Turbo Cコンパイラ世代のおじさん。  主な所有資格 ネスペ/登録セキスペ/MCP/MCSE/CCNA/職業訓練指導員免許  そのほか  映画『電気海月のインシデント』 -IT技術監修 –  公開中の自作フリーソフト https://www.vector.co.jp/vpack/browse/person/an060626.html 2

Slide 3

Slide 3 text

公開ソフト一覧 https://www.vector.co.jp/vpack/browse/person/an060626.html 3

Slide 4

Slide 4 text

今日話すこと  べき乗した値を特定の値で割った余りには規則性がある  RSA公開鍵暗号化方式で登場するとある2つの素数p, q と整数 n, e, d の話  暗号化と復号、電子署名の生成と照合の手順  OpenSSLの鍵のフォーマット(整数 n, e, d, p, q が入っている) 4

Slide 5

Slide 5 text

RSA公開鍵暗号化方式とは RSA暗号とは、桁数が大きい合成数の素因数分解問題が困難であることを安全性の根拠 とした公開鍵暗号の一つ。 暗号とデジタル署名を実現できる方式として最初に公開さ れたものである。 1977年に発明され、発明者である  Ronald Linn Rivest(ロナルド・リベスト)  Adi Shamir(アディ・シャミア)  Leonard Max Adleman(レオナルド・エーデルマン) の原語表記の頭文字をつなげてこのように呼ばれる。当時、ディフィーとヘルマンに よって発表されたばかりの公開鍵暗号という新しい概念に対し、秘匿や認証を実現でき る具体的なアルゴリズムを与えた。 この暗号はフェルマーの小定理に基づいている。 5

Slide 6

Slide 6 text

オイラーのφ関数 オイラーのトーシェント関数(オイラーのトーシェン トかんすう、英: Euler‘s totient function)とは、正の 整数 n に対して、 n と互いに素である 1 以上 n 以下の 自然数の個数 φ(n) を与える数論的関数 φ である。 φ = ෍ 1≤≤ , =1 1 例えば、1, 2, 3, 4, 5, 6 のうち 6 と互いに素なのは 1, 5 の 2 個であるから、定義によれば φ(6) = 2 である。 1, 2, 3, 4, 5, 6, 7 のうち 7 以外は全て 7 と互いに素だ から、φ(7) = 6 と定まる。 6

Slide 7

Slide 7 text

オイラーのφ関数 1 から 20 までの値は以下の通りである。 1, 1, 2, 2, 4, 2, 6, 4, 6, 4, 10, 4, 12, 6, 8, 8, 16, 6, 18, 8,… p を素数とすると、1 から p − 1 のうちに p の素因子である p を因子として含むもの は存在しないから、φ(p) = p − 1 が成り立つ。 1から20までの値を見ても、、 φ(3) … 2 φ(7) … 6 φ(11) … 10 φ(13) … 12 φ(17) … 16 となっており、φ(p) = p – 1 が成立している様子がわかる。 7

Slide 8

Slide 8 text

フェルマーの小定理とオイラーの定理  フェルマーの小定理 p を素数とし、a を p の倍数でない整数(a と p は互いに素)とするときに、 −1 ≡ 1 ( ) すなわち、a の p − 1 乗を p で割った余りは 1 であるというもの。  オイラーの定理 フェルマーの定理を一般化したもの。 Nが正の整数でaをnと互いに素(a と n を共に割り切る正の整数が 1 のみ)な整数としたとき、 φ() ≡ 1 が成立する。ここで φ() はオイラーのφ関数である。 8

Slide 9

Slide 9 text

フェルマーの小定理を確かめてみる(p = 7 ) p = 7 の場合  1(7−1) = 16 = 1 ≡ 1 ( 7)  2(7−1) = 26 = 64 ≡ 1 ( 7)  3(7−1) = 36 = 729 ≡ 1 ( 7)  4(7−1) = 46 = 4096 ≡ 1 ( 7)  5(7−1) = 56 = 15625 ≡ 1 ( 7) 9 pow(n, p – 1) % p の計算例 $ python >>> pow(1, 6) % 7 1 >>> pow(2, 6) % 7 1 >>> pow(3, 6) % 7 1 >>> pow(4, 6, 7) 1 >>> pow(5, 6, 7) 1 >>> pow(6, 6, 7) 1 >>> pow(7, 6, 7) 0 >>> pow(13, 10, 12) 1 Pythonのpow関数 pow(base, exp[, mod])

Slide 10

Slide 10 text

ほかにも規則性がいろいろあるっぽい… 10 (4+1) ≡ ( 10) 1乗、5乗、9乗、13乗、… (4*n + 1) 乗の1桁目(10で割った余り)は、 元の値の1桁目と同じ値になっている。

Slide 11

Slide 11 text

もっとべき乗の数を増やして確かめてみる (4+1) ≡ ( 10) 11 3の(4*n + 1)乗の1桁目の値は、必ず3となる。 >>> pow(3, 4*10 + 1, 10) 3 >>> pow(3, 4*21 + 1, 10) 3 >>> pow(3, 4*99 + 1, 10) 3 >>> pow(3, 4*999 + 1, 10) 3 >>> pow(3, 4*1024 + 1, 10) 3 >>> pow(3, 4*65535 + 1, 10) 3 5の(4*n + 1)乗の1桁目の値は、必ず5となる。 >>> pow(5, 4*10 + 1, 10) 5 >>> pow(5, 4*21 + 1, 10) 5 >>> pow(5, 4*99 + 1, 10) 5 >>> pow(5, 4*999 + 1, 10) 5 >>> pow(5, 4*1024 + 1, 10) 5 >>> pow(5, 4*65535 + 1, 10) 5

Slide 12

Slide 12 text

12

Slide 13

Slide 13 text

RSA公開鍵暗号化方式の手順 鍵ペア(公開鍵と秘密鍵)を作成して公開鍵を公開する。まず、適当な正整数 e(通常 は小さな数。65537 (= 216+ 1) がよく使われる)を選択する。 また、大きな2つの素数 {p, q} を生成し、それらの積 n (=pq) を求めて、{e, n} を平文の 暗号化に使用する鍵(公開鍵)とする。 2つの素数 {p, q} は、暗号文の復号に使用する鍵(秘密鍵)d ( = −1 ( ( − 13 出典)RSA暗号 – Wikipedia https://ja.wikipedia.org/wiki/RSA%E6%9A%97%E5%8F%B7

Slide 14

Slide 14 text

RSA公開鍵暗号化方式で使用する整数 整数Nと整数Eの組み合わせが公開鍵であり、 整数Nと整数Dの組み合わせが秘密鍵である。 14 変数 内容 セキュリティパラメータk 鍵のビット数(ビット数が大きいほど強度が高くなる) 整数p, q ある程度の大きさ の2つの素数 (2/k ビット) 整数n n = p*q とある2つの素数(p, q)の積 整数e (Encrypt:暗号化) φ(n)=(p-1)(q-1)未満で互いに素な正の整数 一般的には2^16 + 1 = 65537などの固定された値がよく使われる 整数Eは鍵の強度に影響しない。また、数が小さく、ビットが立たな い数字のほうが乗算速度が速くなる 整数d (Decrypt:復号) e*d ≡ 1 (mod φ(n)) を満たす整数e (e*dをφ(n)で割った余りが1)

Slide 15

Slide 15 text

RSA公開鍵暗号化方式のパラメータ 15 p = 1879, q = 2531 の場合、整数Nは、 N = p*q = 1879*2531 = 4755749 (ちなみに N < pow(2, 23)なので、鍵長は23ビット) となります。整数E は、φ(n) 未満の正の整数でφ(n)と互いに素であれば良いので、 7, 13, 17, 23, … 65537 などの素数であればなんでもよいのですが、 とありあえず、ここでは E = 7 と決めておくことにしましょう。 E*D ≡ 1 (mod N) となる 整数D を(プログラムなどで)計算して求めると、 D = 678763 となります。

Slide 16

Slide 16 text

暗号化および復号の実験 16 Python3での実行例 >>> P = 1879 >>> Q = 2531 >>> N = P*Q >>> N 4755749 >>> E = 7 >>> D = 678763 公開鍵で暗号化 >>> pow(999, E, N) 1878484 秘密鍵で復号 >>> pow(1878484, D, N) 999 秘密鍵で暗号化 >>> pow(999, D, N) 3065016 公開鍵で復号 >>> pow(3065016, E, N) 999 元の値を 999 として、 暗号化(公開鍵)および復号(秘密鍵)をやってみる。 公開鍵で暗号化 pow(999, E, N) → 1878484 秘密鍵で復号 pow(1878484, D, N) → 999 元の値が戻った!!! 暗号化(秘密鍵)および復号(公開鍵)をやってみる。 秘密鍵で暗号化 pow(999, D, N) → 3065016 公開鍵で復号 pow(3065016, E, N) → 999 元の値が戻った!!! (一般的には、秘密鍵での符号化処理は暗号化とは言いませんが、ここでは便 宜上、暗号化と表記しています。) 公開鍵と秘密鍵はペアとして使用する不思議な値(笑)の組み合わせで、 この組み合わせを使うと、  公開鍵で暗号化したあと、秘密鍵で復号  秘密鍵で暗号化したあと、公開鍵で復号 のどちらの計算でも、元の値を復元することができるのです。

Slide 17

Slide 17 text

素数の生成(方法1) Pythonでの直接の演算は、処理速度が遅い のでお勧めしませんが、念のため… 1.試し割り法 nまでのすべての整数で割ってみる。 17 出典) 【Python入門】素数の生成・判定プログラムを実装してみよう! - Samurai Blog https://www.sejuku.net/blog/74038 「試し割り法」のプログラム例 import math number = 123 def trial_division(target): dest = int(math.sqrt(target)) for i in range(2, dest): if target % i == 0: print(str(target) + 'は合成数!') return print(str(target) + 'は素数!') trial_division(number)

Slide 18

Slide 18 text

素数の生成(方法2) 2.エラトステネスのふるい 1~nまでの表を用意しておき、1,2,3,…n/2の倍数 を除外する。 18 出典) 【Python入門】素数の生成・判定プログラムを実装してみよう! - Samurai Blog https://www.sejuku.net/blog/74038 「エラトステネスのふるい」のプログラム例 import math serach_number = 100 def sieve_of_eratosthenes(target): dest = int(math.sqrt(target)) target_list = list(range(2, target + 1)) prime_list = [] while True: num_min = min(target_list) if num_min >= dest: prime_list.extend(target_list) break prime_list.append(num_min) i = 0 while True: if i >= len(target_list): break elif target_list[i] % num_min == 0: target_list.pop(i) i += 1 print(prime_list) sieve_of_eratosthenes(serach_number)

Slide 19

Slide 19 text

2つの素数から、整数N, E, Dを求めるプ ログラム例 19 最小公倍数を求めるプログラム例 def lcm(p, q): # 最小公倍数を求める。 return (p * q) // math.gcd(p, q) 2つの素数p, qから、整数N, 整数E, 整数D を求める プログラム例 def generate_keys(p, q): # 2つの素数(p, q)の積nを求める N = p * q # p - 1 と q - 1 の最小公倍数を求める L = lcm(p - 1, q - 1) # 公開鍵で使用するeを算出する for E in range(2, L): if math.gcd(E, L) == 1: break # 秘密鍵で使用するdを算出する for D in range(2, L): if (E * D) % L == 1: break return (E, N), (D, N) このプログラムでは、 変数E , 変数D が それぞれの forループ 内で見つからなかった場合のエラー処 理がありません。引用する場合は注意 してください。 鍵長が2048ビットの場合、 Python3のこの演算では 遅すぎて処理が終わらない

Slide 20

Slide 20 text

整数Dを求める処理を見直してみる 20 整数Dを求めるプログラム例2 # 秘密鍵で使用するdを算出する i = 0 while True: if (i * l + 1) % e == 0: d = (i * l + 1) // e break i += 1 整数Dを求めるプログラム例1 # 秘密鍵で使用するdを算出する for D in range(2, L): if (E * D) % L == 1: break 鍵長 左側の処理 (プログラム例1) 右側の処理 (プログラム例2) 鍵長23ビット(100回繰り返し) (p = 1879, q = 2531, n = 4755749, e = 7) Python3: 5.841[s] Golang: 0.564[s] Python3: 0.057[s] Golang: 0.051[s] 鍵長2048ビット(100回繰り返し) (e = 65537) Python3: 計測断念 Golang: 未計測 Python3: 1.625[s] Golang: 未計測 数式 ==1での比較ではなく、 数式 == 0 で比較した方が、 圧倒的に処理が速い。

Slide 21

Slide 21 text

OpenSSLの鍵で同じ実験をやってみる ここから先は、スライドで書くのはしんどいので、 手順をQiitaの記事にまとめました。 OpenSSLの鍵を使った暗号化/復号をPythonのpow関数でやってみる https://qiita.com/gx3n-inue/items/e28f16e0ae0c9f7133ee Python3だと、事実上、整数型の上限が無いのは楽で良いですよね~ まぁ、ほかの言語でもBigIntegerとかありますが… 21

Slide 22

Slide 22 text

DER (Distinguished Encoding Rule) 形式 のフォーマット RSAPrivateKey ::= SEQUENCE { version Version, modulus INTEGER, -- n publicExponent INTEGER, -- e privateExponent INTEGER, -- d prime1 INTEGER, -- p prime2 INTEGER, -- q exponent1 INTEGER, -- d mod (p-1) exponent2 INTEGER, -- d mod (q-1) coefficient INTEGER, -- (inverse of q) mod p otherPrimeInfos OtherPrimeInfos OPTIONAL } 22 出典) Information technology – ASN.1 encoding rules: Specification of Basic Encoding Rules (BER), Canonical Encoding Rules (CER) and Distinguished Encoding Rules (DER) https://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf

Slide 23

Slide 23 text

Python用のおすすめ暗号処理ライブラリ  PyCryptodome https://www.pycryptodome.org/en/latest/ https://www.pycryptodome.org/en/latest/src/examples.html RSAの鍵の生成(ASCII/PEM形式に対応。OpenSSLで読み込み可)や暗号化/復号/電 子署名の生成などのAPIが用意されています。 RSAだけでなく、AES, DES/3DES, RC2 などの共通鍵暗号化方式、MD5/SHA-1/SHA- 2/SHA-3などのハッシュ関数もあります。 23

Slide 24

Slide 24 text

PyCryptodome Examples 公開鍵と秘密鍵の生成 from Crypto.PublicKey import RSA key = RSA.generate(2048) # 鍵ペアを生成 private_key = key.export_key() # 秘密鍵をエクスポート file_out = open(“private.pem”, “wb”) file_out.write(private_key) public_key = key.publickey().export_key() # 公開鍵をエクスポート file_out = open("receiver.pem", "wb") file_out.write(public_key) 24 PyCryptodome Examples https://www.pycryptodome.org/en/latest/src/examples.html

Slide 25

Slide 25 text

PyCryptodome Examples RSAの公開鍵で共通鍵を暗号化し、AES(共通鍵)でメッセージを暗号化する例(ハイブリット暗号) from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from Crypto.Cipher import AES, PKCS1_OAEP data = “I met aliens in UFO. Here is the map.”.encode(“utf-8”) # 元の文字列 file_out = open(“encrypted_data.bin”, “wb”) # 暗号化データの出力ファイル recipient_key = RSA.import_key(open(“receiver.pem”).read()) # 公開鍵を読み込む session_key = get_random_bytes(16) # 共通鍵を生成 # Encrypt the session key with the public RSA key cipher_rsa = PKCS1_OAEP.new(recipient_key) # RSA処理用のインスタンス生成 enc_session_key = cipher_rsa.encrypt(session_key) # 共通鍵を公開鍵で暗号化 # Encrypt the data with the AES session key cipher_aes = AES.new(session_key, AES.MODE_EAX) # AES処理用のインスタンス生成 ciphertext, tag = cipher_aes.encrypt_and_digest(data) # 元の文字列を共通鍵で暗号化 [ file_out.write(x) for x in (enc_session_key, cipher_aes.nonce, tag, ciphertext) ] # 鍵情報と暗号化データをファイルへ出力 25 PyCryptodome Examples https://www.pycryptodome.org/en/latest/src/examples.html

Slide 26

Slide 26 text

PyCryptodome Examples RSAの秘密鍵で共通鍵を復号し、AES(共通鍵)でメッセージを復号する例(ハイブリット暗号) rom Crypto.PublicKey import RSA from Crypto.Cipher import AES, PKCS1_OAEP file_in = open("encrypted_data.bin", "rb") private_key = RSA.import_key(open(“private.pem”).read()) # 秘密鍵の読み込み enc_session_key, nonce, tag, ciphertext = ¥ [ file_in.read(x) for x in (private_key.size_in_bytes(), 16, 16, -1) ] # 鍵情報と暗号化後のデータを読み込む # Decrypt the session key with the private RSA key cipher_rsa = PKCS1_OAEP.new(private_key) # RSA処理用のインスタンス生成 session_key = cipher_rsa.decrypt(enc_session_key) # 共通鍵を秘密鍵で復号 # Decrypt the data with the AES session key cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce) # AES処理用インスタンス生成 data = cipher_aes.decrypt_and_verify(ciphertext, tag) # 共通鍵で暗号化データを復号 print(data.decode(“utf-8”)) # 結果を出力 26 PyCryptodome Examples https://www.pycryptodome.org/en/latest/src/examples.html

Slide 27

Slide 27 text

Pycryptodomeのencrypt関数の中身 pow(元の値, e, n) を呼び出していますね… 27 Crypto/Cipher/PKCS1_OAEP.py def encrypt(self, message): … # Step 3b (RSAEP) m_int = self._key._encrypt(em_int) lib/Crypto/PublicKey/RSA.py def _encrypt(self, plaintext): if not 0 < plaintext < self._n: raise ValueError("Plaintext too large") return int(pow(Integer(plaintext), self._e, self._n)) GitHub - PyCryptodome https://github.com/Legrandin/pycryptodome

Slide 28

Slide 28 text

Pycryptodomeのdecrypt関数の中身 こちらも、 pow(暗号化後の値, e, n) を呼び出しています… 28 Crypto/Cipher/PKCS1_OAEP.py def decrypt(self, ciphertext): … # Step 2b (RSADP) m_int = self._key._decrypt(ct_int) lib/Crypto/PublicKey/RSA.py def _decrypt(self, ciphertext): … … result = (r.inverse(self._n) * mp) % self._n … if ciphertext != pow(result, self._e, self._n): … GitHub - PyCryptodome https://github.com/Legrandin/pycryptodome

Slide 29

Slide 29 text

ファイルの暗号化/復号処理および 電子署名の生成/照合処理の作成例(自作)  ファイルの暗号化/復号処理および電子署名の生成/照合処理の作成例(自作) https://github.com/NobuyukiInoue/Example_RSA_by_pycryptodome  ファイルの暗号化 $ python rsa_main.py encrypt 暗号化したい元ファイル名 暗号化後のファイル名 公開鍵ファイル  暗号化されたファイルの復号 $ python rsa_main.py decrypt 暗号化後の出力結果ファイル 復号後のファイル名 秘密鍵ファイル  電子署名の生成 $ python rsa_main.py signature 元ファイル名 電子署名ファイル名 秘密鍵ファイル  電子署名の照合 $ python rsa_main.py verify 元ファイル名 電子署名ファイル名 公開鍵ファイル 29

Slide 30

Slide 30 text

まとめ  まとめ RSAの暗号化/復号処理の中身は べき乗と剰余(pow関数)である!! ご清聴ありがとうございました! 30