Square and Multiply算法剖析(含自己的推导)

395 阅读2分钟

大家好,我是工作了三年来读水硕(真不水,太难了还是全英授课)的作家林可,最近在信息安全推导Square and Multiply(其实就是快速幂取模的一个算法,具体中文名容易混,建议还是以英文记忆)。

这门课是一个南非教授讲的,ppt逻辑性很强,不像本科时候扔一本书从第一章第二章开始念教材公式定理,当然可能是我本科没好好听课,没养成自己串联知识点的习惯。

废话不说,Square and Multiply其实是为了之后推导RSA算法的储备知识,整个ppt是循序渐进的,从幂运算到欧拉函数再到RSA,必须掌握了每个小的知识点最后才能串联起来,不然死记硬背,东记一个西记一个,没串联起来知识体系,整个人会非常的懵逼。本文仅限于解析Square and Multiply(下周要考试了没时间,之后有时间再串联起来 )

废话时间结束,让我切换专业工作态度解释和推导:

(a). 首先放ppt引出问题(问题很重要!!没有问题就没有求知欲,没有求知欲背不下来公式的)

image.png

image.png

image.png


(b). 阅读以上PPT知道Square and Multiply其实就是为了解决:

形如 aemod na^e\mod\ n,当e非常大时候怎么快速计算

method1用的方法符合人类第一直觉,一步步写出来e个11x11……x11,从左往右计算,这就引出了新的问题:这样太慢了,能不能优化?

method2(Square and Multiply)中运用了取模的分配率:

ab mod n=[(a mod n)(b mod n)] mod nab\ mod\ n=[(a\ mod\ n)(b\ mod\ n)]\ mod\ n

b=ab=a时,也就是a2 mod n=(a mod n)2 mod na2(a mod n)2a^2\ mod\ n=(a\ mod\ n)^2\ mod\ n \Rightarrow a^2\equiv(a\ mod\ n)^2

到这一步应该大家都能看懂


(c). 只不过到了第三页ppt,Square and Multiply的代码实现跟2中的解题步骤对不上了,大多数人产生疑惑都在这一步,按照method2教的,我们手写推导需要

  1. 一个个推出11的2、4、8次方对应的mod n得到m1、m2、m3
  2. 将 (m1、m2、m3)相乘再mod n

而No.3 ppt的代码实现跟上面(b)中的步骤对不上了:

#Square and Multiply Mod:
def squre_multiply_mod(a, e, n):
    z = 1
    binary = bin(e)[2:]
    #从左到右遍历二进制
    for i in binary:
        z = (z * z) % n
        if i == '1':
            z = (z * a) % n 
    return z
print("final result is ", squre_multiply(11, 15, 13))

本文主要就是为了推导(c)中的算法为什么成立,我想了半天怎么跟(b)中手写步骤产生关联,也就是怎么从2到3的,查阅了大部分资料+GPT都是胡说八道:概念太多了,千人千面,没有独立思考能力的话非常容易混淆。洗澡的时候突然悟空了,应该抛开所有上下文,单纯去证明(c)的算法成立,下面给出我是怎么撸清楚上面的代码的原理,此时让我们先忘记ppt的第一页和第二页,正如练成神功必先自宫,不破不立。

  1. 谷歌搜索得到的Square and Multiply:www.practicalnetworking.net/stand-alone… ,这里是讨论怎么讲aea^e如何减少计算步骤,并未取模。

    后者ppt和代码中的中的S&M是用于取模的,为了严谨区分防止混淆,定义它为”Square and Multiply Mod“。但是前者和后者是充分关系,可以根据前者推出后者,接下来先推导前者。

  2. 根据1中前者的链接可以知道,将一个数aea^e用"Square and Multiply"拆分的话有以下步骤

    1. Convert the exponent to Binary. (将幂转成二进制形式,从左到右遍历)
    2. For the first 1, simply list the number (如果是从左到右第一个1,直接白纸上写出底数a)
    3. For each ensuing 0, do Square operation(接下来碰到0,直接将a平方,a=a2a=a^2
    4. For each ensuing 1, do Square and Multiply operations(接下来碰到1,a=a2+1a=a^2+1)
    5. 最后得到的aa其实就是aea^e

    阅读完这个算法,并举例子其实可以知道是对的

    37 = 100101 in Binary                                         省略底数N,只看指数的变化
    1  First One lists number                  N                         1
    0  Zero calls for Square                  (N)^2                     1*2
    0  Zero calls for Square                 ((N)^2)^2                 1*2*2
    1  One calls for Square + Multiply      (((N)^2)^2)^2*N          (1*2+2)*2+1
    0  Zero calls for Square               ((((N)^2)^2)^2*N)^2       [(1*2+2)*2+1]*2
    1  One calls for Square + Multiply   (((((N)^2)^2)^2*N)^2)^2*N  {[(1*2+2)*2+1]}*2+1
    

    "省略底数N,只看指数的变化"这个步骤就是我突然悟了的关键,问题归约为证明:

    当二进制数b0b1b2b_0b_1b_2(设十进制为x)从左到右增加一位比特b3b3的时候,指数变化规律为:

    b3=1b_3=1b0b1b2b3=2x+1b_0b_1b_2b_3=2x+1,

    b3=0b_3=0b0b1b2b3=2xb_0b_1b_2b_3=2x

    乍一看也无从下手证明,我们多举具体的几个例子就能发现规律了:

    x0=十进制4=二进制(100)=1×2×2x_0=十进制4=二进制(100)=1\times2\times2,此时在100后面增加1或者0变成1001或1000

    1001=23+20=2×22+1=2×(1×2×2)+1=2x0+11001=2^3+2^0=2\times 2^2+1=2\times(1\times2\times2)+1=2x_0+1

    1000=23=2×(22)=2(1×2×2)=2x01000=2^3=2\times(2^2)=2*(1\times2\times2)=2x_0

    这会有点感觉了,但怎么推广到所有情况都成立呢?让我们来返璞归真,回到二进制转十进制算法的本质:一个二进制数可以恢复成十进制,通过若干个2的次方数相加(此时下标(0-k)从右往左排列)

    binary(bkb2b1b0)decimalbk2k+bk12k1++b121+b020binary(b_k……b_2b_1b_0)\xrightarrow{decimal}b_k2^k+b_{k-1}2^{k-1}+……+b_{1}2^1+b_{0}2^0

    如果在binary右边加上一位比特位bb,此时为new_binary(bkb2b1b0b)new\_binary(b_k……b_2b_1b_0b),显而易见,从右往左数,binary的第1位就是new_binary的第2位,相当于滑动窗口(bkb2b1b0)(b_k……b_2b_1b_0)整体左移了一位,此时bkb_k对应的2的指数应该是k+1k+1

    new_binary(bkb2b1b0b)new\_binary(b_k……b_2b_1b_0b)

    =bk2k+1+bk12k++b122+b021+b20=b_{k}2^{k+1}+b_{k-1}2^k+……+b_{1}2^2+b_{0}2^1+b2^0

    =2×(bk2k+bk12k1++b121+b020)+b=2\times (b_{k}2^k+b_{k-1}2^{k-1}+……+b_{1}2^1+b_{0}2^{0})+b

    =2×Decimal(binary)+b=2\times Decimal(binary)+b (b=0 or b=1)\qquad (b=0\ or\ b=1)

    综上, 算法square_multiply(aea^e)得证,也就是可以通过此算法计算aea^e

  3. 已知square_multiply(ae)square\_multiply(a^e),证明squre_multiply_mod适用于(ae) mod n(a^e)\ mod\ n

    #Square and Multiply
    def squre_multiply(a, e):
        z = 1
        binary = bin(e)[2:]
        #从左到右遍历二进制
        for i in binary:
            z = (z * z)
            if i == '1':
                z = (z * a)
        return z
    print("11^15=", squre_multiply(11, 15))
    
    

    回顾一下乘方的同余性质:

    如果ab (mod n)a\equiv b\ (mod\ n), 那么aebe (mod n)ea^e\equiv b^e\ (mod\ n),e是自然数。

    比如计算135 mod 11,由于132 (mod n)13^5\ mod\ 11,由于13\equiv 2\ (mod\ n),所以1352532(mod 11)13^5\equiv 2^5\equiv32(mod\ 11)

    也就是对一个指数a的乘方进行取模等价于:令初始值a0=1a_0=1,每次乘完底数aa立刻进行取模,对应到上述Square and Multiply的代码,只需要在每次乘法后立刻取模,最终就能得到ae(mod n)a^e(mod\ n)

    #Square and Multiply Mod:
    def squre_multiply_mod(a, e, n):
        z = 1
        binary = bin(e)[2:]
        #从左到右遍历二进制
        for i in binary:
            #乘完立刻取模
            z = (z * z) % n  
            if i == '1':
                #乘完立刻取模
                z = (z * a) % n 
        return z
    print("final mod is ", squre_multiply(11, 15, 13))
    

至此,上述代码squre_multiply_mod算法就是对大指数如a1111111a^{1111111}取模的最优雅且快速的实现

本文完结,码字不易,有人看点赞~