目錄
前兩天二刷了《模仿遊戲》,Alan Turing在二戰中研製的圖靈機破譯了德軍號稱牢不可破的Enigma密碼機。這部劇讓我對計算機產生了一些新的理解,結合以前修過的密碼學原理課程,因此想記錄一下之前沒掌握好的數論知識,並且以RSA公鑰密碼爲例,解一道經典題。
1 逆元定義
若,且 a 與 b 互斥,那麼就能定義 x 爲 a 的逆元,記爲,也可稱爲 x 爲 a 的倒數。
2 歐幾里得算法(求最大公約數)
首先介紹古老而又強大的歐幾里得算法(又稱輾轉相除法):
兩個數 a 和 b 的最大公因子
(greatest common divisior)是能整除它們兩者的最大整數。歐幾里德算法用於計算兩個整數 a,b 的最大因子。記 gcd(a,b)
爲自然數 a與 b的最大公因子。特別的,有 gcd(0, n) = 0,因爲任何整數都能整除 0。
內容:
代碼:
int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
3 擴展歐幾里得算法
3.1 預備知識
1.取模運算:
(a + b) % c = (a % c + b % c) % c
(a * b) % c = (a % c * b % c) % c
取模運算對除法不成立,當要求(a / b) % c
時,可轉化爲逆元來求:
這就是逆元的作用。
2.裴蜀定理:
給予兩個整數 a,b,必存在整數 x,y 使得
即如若 有解,那麼有 (c一定是的若干倍)
特例:當時,如果 有解,那麼
3.2 關於擴展歐幾里得算法
- 擴展歐幾里得算法(Extended Euclidean algorithm)是歐幾里得算法的擴展。它可用來求解形如)方程的一組整數解。
- 對於求解方程,只需求解出方程的一組解,將 分別乘上即可得到方程 的一組解。
- 擴展歐幾里得算法可以用來計算模反元素(也叫模逆元),而模反元素在RSA加密算法中有舉足輕重的地位。
3.3 模板
(思考了一下該先放模板,還是先放算法的證明過程,最後決定先放模板, 然後就着模板理解算法的證明過程)
ll ex_gcd(ll a, ll b, ll &x, ll &y){
//標記1
if(b == 0){
x = 1, y = 0;
return a;
}
ll d = ex_gcd(b, a % b, x, y);
ll temp = y;
//標記2
y = x - (a / b) * y;
x = temp;
//標記3
return d;
}
3.4 算法推導過程
現有方程:
記ex_gcd(a, b, x, y)
爲求解上述方程的函數,函數返回的是的最大公約數(對應模板代碼標記3
),其中形參 爲引用參數(全局變量),也是上述方程的解。
- 邊界情況(對應模板代碼
標記1
):
當 時,方程爲,解得
此時a就是的最大公約數,因此函數return a
。 - 一般情況(對應模板代碼
標記2
):
當時,有歐幾里得算法
則有方程
另外有等式(不是啥公式,一個運算技巧):
則方程可化爲
上式化簡得:
於是可以得到關於方程解的遞推關係:
- 由此我們得到了邊界條件以及遞歸式,即每次遞歸
ex_gcd(b, a mod b, x, y)
,稍加處理,即可求得方程的一組解。
3.5 利用拓展歐幾里得算法求逆元
那麼問題來了,我們通過函數
ex_gcd(a, b, x, y)
,求得(即最大公約數)的結果,以及一組方程解,對求逆元有什麼作用?
1.根據逆元定義
當我們求 在 情況下的逆元時,假設逆元爲x,即
轉化等式:
移項
則最小的 即爲 在 情況下的一個逆元
2.由裴蜀定理:
當 時,由擴展歐幾里得函數ex_gcd(a, b, x, y)
求得的方程解 即爲我們所求的最小乘法逆元。
3.因此下面代碼中的標記4
得到解釋:
//拓展歐幾里得算法
ll ex_gcd(ll a, ll b, ll &x, ll &y){
//標記1
if(b == 0){
x = 1, y = 0;
return a;
}
ll d = ex_gcd(b, a % b, x, y);
ll temp = y;
//標記2
y = x - (a / b) * y;
x = temp;
//標記3
return d;
}
//求a在mod下的逆元x
ll getInv(ll a, ll mod){
ll x, y;
ll d = ex_gcd(a, mod, x, y);
//標記4
return d == 1 ? (x + mod) % mod : -1;
}
- 當擴展歐幾里得函數
ex_gcd(a, b, x, y)
返回的最大公約數 時, 即爲 下的最小乘法逆元,(x + mod) % mod
是爲了將 調整到 0 ~ (b - 1)的範圍中。 - 當函數的返回值 時,即說明逆元不存在,返回 。
- 拓展歐幾里得求逆元的時間複雜度:
- 適用範圍:只要存在逆元即可求,適用於個數不多但是mod很大的時候,也是最常見的一種求逆元的方法。
4 費馬小定理
4.1 定義
如果 是一個質數,且整數 不是 的倍數,則有:
- 由公式得:
因此 即爲 在 意義下的逆元。 - 而 可用快速冪求解(關於快速冪原理可在此博客瞭解:快速冪 & 快速乘原理講解(模板))
4.2 模板
//快速冪
ll ksm(ll a, ll p, ll mod) {
ll ans = 1, base = a % mod;
while(p) {
if(p & 1) {
ans = (ans * base) % mod;
}
base = (base * base) % mod;
p >>= 1;
}
return ans;
}
//求逆元
ll getInv(ll a, ll mod){
return ksm(a, p - 2, mod);
}
- 費馬小定理求逆元的時間複雜度:
- 適用範圍:根據費馬小定理,爲質數時適用。並且比擴展歐幾里得算法好寫一些。
5 歐拉定理
歐拉定理(也稱費馬-歐拉定理),是一個關於同餘的性質。歐拉定理表明,若 爲正整數,且 互質,則:
- 由公式得:
因此 即爲 在 意義下的逆元。 - 可以看到,費馬小定理是歐拉定理的特例,當 爲質數時,,
- 歐拉定理比費馬小定理適用範圍更廣,因爲模數 可以不是質數。
- 一般很少有人直接用歐幾里得求逆元(偷個懶,板子就不貼上來了)
6 RSA公鑰密碼經典例題
說了這麼多廢話 (正經話),終於可以開始做題了。
6.1 題目描述
RSA是一種經典的加密算法。它的基本加密過程如下。
首先生成兩個質數 ,令 ,設 與 互質,則可找到 使得 除 的餘數爲 1。
組成了私鑰, 組成了公鑰。
當使用公鑰加密一個整數 時(小於 ),計算 ,則 是加密後的密文。
當收到密文 時,可使用私鑰解開,計算公式爲 。
例如,當 時,。
若加密數字,得.
解密數字,得.
現在你知道公鑰中 ,同時你截獲了別人發送的密文,請問,原文是多少?
6.2 分析
- 由題目已知,根據解密公式 ,只需求得私鑰 ,即可解得原文 。
- 根據題目信息: 與 互質,則可找到 使得 除 的餘數爲 1,翻譯成
人話(公式)就是: - 設 ,即:
- 由上述公式可得 即爲 在 意思下的乘法逆元。在這裏我們用拓展歐幾里得算法求解。
- 最後一個問題:關於 即 應該如何求解。這裏用到質因數分解,由題目可知 ,且 均爲質數。我們先找到一個小於 的質數 ,再用 即可得到 。
6.3 題解
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
ll n = 1001733993063167141, d = 212353, c = 20190324;
//判斷質數
ll isPrime(ll x){
for(ll i = 2;i <= x;i++){
if(x % i == 0){
return i;
}
}
}
//擴展歐幾里得算法
ll ex_gcd(ll a, ll b, ll &x, ll &y){
if(b == 0){
x = 1, y = 0;
return a;
}
ll d = ex_gcd(b, a % b, x, y);
ll temp = y;
y = x - (a / b) * y;
x = temp;
return d;
}
//求a在mod下的乘法逆元x
ll getInv(ll a, ll mod){
ll x, y;
ll d = ex_gcd(a, mod, x, y);
return d == 1 ? (x + mod) % mod : -1;
}
//快速乘
ll ksc(ll a, ll b, ll mod) {
ll ans = 0;
while(b) {
if(b & 1) {
ans = (ans + a) % mod;
}
a = (a + a) % mod;
b >>= 1;
}
return ans;
}
//快速冪
ll ksm(ll a, ll b, ll mod) {
ll ans = 1, base = a;
while(b) {
if(b & 1) {
ans = ksc(ans, base, mod) % mod;
}
base = ksc(base, base, mod) % mod;
b >>= 1;
}
return ans;
}
int main(){
ll p = isPrime(n), q = n / p, k = (p - 1) * (q - 1);
ll e = getInv(d, k);
// printf("e is %lld\n", e);
ll x = ksm(c, e, n);
printf("%lld\n", x);
return 0;
}
- 注意到題目中的數據實在是太大了,單獨用快速冪求解 會導致數據溢出,因此我們對快速冪優化了一下(用快速乘求每次 的餘數,再快速冪對餘數進行冪運算),達到模擬大數模冪運算的效果。
- 相關博客可參考:快速冪 & 快速乘取模(模擬大數模冪運算,解決乘法爆long long問題)
運算結果:
- 答案:
579706994112328949
- RSA是非對稱加密算法,基於大數分解,題目只是模擬了一下RSA解密的過程,可以看到當 時,我的電腦跑出結果就花了十幾秒。而真正RSA密碼中, 一般而言可達到 比特,普通的計算機跑出結果要十年左右,量子計算機需一週。
參考博客:歐幾里德算法與擴展歐幾里德算法