密碼學——ELGamal算法
ElGamal公鑰密碼體制是1984年斯坦福大學的Tather ElGamal提出的一種基於離散對數問題困難性的公鑰體制。1985年,Tather ElGamal利用ElGamal公鑰密碼體制設計出ElGamal數字簽名方案,該數字簽名是經典數字簽名方案之一,具有高度的安全性與實用性。後來,ElGamal數字簽名體制的變體被使用於數字簽名標準DSS中。直到今天,很多新的數字簽名方案仍然屬於ElGamal數字簽名體制的變體或擴展。這個就是ElGamal加密算法。該算法安全性依賴於計算有限域上離散對數難題:求解離散對數(目前)是困難的,其逆運算指數運算簡單。
算法思路
假設有2個用戶Alice 和 Bob,Bob欲使用ElGamal加密算法向Alice發送信息。
對於Alice,首先要選擇一個素數q, α是素數q的本原根。
[本原根的概念對應模q乘法羣(需循環羣)中的生成元。]
- Alice產生一個XA, XA∈(1, q - 1)
- 計算YA = αXA mod q
- A的私鑰爲XA, 公鑰爲 {q, α, YA}
公鑰存在於某個可信公開中心目錄,任何用戶都可訪問
對於Bob, 首先去上述中心目錄訪問得Alice的公鑰 {q, α, YA}
然後將自己欲發送的明文M, (M ∈ [1, q - 1])準備好。
- 選一個隨機整數k, k ∈ [1, q - 1]
- 計算可解密自己密文的祕鑰 PrivateK = (YA)k mod q (即αXA*k mod q)
- 將M加密成明文對(C1, C2) 其中C1 = αk mod q, C2 = PrivateK * M mod q
- 明文對發送給Alice
Alice收到明文對:
PrivateK = (C1)XA mod q(即αk*XA mod q)
M = C2 * PrivateK-1 mod q
算法大多就是一些乘法,求冪之類的運算。剩下個關鍵內容就是如何尋找素數p的本原根,或者說如何找有限域GF§中的生成元。
我們在羣這個概念裏討論。
p是素數,令Zp = {1, 2, 3, …, p - 1},因爲考慮乘法,去掉了0元素。
2個定理:
Euler定理:設P和a是互素的兩個整數,則有aφ§=1 mod p
拉格朗日定理: 設 G 是有限羣, H 是 G 的子羣,|H| 整除 |G|
回顧這樣2個概念:設G是羣, a∈G, 使得等式ak = e成立的最小整數k稱爲元素a的階。而羣的階是指該羣中的元素個數。值得留意的是,以某個生成元去生成某個子羣,該子羣的階就是該元素的階(當然了)。
因Zp中所有元素與p互素,由歐拉定理,Zp中所有元素的階不超過p-1,(因爲羣的階φ§是p-1,而至少有aφ§=1 mod p)。
對於Zp中的任一元素,以該元素爲生成元形成的一個循環羣,設爲S(羣S的階在數值上即該元素的階),根據羣的封閉性可知S必然爲Zp的子羣,根據拉格朗日定理,可知Zp的元素的階必然是|Zp| (即p-1)的因子。
於是可以得到這樣一個結論:若有這樣一個元素a,其階爲Ka, Ka是p-1的平凡因子(即因子1 或者因子p-1), 那麼a或者是單位元,或者是生成元。 又知Zp的單位元是1,那麼根據單位元的唯一性,可知若a非1,則a必爲生成元。問題在於,p-1的因子可能很多,我們還不是得一個個去找到階是p-1的平凡因子的元素?
爲此,我們構造一種特殊的素數,使得p-1的因子數量很少。取p - 1 = 2 * Q ,其中p是素數,Q也是素數。 因爲Q是素數,因子僅1, Q。所以p - 1的因子只有 {1, 2, Q, p - 1}四個。
到此已經非常明朗,我們找到滿足上述條件的素數p,然後在Zp中尋找這樣一個元素a,a的階非2,非Q,即a^2 mod p != 1 && a^Q mod p != 1,若a又非單位元1,那麼a必然是生成元。
留意Zp未必一定有生成元, 若1 到 (p - 1)經上述檢驗都不滿足, 考慮另取一個素數p。至於代碼實現上出現的問題:若mpz_probab_prime_p(tmp.mt, 6) == 1 改爲 mpz_probab_prime_p(tmp.mt, 6) == 2,p一旦較大,程序運行速度很慢。取2爲真素數檢驗,速度很慢,1爲概率素數檢驗,速度快。
算法實現
本人也在求學路上,能力有限,這裏借鑑一下大神們寫出的腳本:
#include<bits/stdc++.h>
#include<gmp.h>
#include<gmpxx.h>
using namespace std;
#define mt get_mpz_t()
typedef mpz_class bn;
gmp_randstate_t rstate;
struct public_keys {
// Big prime p, primitive root, Y = XA^(primitive root)
bn p, pr, Y;
public_keys(bn _p = 0, bn _pr = 0, bn _Y = 0) : p(_p), pr(_pr), Y(_Y) {}
};
// Trusted third party 一個所有用戶可訪問的公開中心目錄
map<string, public_keys> ttp;
// 返回start 到 end的一個隨機數
inline bn randNum(bn start, bn end) {
bn tmp;
mpz_urandomm(tmp.mt, rstate, bn(end + 1 - start).mt);
return tmp + start;
}
// 返回 a^b mod n
inline bn aebmodn(bn a, bn b, bn n) {
bn ret;
mpz_powm(ret.mt, a.mt, b.mt, n.mt);
return ret;
}
// 在2^bits範圍內生成一副公鑰,存於公開目錄中,記在name名下 返回私鑰
bn elgmal_keyGen(string name, unsigned long int bits) {
bn p = 2, pr = -1, Y = -1, bounds = 2;
mpz_pow_ui(bounds.mt, bounds.mt, bits);
bool found = 0;
while(1) {
mpz_urandomm(p.mt, rstate, bounds.mt);
mpz_nextprime(p.mt, p.mt);
bn tmp = (p - 1) / 2;
if (p < bounds && mpz_probab_prime_p(tmp.mt, 6) == 1) {
// pr是1到p-1的一個數
pr = randNum(1, p - 1);
while (1) {
// pr^tmp % p
bn pexpn = aebmodn(pr, tmp, p);
if (pr != 1 && (pr * pr) % p != 1 && pexpn != 1) {
found = 1;
break;
}
pr = (pr + 1) % p;
}
}
if (found) break;
}
bn XA = randNum(2, p - 2);
Y = aebmodn(pr, XA, p);
ttp[name] = public_keys(p, pr, Y);
return XA;
}
// 密文對(C1, C2)
struct cipher_text {
bn c1, c2;
cipher_text(bn _c1 = 0, bn _c2 = 0) : c1(_c1), c2(_c2) {}
};
/*
* 根據公開中心目錄中name名下的公鑰 對明文m進行加密
* 返回密文對(C1, C2)
*/
cipher_text elgamal_encrypt(string name, bn m) {
public_keys pk = ttp[name];
bn k = randNum(1, pk.p - 1);
bn privateKey;
mpz_powm(privateKey.mt, pk.Y.mt, k.mt, pk.p.mt);
bn cipher1 = aebmodn(pk.pr, k, pk.p);
bn cipher2 = (m * privateKey) % pk.p;
return cipher_text(cipher1, cipher2);
}
// 根據在自己name名下的公鑰及 自己的祕鑰dk 解密密文對ct 返回明文
bn elgamal_decrypt(string name, bn dk, cipher_text ct) {
public_keys pk = ttp[name];
bn privateKey = aebmodn(ct.c1, dk, pk.p);
bn k_inverse;
mpz_invert(k_inverse.mt, privateKey.mt, pk.p.mt);
return (ct.c2 * k_inverse) % pk.p;
}
int main() {
gmp_randinit_mt(rstate);
gmp_randseed_ui(rstate, time(NULL));
// Alice初始化自己在中心的公鑰並得到自己的私鑰
bn dk = elgmal_keyGen("Alice", 128);
cout<<"Input the message: ";
bn n; cin>>n;
if (n > ttp["Alice"].p) cout<<"message's length is out of bounds\n";
// Bob根據Alice用戶信息對明文n加密
cipher_text ct = elgamal_encrypt("Alice", n);
// Alice進行解密
bn res = elgamal_decrypt("Alice", dk, ct);
cout<<"decrypt result : " <<res <<"\n";
return 0;
}