众所周知,RSA是个非常有名的非对称加密算法。我呢正在写的程序需要用到这个算法,而我还没找到靠谱的c++写的这个算法,所以,就要学一下这个算法了。

很久以前看过这个算法的介绍,又是算幂又是取模的,我那数学,直接劝退,这次要用了...

轮子

很久以前,经常看到1024位加密,2048位加密什么的,稍微看了下,这个位数越长,越安全,计算越慢。我看C++的 long long 也就32位呗,我拿64个long long 来存?
不不不,我需要高精度。高精度这东西在Github上好多,找个顺手的用吧。

密钥的生成和加解密过程

看着查的资料。有p,q,φ(n),n,e,d这6个量。

首先找两个质数,称它俩为p和q。

然后记p * q为n 。这个N的二进制位数就是加密算法的位数。

记(p - 1) * (q - 1)为φ(n)。这玩意儿不好打,还不能当变量名,我们叫它m吧。

在区间(1, m)随机找一个数,作为e。应该保证它与m互质(为什么呢?之后会用到)。

解方程d * e mod m = 1,求出d,需要整数解。

d和n是私钥。
e和n是公钥。

对于公钥加密 私钥解密

密文 = 明文 ^ e mod n
明文 = 密文 ^ d mod n

对于私钥加密 公钥解密

密文 = 明文 ^ d mod n
明文 = 密文 ^ e mod n

如何计算

首先是p和q的计算,这个算法能够正确需要p和q必须是质数。如何生成质数...

首先要生成一个随机数,然后进行质数判断,如果不是,就再随机。

生成一个随机大数我这样来做。

BigInteger RSA::BigIntRand(const unsigned int& n) const{
	srand(time(NULL));// 值随机数种子
	BigInteger r;
	for(unsigned int len = 0; len < n; len += 16){
		uint16_t i = (uint16_t)rand();
		r *= 0b10000000000000000;// 这可怜的高精度库居然没有位运算,盲猜是用十进制做的数据存储
		r += i;
	}
	return r;
}

然后是质数判断。有一个基于费马小定律的算法叫 米勒-拉宾素性检验 ,这样:

bool RSA::isPrime(BigInteger p)const{
	BigInteger s = p - 1;
	while (s % 2 == 0) {
	   s /= 2;
	}
	for (int i = 0; i < PrimeTestCount; i++) {
	   BigInteger a = BigInteger(rand()) % (p - 1) + 1, temp = s;
	   BigInteger mod = BigModularPow(a, temp, p);
	   while (temp != p - 1 && mod != 1 && mod != p - 1) {
		  mod = BigModularPow(mod, mod, p);
		  temp *= 2;
	   }
	   if (mod != p - 1 && temp % 2 == 0) {
		  return false;
	   }
	}
	return true;
}

有了高精度,上面的都不是问题,生成问题都在d和e这里,别说写程序,给我道这样的数学算数题我都算不出来。

e说是随机取,但是考虑到效率,一般都是取65537(二进制10000000000000001),好像是因为0比较多,好算。

就剩下d了,d * e mod m = 1 ,这能咋解?据我搜索,不止一种方法可以解,这里用其中一种办法。

把d写成未知数x,同时设未知数y。

d * e mod m = 1 等价于 x * e + y * m = 1,到这里要请出学oi一辈子没用到的 欧几里得算法 了。


欧几里得算法

int gcd(int a, int b){
    if (a % b==0) return b;
    else return gcd(b, a % b);
}

这个算法是拿来算最大公约数的

我有一个朋友对它倒背如流,不过貌似从来没用过...

这个算法用了递归,它递的时候是在不断辗转相除,而归的时候只是做了一件事,传回计算结果(其实还有一件事,递归用到的栈内存需要还回去,总不能直接exit(0)吧)。

其实呢,它往回归的时候是可以带走一些有用的信息的,这就是 扩展欧几里得算法。

 int ext_gcd(int a, int b, int* x,int* y){
    if( b== 0){
        *x = 1,*y = 0 ;
        return a;
    }
    else{
        int r = ext_gcd(b, a % b, x, y);
        int t = *x;
        *x = *y;
        *y = t - a / b* *y;
        return r;
    }
}

c++ 不支持函数多返回值,所以用到了指针。

扩展欧几里德算法可以在计算最大公约数的同时计算出方程ax + by = gcd(a,b)的解。

上述代码中函数的返回值为gcd(a,b),传入指针的x,y接收方程解。


x * e + y * m = 1 方程的解也就是ext_gcd(e, m, x, y)

但是需要满足gcd(e, m) = 1 ,也就是说 e和m互质。

最终解出来的x可能是个负数,如果是负数的话,应该让 x += m (资料这么说的,也不知道为啥)

这样d也就完成了。

最后加解密也不好算,这个运算是叫模幂运算(算完幂取模),说是用 蒙哥马利算法 来优化,进一步优化,维基百科上说用 "从右到左二位算法" 优化。

最后对着Lua代码抄了便C++代码,没整明白啥意思

BigInteger RSA::BigModularPow(BigInteger base_, BigInteger exp_, BigInteger mod){// 这里的变量类型应该是加上const&的,但是这个高精度库重载运算符离谱地没加const,找不到重载
	if(mod == BigInt_1){
		return 0;
	}
	BigInteger r = 1;
	// 这地方把base和exp又定义了一遍,考虑到传递来的BigInteger的数据可能是引用传递
	BigInteger base = base_;
	BigInteger exp = exp_;
	while(exp > 0){
		if(exp % 2 == 1){
			r = (r * base) % mod;
		}
		exp /= 2;
		base = (base * base) % mod;
	}
	return r;
}

至此整个RSA算法完成!

我能想到的,最大的成功就是无愧于自己的心。