先贴学习RSA时的练习代码(uint32范围):
// RSA加密系统:
// 1. 随机选取2个大的素数p和q
// 2. 根据n=p*q式计算出n的值
// 3. 选取一个与f(n)互素的小奇数e. 其中f(n)为欧拉装载函数, 值为(p-1)(q-1)
// 4. 对于模f(n), 计算e的乘法逆元d
// 5. 公开对P=(e,n), 并把它作为RSA公开秘钥
// 6. 把对S=(d,n)进行保密, 并把它作为RSA的机密秘钥
// 加密过程: C = (M^e)%n
// 解秘过程: M = (C^d)%n
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <time.h>
// 返回一个位于low和high之间的随机数
#define ran(low,high) ((rand()%((high)-(low)+1))+(low))
// 计算数组的元素个数
#define NELEMS(x) ((sizeof(x)) / (sizeof((x)[0])))
// 构造素数序列primes[]
void makePrimes(int primes[], int num)
{
primes[0] = 2;
for(int p = 3, cnt = 1; cnt < num; p += 2)
{
for(int k = 0; ; ++k)
{
// 可以在div时得到余数
div_t dt = div(p, primes[k]);
if(dt.rem == 0) break;
if(dt.quot <= primes[k]) { primes[cnt++] = p; break; }
}
}
}
// 返回2个随机素数, 在10000~40000之间
void rand_pq(int& p, int& q)
{
// [2, 2^16]范围内有6542个素数
static int primes[1024*8];
static int low, high;
// 初始化(只处理一次)
if(low == 0 && high == 0)
{
// 生成素数序列
makePrimes(primes, NELEMS(primes));
// 计算10000和40000附近的素数下标
for(int i = 0; i < NELEMS(primes); ++i)
{
if(!low && primes[i] > 10000) { low = i; continue; }
if(!high && primes[i] > 40000) { high = i; break; }
}
// 设置随机数种子
srand(time(NULL));
}
// 在low, high之间返回2个不同的随机数
int r1 = ran(low,high);
int r2 = ran(low,high);
// r1和r2不能相等
while(r2 == r1) r2 = ran(low,high);
// 返回r1,r2位置对应的素数
p = primes[r1];
q = primes[r2];
}
// 扩展的欧几里得算法
int xgcd(int a, int b, int& x, int& y)
{
if(b == 0) { x = 1, y = 0; return a; }
int d = xgcd(b, a%b, x, y);
int t = x-(a/b)*y; x = y; y = t;
return d;
}
// 针对(p-1)(q-1)产生e和d
void rand_ed(int p, int q, int& e, int &d)
{
int n = (p-1)*(q-1);
int x, y;
// 选择一个小的互素奇数e
for(e = 37; ; e += 2)
{
if(xgcd(e, n, x, y) == 1) break;
}
// 将d转换到[0, n-1]之间
while(x > 0) x -= n;
while(x < 0) x += n;
d = x;
}
// 计算(u*v)%m
unsigned mul_mod(unsigned u, unsigned v, unsigned z)
{
// 如果u*v没有溢出, 则直接计算
if((u*v)/u == v) return (u*v)%z;
// 进行长乘法(结果为64位)
unsigned u0, v0, w0;
unsigned u1, v1, w1, w2, t;
u0 = u & 0xFFFF; u1 = u >> 16;
v0 = v & 0xFFFF; v1 = v >> 16;
w0 = u0*v0;
t = u1*v0 + (w0 >> 16);
w1 = t & 0xFFFF;
w2 = t >> 16;
w1 = u0*v1 + w1;
// x为高32位, y为低32位
unsigned x = u1*v1 + w2 + (w1 >> 16);
unsigned y = u*v;
// 进行长除法(被除数为64位)
for (int i = 1; i <= 32; i++)
{
t = (int)x >> 31; // All 1's if x(31) = 1.
x = (x << 1) | (y >> 31); // Shift x || y left
y <<= 1; // one bit.
if((x|t) >= z) { x -= z; y++; }
}
return x; // y为商, x为余数
}
// 计算(a^p)%n
unsigned pow_mod(unsigned a, unsigned p, unsigned n)
{
unsigned k = 1;
// 反复平方法
while(p > 1)
{
if(p&1) k = mul_mod(k, a, n);
a = mul_mod(a, a, n); p >>= 1;
}
return mul_mod(k, a, n);
}
// 产生RSA需要的所有参数
void rsa_rand(int& e, int& d, int& n)
{
int p, q;
rand_pq(p, q);
rand_ed(p, q, e, d);
n = p*q;
}
// 加密和解密的过程
int rsa_encryp(int m, int ed, int n)
{
assert(m > 0 && m < n);
return pow_mod(m, ed, n);
}
// RSA加密测试
int main(void)
{
int e, d, n;
int m = 119; // 需要加密的数据
printf("RSA加密系统.\n\n");
// 产生秘钥
rsa_rand(e, d, n);
printf("秘钥: e = %d, d = %d, n = %d\n\n", e, d, n);
while(1)
{
printf("\n输入数据M(0<M<n): ");
if(scanf("%d", &m) != 1) break;
if(m <= 0 || m >= n) break;
// 加密前数据
printf("加密前: %d\n", m);
// 加密文件, 秘钥为(e, n)
printf("加密后: %d\n", m = rsa_encryp(m, e, n));
// 解密文件, 秘钥为(d, n)
printf("解密后: %d\n", m = rsa_encryp(m, d, n));
}
return 0;
}
关于学习RSA的几点体会:
1. 该实现的RSA密码强度很低, 可能仅满足于学习需要
具体地n = p*q, n为32位int类型数据, 则p/q中必然 有一个值小于等于sqrt(n) <= sqrt(2^32) == 2^16. 而[0,2^16]范围内只有6542个素数, 因此因式分解n的 难度很低.
2. 实际产生大的随机素数时, 一般采用费马小定理
费马定理只是一种概率测试. 由于这里涉及的素数范围 很小, 因此直接从素数表中随机选取. 素数表的构造过 程很有意思, makePrimes从第一个素数2开始, 根据已知 的素数逐步扩充未知的素数.
3. 关于GCD
这里采用了扩展的GCD算法, 为的是得到下面的等式: x*a + y*b == gcd(a,b). 根据x可以得到模线形方程a*x=1(mod b)的解. GCD还有一种二进制的实现, 这里采用的是Euclid给的 算法.
观察代码可以发现, a/b和x/y参数的传递方向是相反: a/b在进栈的时候向栈内传递信息; x/y则是在出栈的 时候从栈底向外传递信息.
4. 关于与(p-1)*(q-1)互素的小奇数e
满足e的条件的基数很多, 但是具体取哪个呢? 最小的 是否可以?
个人觉得e的值最好不要小于log2(n). 因为如果e的值 太小, 比如为3, 那么当m^3也小于n时就相当于没有加密 了. 针对这里n为int型数据的话, e大于32应该就可以了.
5. m为1和n-1时
m为1的话, 加密解密都是1, 因为1的x次幂依然为1. m为n-1, 测试了一些数据, 加密后的值依然为n-1, 我想应该可以从数论角度给出证明, 数学比较强的朋友 可以尝试证明一下.
6. m < 0 or m >= n
因为RSA需要用模n运算, 因此如果m超出n范围的话, 解密 肯定会出现错误. 最好回避.
7. m为0
这个情况也比较特殊, 回避.
8. (a^p)%n运算
由于a^p结果可能溢出, 因此采用了长乘法(乘积为64位数).
9. 函数f(a) = (a^p)%n的循环周期
由于函数f的值域固定(0,n-1), 因此这个函数必然有循环周期. 大家有感兴趣可以自己测试一下循环周期.