ElGamal公钥密码体制是1984年斯坦福大学的Tather ElGamal提出的一种基于离散对数问题困难性的公钥体制。1985年,Tather ElGamal利用ElGamal公钥密码体制设计出ElGamal数字签名方案,该数字签名是经典数字签名方案之一,具有高度的安全性与实用性。后来,ElGamal数字签名体制的变体被使用于数字签名标准DSS中。直到今天,很多新的数字签名方案仍然属于ElGamal数字签名体制的变体或扩展。这个就是ElGamal加密算法。该算法安全性依赖于计算有限域上离散对数难题:求解离散对数(目前)是困难的,其逆运算指数运算简单。
假设有2个用户Alice 和 Bob,Bob欲使用ElGamal加密算法向Alice发送信息。
对于Alice,首先要选择一个素数q, α是素数q的本原根。
[本原根的概念对应模q乘法群(需循环群)中的生成元。]
公钥存在于某个可信公开中心目录,任何用户都可访问
对于Bob, 首先去上述中心目录访问得Alice的公钥 {q, α, YA}
然后将自己欲发送的明文M, (M ∈ [1, q - 1])准备好。
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
#include
#include
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 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 : " <