椭圆曲线基本运算的优美实现
ElGamal加解密的基本原理
选择椭圆曲线
E
E
E,构造一个椭圆群
E
p
(
a
,
b
)
E_p(a, b)
Ep(a,b)
在
E
p
(
a
,
b
)
E_p(a,b)
Ep(a,b)中挑选生成生成元
G
G
G,使满足
n
⋅
G
=
O
n·G=O
n⋅G=O的最小的n是一个非常大的素数
选择一个小于
n
n
n的整数
x
x
x作为私钥,产生公钥
Y
=
x
⋅
G
Y=x·G
Y=x⋅G
加密算法
- 在区间 [ 1 , n − 1 ] [1,n−1] [1,n−1]内选取随机数 k k k,计算 k ⋅ G k·G k⋅G
- 将明文 m m m加载到点 P m P_m Pm上,计算 P m + k ⋅ Y P_m+k·Y Pm+k⋅Y
- 传送加密数据 { k ⋅ G , P m + k ⋅ Y } \{k·G,P_m+k·Y\} {k⋅G,Pm+k⋅Y}给接收方。
解密算法
接收方使用自己的私钥x计算:
(
P
m
+
k
⋅
Y
)
−
x
⋅
(
k
⋅
G
)
=
(
P
m
+
k
⋅
Y
)
−
k
⋅
(
x
⋅
G
)
=
P
m
(P_m+k·Y)−x·(k·G) = (P_m+k·Y)−k·(x·G) = P_m
(Pm+k⋅Y)−x⋅(k⋅G)=(Pm+k⋅Y)−k⋅(x⋅G)=Pm
设计难点
实现椭圆曲线加强的ElGamal算法,最重要的就是实现完善的椭圆曲线基本算法
需要注意
O
O
O点的定义、负点的定义、椭圆曲线的加法等等
在最初编写程序的时候,加法公式写错了一个符号,导致了长达几个小时的debug
之后就要进行
E
p
E_p
Ep的构造,这一部分的难点就是如何判断某个数是否是模
p
p
p的平方剩余
这里我在预处理过程中就将模
p
p
p的所有平方剩余都储存了下来,实现
O
(
1
)
O(1)
O(1)的平方剩余判断
由于合适的椭圆曲线及其生成元非常难构造,
在浏览了大量资料之后,我们确定了一组参数:
P
=
211
,
A
=
0
,
B
=
−
4
,
G
(
2
,
2
)
P = 211,A = 0,B = -4,G(2, 2)
P=211,A=0,B=−4,G(2,2)
这组参数可以满足加密的基本要求
将明文加载到椭圆曲线的点上,课本上给出了具体的方法:
但是针对具体的椭圆曲线,k的取值也不一样
在经过反复实验之后,我们发现当k=16时,可以针对0-9的数字都找到对应的椭圆曲线点
再者,我们需要实现加解密任意长度的明文和密文,因此我们对于消息进行逐(十进制)位的加解密
完整代码
算法本身的实现还是比较简单的,只要有正确的椭圆曲线基本算法支持,就能够较顺利的完成
#include<iostream>
#include<cstring>
#define LL long long
using namespace std;
const LL INF = 0x33333333;
const int MAX = 1e4;
const int N = 10;
const int Block = 1;
const int k_to_word = 16;
bool prime[MAX + 1];
LL is_square[MAX + 1];
LL PRM[MAX / 3];
LL A, B, P, K, m, c;
int tot = 0, EpCnt;
string M; //按照字符串形式输入明文和密文
struct Node {
LL x, y;
};
Node Ep[MAX + 1];
Node G, Pa, Pm;
Node Cm1, Cm2;
//扩展欧几里得求逆元
//sa+tb=gcd(a,b)
LL s, t;
void exgcd(LL a, LL b) {
if (!b) {
s = 1; t = 0;
return;
}
exgcd(b, a % b);
LL ss = s;
s = t;
t = ss - (a / b) * t;
}
LL MultiInv(LL x) {
exgcd(x, P);
return (s + P) % P;
}
bool operator ==(const Node& a, const Node& b) {
return a.x == b.x && a.y == b.y;
}
Node AddInv(const Node& a) {
Node tmp;
tmp.x = a.x;
tmp.y = P - a.y;
return tmp;
}
//加法的公式不要写错了
Node operator +(const Node& a,const Node& b) {
if (a == AddInv(b)) return Ep[0];
if (a == Ep[0]) return b;
if (b == Ep[0]) return a;
LL o;
if (a == b) {
o = (3 * a.x * a.x + A) % P * MultiInv(2 * a.y % P) % P; //注意这里很容易爆LL
}
else {
o = (b.y - a.y + P) % P * MultiInv((b.x - a.x + P) % P) % P;
}
Node ans;
ans.x = ((o * o % P - a.x + P) % P - b.x + P) % P;
ans.y = ((o * (a.x - ans.x + P) % P) % P - a.y + P) % P;
return ans;
}
Node operator *(int tm, const Node& a) {
if (tm == 1) return a;
Node ans;
ans = a + a;
for (int i = 3;i <= tm;++i)
ans = ans + a;
return ans;
}
//素数筛
void cal_prime() {
memset(prime, 1, sizeof(prime));
for (int i = 2;i <= MAX;++i)
if (prime[i]) {
PRM[tot++] = i; //素数表
for (int j = 2;j * i <= MAX;++j) prime[i * j] = 0;
}
}
void init() {
P = 211;
A = 0;
B = -4;
G.x = 2;
G.y = 2;
EpCnt = 0;
Ep[EpCnt].x = Ep[EpCnt].y = INF; //O
cout << "(INF, INF)";
//平方剩余
memset(is_square, -1, sizeof(is_square));
for (int i = 0;i <= P/2;++i)
is_square[(i * i) % P] = i;
for (int i = 0;i < P;++i) {
LL j = (((i * i) % P * i % P) + (A * i) % P + B) % P;
//判断j是否是平方剩余
if (is_square[j] >= 0) {
j = is_square[j];
Ep[++EpCnt].x = i;
Ep[++EpCnt].y = j;
cout << ", (" << i << ", " << j << ")";
if (j) {
Ep[++EpCnt].x = i;
Ep[++EpCnt].y = P - j;
cout << ", (" << i << ", " << P-j << ")";
}
}
}
//计算G
cout << "\nG(" << G.x << ", " << G.y << ")\n\n";
}
void e_ElGamal() {
//将明文加到点上
for (int i = 1;i < k_to_word;++i) {
LL x = (k_to_word * m + i) % P;
LL y = (((x * x) % P * x % P) + (A * x) % P + B) % P;
if (is_square[y]>=0) {
Pm.x = x;
Pm.y = is_square[y];
break;
}
}
cout << "嵌入明文信息m的点:\n";
cout << "(" << Pm.x << ", " << Pm.y << ")\n";
int k = rand() % (P - 2) + 1;
Cm1 = k * G;
Cm2 = Pm + k * Pa;
cout << "密文:\n";
cout << "{(" << Cm1.x << ", " << Cm1.y << "), (" << Cm2.x << ", " << Cm2.y << ")}\n";
}
void d_ElGamal() {
Pm = Cm2 + AddInv(K * Cm1);
cout << "嵌入明文信息m的点:\n";
cout << "(" << Pm.x << ", " << Pm.y << ")\n";
m = Pm.x / k_to_word;
cout << "明文:\n" << m << endl;
}
LL getbit(string s, int l, int r)
{
LL ans = 0;
for (int i = l;i <= r;++i) ans = ans * 10 + (s[i] - '0');
return ans;
}
void encrypt() {
K = rand() % (P - 2) + 1;
Pa = K * G;
cout << "\n为您自动生成的密钥:\n公开钥Pa(" << Pa.x << ", " << Pa.y << ")\n秘密钥K(" << K << ")\n";
int len = M.length();
int exlen = Block - len % Block;
for (int i = 0;i < exlen;i++) M += "0"; //补充字节
for (int i = 0;i < (int)ceil((double)len / (double)Block);++i) { //分段加密
m = getbit(M, i * Block, (i + 1) * Block - 1);
e_ElGamal();
}
}
void decrypt() {
cout << "请输入解密所需的私钥:\nK=";
cin >> K;
d_ElGamal();
}
int main()
{
srand(static_cast<unsigned int>(time(0)));
int opt;
do {
cout << "\n选择操作: 0->生成Ep(a,b)和G, 1->信息加密, 2->信息解密, 3->退出\n";
cin >> opt;
switch (opt) {
case 0:
cout << "\n自动生成Ep(a,b):\n ";
init();
break;
case 1:
cout << "\n请输入待加密明文: ";
cin >> M;
encrypt();
break;
case 2:
cout << "\n请输入待解密密文{Cm1(x,y), Cm2(x,y)}:\n";
cout << "Cm1.x=";
cin >> Cm1.x;
cout << "Cm1.y=";
cin >> Cm1.y;
cout << "Cm2.x=";
cin >> Cm2.x;
cout << "Cm2.y=";
cin >> Cm2.y;
decrypt();
break;
default:
break;
}
} while (opt != 3);
return 0;
}