# 學習筆記:RSA非對稱加密(python) ## 簡介&基本原理 RSA是一種非對稱加密,也就是加密與解密分別由公鑰與私鑰進行。公鑰用於加密,私鑰用於解密,公鑰加密的訊息只有私鑰解的開。(用於數位簽名時,可能變成私鑰加密,公鑰解密) 過去使用對稱式加密時,加解密兩方都需要知道密鑰,而密鑰傳輸的過程難免會有風險。這種風險可以由非對稱式加密避免,因為只要將公鑰傳遞出去就好,私鑰只要不洩漏就是安全的。(不考慮時間攻擊的話) ## 擴展歐幾里得 #### 歐幾里得算法(輾轉相除法) 就是普通的輾轉相除法 $gcd(a,b)=gcd(b,a\%b)$ $gcd(a,0) = a$ 實作也很簡單 ```py= def gcd(a, b): if b == 0: return a else: return gcd(b, a % b) ``` #### 擴展歐幾里得 擴展歐幾里得算法是用來求只知$a$、$b$下$ax+by=gcd(a,b)$的$x$、$y$ $ax+by=gcd(a,b)$ $\ \ \ \ \ \ \ \ \ \ \ \ \ =gcd(b,a\ mod \ b)$ $\ \ \ \ \ \ \ \ \ \ \ \ \ =gcd(b,a-\lfloor\frac{a}{b} \rfloor b$ $\ \ \ \ \ \ \ \ \ \ \ \ \ =bx^{\prime}+(a-\lfloor\frac{a}{b} \rfloor b)y^{\prime}$ $\ \ \ \ \ \ \ \ \ \ \ \ \ =ay^{\prime}+b(x^{\prime}-\lfloor\frac{a}{b} \rfloor y^{\prime})$ 得$x=y^{\prime}$、$y=x^{\prime}-\lfloor\frac{a}{b} \rfloor y^{\prime}$ 所以只要先求出$x^{\prime}$和$y^{\prime}$就好了 因為$gcd(a,0)=a$,遞迴到$gcd(a,0)$時$y^{\prime}=1$,$x^{\prime}=0$ 代入公式就可以得到前一層遞迴的$x$,$y$了 ```python= def extended_gcd(a, b): if b == 0: return a, 1, 0 gcd, x, y = extended_gcd(b, a % b) return gcd, y, x - a//b * y ``` ## 算模擬元 當使用擴展歐幾里得找到一組$x$、$y$滿足$ax+by=1=gcd(a,b)$時,可得出$ax≡1(mod\ b)$,$x$就是$a$的模反元素 ## 密鑰生成 1. 挑選兩個質數$p、q$ ,$n = pq$ 2. $φ(n)=φ(p)φ(q)=(p-1)(q-1)$ 3. 求$e$,在1到$φ(n)$之間選一數$e$,且滿足$gcd(e,φ(n))=1$ (滿足此條件才能算模反元素,第四步會用到) 5. 計算$e$與$φ(n)$的模逆元$d$,也就是滿足$ed≡1\ mod\ φ(n)$(這是同餘,詳見[模運算](/564I9dxSTf6crJP95qRnlQ)) $p$、$q$、$φ(n)$、$d$皆要避免洩漏 公鑰對$(e,n)$ 私鑰對$(d,n)$ ## 加密 $M$為明文換成的整數,$C$為加密成的整數 加密:$C=M^{e}\ mod\ n$(其中$M<n$,因為會$mod\ n$) 解密:$M = C^{d}\ mod\ n$ ## 解密 解密$M = C^{d}\ mod\ n$ ### 原理 >[證明參考wiki](https://zh.wikipedia.org/zh-tw/RSA%E5%8A%A0%E5%AF%86%E6%BC%94%E7%AE%97%E6%B3%95) $C=M^{e}\ mod\ n$ $C^{d}=(M^{e})^{d}\ mod\ n$ 因$ed≡1\ mod\ φ(n)$ 故存在整數$k$,使$ed=kφ(n)+1$ $M^{ed}=M^{1+φ(n)}=M*M^{kφ(n)}=M(M^{φ(n)})^{k}$ 若$n$與$M$互質,則由歐拉定理可得 $M^{ed}≡M(M^{φ(n)})^{k}≡M(1)^k≡M\ mod\ n$ 故$M^{ed}≡M\ mod\ n$ 得證 ## 實作 github:https://github.com/samsonjaw/RSA ```cpp= import random import math def extended_gcd(a, b):#ax + by = gcd if b == 0: return a, 1, 0 gcd, x, y = extended_gcd(b, a % b) return gcd, y, x - a//b * y def mod_inverse(a, m):# a x b ≡ 1 (mod n) #e x d ≡ (mod phi(n) ) g, x, y = extended_gcd(a, m) if(g != 1): print('don\'t have inv') return x % m def generate_key(p,q): n = p * q # phi(n) = phi(p) * phi(q) = (p-1)(q-1) phi = (p - 1) * (q - 1) e = random.randrange(1, phi) g, _, _= extended_gcd(e, phi) while g != 1: e = random.randrange(1, phi) g, _, _= extended_gcd(e, phi) d = mod_inverse(e,phi) return (e, n), (d ,n) def encrypt(public_key, plaintext): #ciphertext = m^e mod n e, n = public_key if isinstance(plaintext, int): return plaintext ** e % n elif isinstance(plaintext, (tuple, list)): return [s ** e % n for s in plaintext] def decrypt(private_key, ciphertext): d, n = private_key if isinstance(ciphertext, int): return ciphertext ** d % n elif isinstance(ciphertext, (tuple, list)): return [s ** d % n for s in ciphertext] def encode_int(str): int_list = [] for i in range (len(str)): int_list.append(ord(str[i])) return int_list def decode_chr(nums): chr_list = [] for i in range(len(nums)): chr_list.append(chr(nums[i])) return "".join(chr_list) # n > plaintext, 2q > p > q #plaintext(ascii) should < n, or plaintext will loss when decrypt def main(): p, q = 181, 281 print('This program is just for studying. p and q is small, so it is not safe.') while True: print('=================choose the function=================') print(' k.generate key') print(' e.encrypt') print(' d.decrypy') print(' 0.end') func = input('input the letter to choose the function:') if func == 'k': public_key, private_key = generate_key(p, q) tmp1 = ' '.join(map(str, public_key)) tmp2 = ' '.join(map(str, private_key)) print(f'public_key:{tmp1},private_key:{tmp2}') elif func == 'e': plaintext = input('Input your plaintext.(make sure that each char is < p*q):') while True: u_input = input('please input public key, and split e n by a space:') try: e, n = map(int,u_input.split()) public_key = (e, n) break except ValueError: print('Invalid input. Please ensure your two numbers separated by a space.') plaintext_int = encode_int(plaintext) tmp = ' '.join(map(str, plaintext_int)) print(f'plaintext(int): {tmp}') ciphertext_int = encrypt(public_key, plaintext_int) tmp = ' '.join(map(str, ciphertext_int)) print(f'ciphertext(int): {tmp}') ciphertext = decode_chr(ciphertext_int) print(f'ciphertext: {ciphertext}') elif func == 'd': while True: try: u_inpur = input('Input your ciphertext(int) which is split by a space:') ciphertext = [int(x.strip()) for x in u_input.split(' ')] break except: print('Invalid input. Please ensure your all numbers separated by a space.') while True: u_input = input('please input public key, and split e n by a space:') try: d, n = map(int, u_input.split()) private_key = (d, n) break except ValueError: print('Invalid input. Please ensure your two numbers separated by a space.') plaintext_int = decrypt(private_key, ciphertext_int) tmp = ' '.join(map(str, plaintext_int)) print(f'plaintext(int):{tmp}') plaintext = decode_chr(plaintext_int) print(f'plaintext:{plaintext}') elif func == '0': break else: print('Invalid input') if __name__ == "__main__": main() ``` ## 安全性 根據上述理論,由於公鑰和私鑰都包含n 要破解RSA似乎只要將n質因數分解,得到p,q就可以算出$φ(n)=(p-1)(q-1)$,進而得到私鑰中的d 然而,就現代數學而言,分解一個足夠大的n是沒有有效算法的,隨著p、q大小的增加,分解n所需的時間也會急遽增加,故只要n夠大,破解RSA幾乎不可能 (以目前的算力而言2048為的n被認為是安全的最小長度)(不考慮時間攻擊的話) ## 應用 1. HTTPS協議就是用RSA傳輸數據 2. 數位簽名 (詳見[數位簽名](/2h6yNrrDSYWD8eDGaAgcsQ)) 3. 安全電子郵件 4. 身分認證 5. VPN . . .