幂取模算法

在众多的加密算法中都需要进行幂的取模运算,比如在RSA算法中需要计算ne mod N,我们称之为幂模算法,其中:

  • N=p*q(p,q为大素数)
  • n为加密数据,n<N
  • e为公钥,d为私钥,满足关系ed≡1 (mod (p-1)*(q-1))

其中n,e都是非常大的数,这样计算ne mod N就需要一些新方法,其计算方式也关系到RSA的效率问题。

对一般的幂模运算:ab mod m,存在下面三种算法:

1. 直接计算

先计算ab,再取模,这样很容易益处,在实际中基本不可行

2. 同余公式

设c是a除以m的余,即c=a-k*m,也可用同余表达式a≡c (mod m)表示,则可以证明:

    2.1 同余性质1:

    对任意整数b

    ab≡bc (mod m) ———–(1)

    证明:

        c=a mod m <=> a = km +c

        =>ab = k*b*m+bc => ab mod m = (k*b*m + bc)mod m = bc mod m

    (1)式等价于:ab mod m =b* (a mod m) mod m,这非常适合递归计算。

    2.2 同余性质2

    a≡c (mod m) => a2≡c2 (mod m)————–(2)

    证明:

        a=k*m+c =>a2=(km)2+2ckm+c2 =>a2 mod m =c2 mod m,即(2)成立

    2.3 基于性质1的算法

    特别地对幂取模计算可以递归使用(1)式

int mod(int a,int b,int m){
    int result = 1;
    for(int i=0;i<b;i++) {
        result = (result*a) %m;
    }
    return result;
}

    如果指数b不大,上述方法很有效。但如前所述,一般在RSA算法中的指数b会很大(可以超过1024位),对如此巨大的一个数字做循环计算,时间开销将是不能接受的,因此需要继续从理论上挖掘计算幂取模的算法。

3.反复平方法

进一步研究指数b的二进制表示发现,对任意的整数b都可表示为:

《幂取模算法》

  • n表示b的实际二进制位数
  • bi表示该位是0或1

因此,ab可表示为:

《幂取模算法》

即用b的每一位表示a的每一项,而对任意相邻的两项存在平方关系,即:

《幂取模算法》

因此我们构造下面的算法:

  • 把b转换为二进制表示,并从右至左扫描其每一位(从低到高)
  • 当扫描到第i位时,根据同余性质(2)计算a的第i项的模:
    《幂取模算法》
    base变量表示第i-1位时计算出的模,通过递归能很容易地确定所有位的模。
  • 如果第i位为1,即bi=1,则表示该位需要参与模运算,计算结果 result = (result*base) mod m;其中result为前i-1次的计算结果;若bi=0,则表示a的第i项为1,不必参与模运算

代码如下:

int mod(int a,int b,int m){
    int result = 1;
    int base = a;
    while(b>0){
         if(b & 1==1){
            result = (result*base) % m;
         }
         base = (base*base) %m;
         b>>>=1;
    }
    return result;
}

这其中有几个点要理解:

  • base记录了a的每项的模,无论b在该位是0还是1,该结果都记录,目的是给后续位为1的项使用,计算方式是前一结果的平方取模,这也是反复平方法的由来
  • result只记录了位为1的项的模结果,该计算方式使用了同余性质1
  • 通过地把a使用二进制表示,并结合同余性质1,2,巧妙地化解了大数取模的运算。对1024位这样的大数,也最多进行1024次循环便可计算模值,性能非常快。

该方法是许多西方数学家努力的结果,通常也称为Montgomery算法。

4. 参考资料

【1】http://en.wikipedia.org/wiki/Modular_exponentiation

【2】初等数论

点赞