大家好,我是工作了三年来读水硕(真不水,太难了还是全英授课)的作家林可,最近在信息安全推导Square and Multiply(其实就是快速幂取模的一个算法,具体中文名容易混,建议还是以英文记忆)。
这门课是一个南非教授讲的,ppt逻辑性很强,不像本科时候扔一本书从第一章第二章开始念教材公式定理,当然可能是我本科没好好听课,没养成自己串联知识点的习惯。
废话不说,Square and Multiply其实是为了之后推导RSA算法的储备知识,整个ppt是循序渐进的,从幂运算到欧拉函数再到RSA,必须掌握了每个小的知识点最后才能串联起来,不然死记硬背,东记一个西记一个,没串联起来知识体系,整个人会非常的懵逼。本文仅限于解析Square and Multiply(下周要考试了没时间,之后有时间再串联起来 )
废话时间结束,让我切换专业工作态度解释和推导:
(a). 首先放ppt引出问题(问题很重要!!没有问题就没有求知欲,没有求知欲背不下来公式的)
(b). 阅读以上PPT知道Square and Multiply其实就是为了解决:
形如 ,当e非常大时候怎么快速计算
method1用的方法符合人类第一直觉,一步步写出来e个11x11……x11,从左往右计算,这就引出了新的问题:这样太慢了,能不能优化?
method2(Square and Multiply)中运用了取模的分配率:
当时,也就是
到这一步应该大家都能看懂
(c). 只不过到了第三页ppt,Square and Multiply的代码实现跟2中的解题步骤对不上了,大多数人产生疑惑都在这一步,按照method2教的,我们手写推导需要
- 一个个推出11的2、4、8次方对应的mod n得到m1、m2、m3
- 将 (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的第一页和第二页,正如练成神功必先自宫,不破不立。
-
谷歌搜索得到的Square and Multiply:www.practicalnetworking.net/stand-alone… ,这里是讨论怎么讲如何减少计算步骤,并未取模。
后者ppt和代码中的中的S&M是用于取模的,为了严谨区分防止混淆,定义它为”Square and Multiply Mod“。但是前者和后者是充分关系,可以根据前者推出后者,接下来先推导前者。
-
根据1中前者的链接可以知道,将一个数用"Square and Multiply"拆分的话有以下步骤
- Convert the exponent to Binary. (将幂转成二进制形式,从左到右遍历)
- For the first 1, simply list the number (如果是从左到右第一个1,直接白纸上写出底数a)
- For each ensuing 0, do Square operation(接下来碰到0,直接将a平方,)
- For each ensuing 1, do Square and Multiply operations(接下来碰到1,)
- 最后得到的其实就是
阅读完这个算法,并举例子其实可以知道是对的
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,只看指数的变化"这个步骤就是我突然悟了的关键,问题归约为证明:
当二进制数(设十进制为x)从左到右增加一位比特的时候,指数变化规律为:
若,,
若,
乍一看也无从下手证明,我们多举具体的几个例子就能发现规律了:
记,此时在100后面增加1或者0变成1001或1000
这会有点感觉了,但怎么推广到所有情况都成立呢?让我们来返璞归真,回到二进制转十进制算法的本质:一个二进制数可以恢复成十进制,通过若干个2的次方数相加(此时下标(0-k)从右往左排列)
如果在binary右边加上一位比特位,此时为,显而易见,从右往左数,binary的第1位就是new_binary的第2位,相当于滑动窗口整体左移了一位,此时对应的2的指数应该是
综上, 算法square_multiply()得证,也就是可以通过此算法计算
-
已知,证明squre_multiply_mod适用于
#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))回顾一下乘方的同余性质:
如果, 那么是自然数。
比如计算,所以
也就是对一个指数a的乘方进行取模等价于:令初始值,每次乘完底数立刻进行取模,对应到上述Square and Multiply的代码,只需要在每次乘法后立刻取模,最终就能得到:
#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算法就是对大指数如取模的最优雅且快速的实现
本文完结,码字不易,有人看点赞~