class="markdown_views prism-atom-one-light">
文章目录
简介
在之前的(1)——(4)中,一步步地实现并优化了RSA及其大数运算库,之前说,RSA的效率取决于除法,是因为计算模幂,需要使用取模,取模使用除法,最后归根结底到了除法上。
然而,有另一种思路,就是在计算模幂时,使用蒙哥马利算法。蒙哥马利算法能够将取模时的除法,转化为相对廉价的乘加和移位操作。
话说,网上的相关中文资料说得简明的可真不容易找,绿盟和FreeBuf的文档说的挺清晰的,之前还有一篇CSDN的也非常不错,搜索引擎搜到的第一篇应该就是。如果说最简明易懂的,可能还是Wikipedia上的蒙哥马利算法,不过是英文的。
算法
说明
蒙哥马利方法进行模幂,需要先实现蒙哥马利约简和蒙哥马利模乘,配合反复平方法,将原先的取模替换为使用蒙哥马利方法,实现模幂。
符号定义
设对N取模,R是一个恰好比N大的数,且R是2的m次幂,R=2m。
R·R’=1(mod N),(-N)·N’=1(mod R),-N=R-N(mod R)
蒙哥马利模乘
考虑求
z=x·y(mod N) ①
利用蒙哥马利约减来求解,蒙哥马利约简简称REDC。REDC(T)约简结果为T·R’(mod N)。只要将z按照某种“形式”输入REDC(),就有望得到最终结果。
这种“形式”,称为蒙哥马利形式。在蒙哥马利形式中:
x表示为
x·R(mod N)
y表示为
y·R(mod N)
把z也表示为蒙哥马利形式,可以是z·R=x·y·R=REDC(x·R·R)·REDC(y·R·R)·R(mod N),把这个形式输入REDC(z·R)进行蒙哥马利约减,得到的结果转换为正常形式,就是z(mod N)。
在这个过程中,需要防止y·R·R这样的连续乘法发生溢出,边乘边约简,计算R·R(mod N)再相乘。
蒙哥马利模乘算法
计算z=x·y(mod N)
x’=REDC(x·(R·R mod N))
y’=REDC(y·(R·R mod N))
z’=REDC(x’·y’)
z=REDC(z’)
return(z)
蒙哥马利约简
在蒙哥马利模乘中用到了蒙哥马利约简蒙哥马利约简REDC(T)的结果为T·R’(mod N),如果输入REDC(T·R),就能够得到T(mod N)的结果。
REDC(T)算法
m = ( ( T % R ) * N’ ) % R;
t = ( T + m * N ) / R;
if ( t >= N )
return( t - N );
else
return( t );
仔细观察这个算法,原本是要对N取模的,在这个算法里面没有出现除以N的操作,而转化为了对R进行取模和除R的操作。因为R是精心挑选的,是2的幂次方,对R进行取模和除法操作,都可以使用移位或者直接选取的方法,相对于除法是廉价的。在这个算法中,耗时的操作是加减法和乘法。
效率
仔细观察,里面涉及到了R、N、N‘、R·R(mod N),其中N’和R·R(mod N)是可以预先计算的,在一次模幂中,只需要一开始计算一次,运行时间取决于乘法。
代码实现
REDC
void mont_redc(BN DT, BN N, BN Np, BN R,BN & result)
{
BN temp1 = { 0 };
BND temp2 = { 0 };
BN m = { 0 }, t = { 0 };
unsigned int R_bits = getbits_b(R);
int Bits = (R_bits + 31) / 32;
int remain = R_bits - 32 * (Bits-1);
if (getbits_b(DT) >= R_bits)
{
for (unsigned int i = 1; i <R[0]; i++)
{
temp1[i] = DT[i];
}
if (remain >1)
{
temp1[R[0]] = (DT[R[0]] & (uint32_t)(1U<<(remain-1))-1U );
temp1[0] = R[0];
}
else {
temp1[0] = R[0]-1;
}
}
else {
cpy_b(temp1, DT);
}
mul(temp1, Np, temp2);
if (getbits_b(temp2) >= R_bits)
{
for (unsigned int i = 1; i < R[0]; i++)
{
m[i] = temp2[i];
}
if (remain > 1)
{
m[R[0]] = (temp2[R[0]] & (uint32_t)(1U << (remain - 1)) - 1U);
m[0] = R[0];
}
else {
m[0] = R[0] - 1;
}
}
else {
cpy_b(m, temp2);
}
memset(temp2, 0, sizeof(temp2));
mul(m, N, temp2);
add(DT, temp2, temp2);
蒙哥马利模乘
void mont_modmul(BN x, BN y, BN R,BN N,BN Np,BN RRN, BN & result)
{
BND temp1 = { 0 }, temp2 = { 0 }, temp3 = { 0 };
BN Xp = { 0 }, Yp = { 0 }, Zp = { 0 };
mul(x, RRN, temp1);
mul(y, RRN, temp2);
mont_redc(temp1, N, Np, R, Xp);
mont_redc(temp2, N, Np, R, Yp);
mul(Xp, Yp, temp3);
mont_redc(temp3, N, Np, R, Zp);
mont_redc(Zp, N, Np, R, result);
}
void mont_modmul(BN x, BN y, BN N ,BN & result)
{
BND temp1 = { 0 }, temp2 = { 0 }, temp3 = { 0 };
BN Xp = { 0 }, Yp = { 0 }, Zp = { 0 };
int m = getbits_b(N);
BN RRN = { 0 };
BN R = { 0 }, Rp = { 0 }, Np = { 0 };
int Bits = (m + 31) / 32;
int remain = m - 32 * (Bits - 1);
R[0] = Bits;
R[Bits] = (uint32_t)(1U <<remain);
sub(R, N, temp1);
inv_b(temp1, R, Np);
modmul(R, R, N, RRN);
模幂
void mont_modexp(BN a, BN b, BN N, BN & result)
{
int m = getbits_b(N);
BN RRN = { 0 };
BN R = { 0 }, Rp = { 0 }, Np = { 0 };
BN a_t = { 1,1 }, b_t;
BN temp1 = { 0 }, temp2 = { 0 };
BN Xp = { 0 }, Yp = { 0 };
int Bits = (m + 31) / 32;
int remain = m - 32 * (Bits - 1);
R[0] = Bits;
R[Bits] = (uint32_t)(1U << (remain - 1));
sub(R, N, temp1);
inv_b(temp1, R, Np);
modmul(R, R, N, RRN);
memset(result, 0, sizeof(result));
cpy_b(b_t, a);
uint32_t *nptr, *mnptr;
nptr = LSDPTR_B(b);
mnptr = MSDPTR_B(b);
char binform[33];
int i = 0;
while (nptr <= mnptr)
{
memset(binform, 0, sizeof(binform));
_ultoa(*nptr, binform, 2);
i = strlen(binform) - 1;
for (int j = 31; j >= 0; j--)
{
if (i >= 0)
{
if (binform[i] == '1')
{
mont_modmul(a_t,b_t, R, N, Np, RRN, a_t);
}
i--;
}
mont_modmul(b_t, b_t, R, N, Np, RRN, b_t);
}
nptr++;
}
cpy_b(result, a_t);
RMLDZRS_B(result);
}
运行结果
运行速度比使用除法的慢不少,应该是代码写得比较low的原因,怀疑移位函数shr_b()写得不够巧妙,乘法函数应该是没有问题,乘法使用快速乘法相比原先的乘法,速度上的贡献并不是特别大,而且数位只有1024位体现不出来。
鉴于速度暂时比原先的慢(或者真的是慢),这个版本的暂不同步到github。希望大神们能指出这其中的不足之处,非常感谢。