首发于CivilNet

从简单数字到现代密码学

背景

计算机程序中大量使用的现代密码学主要有以下4类:

  1. 对称加密。也就是使用一个private的key来将发送的信息加密,在接收端使用同样的key将信息解密。比如DES、AES。
  2. 非对称加密。也就是信息的发送端和接收端各维护自己的公钥/私钥对。发送信息的时候使用接收方的公钥加密,接收方使用自己的私钥解密。如RSA、ECC。
  3. 密钥交换。信息发送方和接收方使用特定的技术(主要就是离散数学)生成双方共享的key,而这个key从未对外暴露过。如DH。
  4. 哈希。信息被hash为固定长度的数字,并且无法逆转。如SHA、MD5。

Gemfield在本文只介绍非对称加密和密钥交换,也就是2和3。因为这两个领域要大量使用到质数的一些特性,所以Gemfield要在下个章节先列出一些定理或者等式。

本文用到的定理或者等式

1,质数定理

小于数字x的质数的个数约等于 x/ln(x)

神奇吧,质数的个数居然和增长极限e挂上关系了!然后,Gemfield还要列出一些离散数学方面的等式,这些等式和取模运算有关,是Gemfield本文所讲述的加密算法的核心之一,非常的重要!

2,Gemfield不管这个等式学名叫啥反正是对的并且有人证明过了

Gemfield要给出的又一个等式的名字叫做“Gemfield不管这个等式学名叫啥反正是对的并且有人证明过了”。这个等式就是:

假设有 a mod b ≡ n ,则 a^k mod b ≡ n^k mod b

用python表示就是:

假设有 a % b == n , (a**k) % b == (n**k) % b

虽然这里没有给出证明过程,但是这个等式还是很符合直觉的。你再仔细感受下,想象下绳子在钟表上缠绕产生的偏移......不放心可以测试下:

gemfield@ThinkPad-X1C:/tmp$ python
Python 2.7.14 (default, Sep 23 2017, 22:06:14) 
[GCC 7.2.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> a=13;b=11;c=2
>>> a%b == c
True
>>> for k in range(1,10000):
...   assert (a**k)%b == (c**k)%b, 'not gemfield expected'
... 
>>> 

3,进一步推导出'Gemfield本文会用的一个等式'

所以进一步展开就有

1, 假设有 a mod b ≡ n 
2, 则 (a mod b)^k = n^k
3, 则 (a mod b)^k mod b ≡ n^k mod b
//根据'Gemfield不管这个等式学名叫啥反正是对的并且有人证明过了'等式中的
//假设有 a mod b ≡ n ,则 a^k mod b ≡ n^k mod b
4, 则 (a mod b)^k mod b ≡ n^k mod b ≡ a^k mod b
//令a=a^k1并且k=k2
5, 则 (a^k1 mod b)^k2 mod b ≡ (a^k1)^k2 mod b
//令a=a^k2并且k=k1
6, 则 (a^k2 mod b)^k1 mod b ≡ (a^k2)^k1 mod b
//联合5和6
7, 则 (a^k1 mod b)^k2 mod b ≡ a^k1k2 mod b ≡(a^k2 mod b)^k1 mod b

所以一条很重要的结论就导出来了:

(a^k1 mod b)^k2 mod b ≡ a^k1k2 mod b ≡(a^k2 mod b)^k1 mod b

这就是'Gemfield本文会用的一个等式',因为太重要了于是Gemfield再用python表示一下:

((a**k1 % b) ** k2) % b == (a ** k1*k2) % b == ((a ** k2 % b)**k1) % b

4,欧拉函数/φ函数

φ函数即欧拉函数。它定义的是数的可分性。

在数论中,对正整数n,欧拉函数是小于或等于n的正整数中与n互质的数的数目(也就是有多少数和n不具有任何公因数)。

比如φ(8)=4,因为在1,2,3,4,5,6,7,8中有1,3,5,7和8互质,所以一共有4个与8互质的数,于是函数的值是4。

φ函数有个很重要的性质叫做乘法性质:如果A与B互质,则φ(A*B) = φ(A) * φ(B)

如果A与B互质,则 φ(A*B) = φ(A) * φ(B)

φ函数的一个极其重要的特性就是,计算φ函数的值相当困难,尤其是当n的值很大的时候,计算φ函数就是几乎不可能的事情了。只有一种情况例外,那就是当n是质数的时候。因为当n为质数的时候,n与2~n之间的整数均互质,也就是互质的数的数量是n-1。于是我们就有了一个很重要的等式:

当P为质数时,φ(P) = P - 1

比如质数7,我们可以不假思索的说出φ(7)=6。再比如质数21377,我们也可以不假思索的说出φ(21377)=21376。


我们都知道大质因数分解是个难题,比方说gemfield随机产生了一个长度约为1024位的大质数P1,再产生一个长度约为1024位的大质数P2,然后P1*P2得到合数N,N的长度要超过2048位。如果一个人想要提前把1024位以内的质数记录在某个硬盘里,用的时候遍历然后试错可以吗?根据质数定理,1024位以内的质数的数量约为:2的1017次方。这个数字远超宇宙的原子数量(事实上要大的多得多),所以暴力穷解是不可能的(注意,即使把质数的位数严格限定在1024位,也就是没有1位到1023位之间的质数,这个数量也是和位数相关成指数级增张的,不影响结论)。

Gemfield会将P1和P2隐藏起来,将N公布出去。任一数字都可以被唯一分解为一组质因数的乘积。关键是,即使N现在被Gemfield公布出去,别人也无从得知P1和P2。根据上述两个等式,我们会得到关于质数P1、P2以及合数N有下面的等式:

φ(N) = φ(P1) * φ(P2) = (P1-1) * (P2-1)

这说明了什么呢?前面说过,当N是一个很大的数的时候,求解φ(N)是一个不可能完成的事情;但是,如果我们知道N的质因数分解,那么求解φ(N)就变成秒杀了。比如77(这只是举例,你完全可以使用一个很大的数)可以分解为7和11,那么φ(77) = φ(7) * φ(11) = (7-1) * (11-1) = 60。


5,欧拉定理(Euler Theorem

在数论中,欧拉定理,(也称费马-欧拉定理)是一个关于同余的性质。欧拉定理表明,若m,n为正整数,且m,n互质,则:

m^φ(n) ≡ 1 (mod n)

用python表示则为:

(m ** φ(n)) % n == 1

根据“Gemfield不管这个等式学名叫啥反正是对的并且有人证明过了”,我们可以将m^φ(n) ≡ 1 (mod n) 进一步展开为:

//等式两边均加个指数k
m^(φ(n)*k) ≡ 1^k (mod n)
//为什么加个指数k呢?因为最终我们要靠迭代k来(通过整除)获取最终的d
//于是
m^(φ(n)*k) ≡ 1 (mod n)
//上述等式也可以转换为传统的等式
m^(φ(n)*k) = h*n + 1 (其中,h为正整数)
//等式两边均乘以m
m * m^(φ(n)*k) = m*h*n + m (其中,h为正整数)
//换成下面的写法
m*m^(φ(n)*k) ≡ m (mod n)
//于是
m^(φ(n)*k + 1) ≡ m (mod n)

于是,又一个重要等式有了:

当m与n互质,则m^(φ(n)*k + 1) ≡ m (mod n)

用python表示为:

#m与n互质,则
m ** (φ(n)*k + 1) % n == m % n

6,费马小定理(Fermat's little theorem)

费马小定理是说,如果p是质数的话, 那么对于任意整数a, 有

a^p ≡ a (mod p)

用python来表示就是:

#p是质数的话
(a ** p) % p == a % p

比如,当a = 2并且 p = 7, 那么 2^7 = 128,那么128 %7就是2,正是a自己。

特别的,当a不能被p整除(因为p是质数,所以其实就是a和p互质),等式就可以简化为:

a^(p-1) ≡ 1 ( mod p )

用python来表示就是:

(a ** (p-1)) % p == 1

比如, 当a = 2 并且 p = 7, 那么 (2**6) % 7 = 1。

这不就是欧拉定理的特例嘛。


7,模反元素(gemfield只是写在这儿了,好像后文没有用到这个)

如果两个正整数a和n互质,那么一定可以找到整数b,使得 ab-1 被n整除,或者说ab被n除的余数是1。这时,b就叫做a的"模反元素"。

比如,3和11互质,那么3的模反元素就是4,因为 (3 × 4)-1 可以被11整除。显然,模反元素不止一个, 4加减11的整数倍都是3的模反元素 {...,-18,-7,4,15,26,...},即如果b是a的模反元素,则 b+kn 都是a的模反元素。

欧拉定理a^φ(n) ≡ 1 (mod n)可以用来证明模反元素必然存在:

a^φ(n) = k*n + 1

a^φ(n) - 1 = k*n

那么对于a和n以及模反元素b就得到:

a*b = a^φ(n)

b = a^(φ(n) -1)

所以就有:

a的 φ(n)-1 次方,就是a关于n的模反元素。


非对称加密

直到20世纪70年代,密码学仍然基于对称的密钥。意味着,发送者用特定的密钥将信息加密,而接收者用相同的密钥解密。加密是将一些信息通过特定的密钥映射到密文中。为了解密密文,你需要使用同一个密钥进行反向映射。 这就是上一节的1。

1970年以来诞生的非对称加密技术依赖一种称作one-way(单向)的数字计算,或者也可以叫作trapdoor的one-way计算方法。该方法朝一个方向计算简单直接,反向就会很复杂,除非你有trapdoor的特殊信息。简单来说就是,从一组数算出另外一个数非常容易,但是反过来却极其困难。比方说手铐(或者扎带),拷上容易,但反方向解开很难。

最先找到这种one-way计算方法的是一名叫做Clifford Cocks的英国数学家和译解密码者。这个方法是modular exponentiation。他是怎么做的呢?在回答这个问题前,我们先玩个数字游戏:假设有3个数字e、N、d,其值分别为17、3233、2753。其中e和N是公开的,是那个女孩的公钥,任何人都可以看到和使用,而d是那位女孩的秘密数字,其他人包括gemfield都不知道。但这并不会妨碍gemfield传递信息,gemfield想要传递一个521的信息给这位女孩,于是gemfield使用这个女孩的公钥e、N进行加密,521^17 MOD 3233计算出来得到加密数字572然后发送给女孩;女孩收到这个信息后使用自己的私钥d进行解密。572^2753 MOD 3233得到数字521,没错,这正是Gemfield的心意:我爱你。

问题是e、N、d的值17、3233、2753又是怎么被挑选出来的呢?以及为什么别人不知道d的值就无法监听gemfield对这位女孩的心意呢?我们先来讨论第一个问题-这些数字是如何挑选出来的。

挑选这三个数字e、N、d的步骤如下所示:

  1. 先选取两个大的质数,29和97(这是很小的质数,只是为了方便演示。实际中这个数字一般长达2048 bits); 备注:p和q之间的距离也要保证,密码学家D. Coppersmith于1996年指出,如果合数N的质因子p和质因子q离得很近,则存在快速算法将N分解。现在一般认为,如果合数N的位数为n,那么|p-q|要同时满足 log|p-q| > n/2 - 100; log|p-q| > n/3。
  2. N为上述2个质数相乘,值为29*97 = 2813;
  3. 计算φ(2813),其值为28*96 = 2688;
  4. 选择e,e一般为小的质数,比如3,5,17等,一个要求是e必须和φ(2813)互质(如果不互质的话后面第5步就得不到解,这非常重要,后面那个步骤里面的式子一目了然),也就是和2688互质,因此3就被排除了(根据互质的定义,若其中一个是质数,另一个不为它的倍数,则这两个数为互质数)。gemfield选择e为5;备注:密码学家D. Coppersmith于1990年指出:用于RSA的指数e不能太小,否则存在快速算法计算得到私钥d。这个定理要用到格密码学的著名算法LLL。不过,由于加密算法要计算m^e,如果e太大的话加密过程可能会比较慢。现在一般认为让e=65537是比较合适的。
  5. 计算私钥d,d = (k * 2688 + 1) / 5,让k从1开始自增直至d为整数,如下面的python程序所示。gemfield选取d为1613,当然你也可以选择其为4301、6989等。备注:密码学家M. Wiener于1990年指出:用于RSA的私钥d不能太小,否则存在快速算法得到私钥d。现在一般认为,如果合数N的位数为n,那么d的值要满足d>2^(n/2)。
>>> for k in range(1,100):
...   d =  (k * 2688 + 1) / 5.0
...   if d.is_integer():
...     print("d is {} when k is {}".format(d,k))
... 
d is 1613.0 when k is 3
d is 4301.0 when k is 8
d is 6989.0 when k is 13
d is 9677.0 when k is 18
d is 12365.0 when k is 23
d is 15053.0 when k is 28
d is 17741.0 when k is 33
d is 20429.0 when k is 38
d is 23117.0 when k is 43
d is 25805.0 when k is 48
d is 28493.0 when k is 53
d is 31181.0 when k is 58
d is 33869.0 when k is 63
d is 36557.0 when k is 68
d is 39245.0 when k is 73
d is 41933.0 when k is 78
d is 44621.0 when k is 83
d is 47309.0 when k is 88
d is 49997.0 when k is 93
d is 52685.0 when k is 98
>>>

至此,一组新的e、N、d就产生了,其值分别为5、2813、1613。其中,e和N为公钥,d为私钥。

gemfield又想给另外一个女孩发送521了,这位第二个幸运的女孩的私钥d正是1613(除了此女孩无人知道),于是gemfield使用此女孩的公钥进行加密,521^5 MOD 2813 = 1420,女孩收到1420后用自己的私钥进行解密,1420^1613 MOD 2813 = 521。于是,这第二位幸运的女孩也收到了gemfield的我爱你信息。

上面演示了如何获取非对称加密中的公钥和私钥,那这么做为什么就可以生效呢?

比方说Gemfield想要发送的机密信息是m(来自单词message):

  1. Gemfield先将m自乘e次,也就是m^e(其中e来自单词encryption)。用python表示就是m**e。注意:m是私有的要传达的信息,而e是公开的。
  2. 然后Gemfield将m^e MOD N,其中N来自选取的2个大质数相乘得到的合数。用python表示就是(m ** e) %N。这样就会得到一个结果c,也就是m^e MOD N ≡ c。
  3. 但是根据c、e、N,我们并不能容易的得到m。这就是根本,这就是上文我们所说的one-way 计算方法。通过m、e、N很容易算出c,但是根据c、e、N却很难算出m。
  4. 好了,上面的e和N可以看作是公钥(公开的部分),如果有个私钥d(来此单词decryption),作用在c上,使得c^d MOD N≡ m,这样就可以恢复原始信息m了。注意,这一步只是描述了Gemfield想要达到的愿望。
  5. 在步骤2中,我们有m^e MOD N ≡ c。在步骤4中,Gemfield希望有c^d MOD N ≡ m。这两个等式合并起来就是,Gemfield希望有 (m^e MOD N)^d MOD N m
  6. 根据前述'Gemfield本文会用的一个等式': (a^k1 mod b)^k2 mod b ≡ a^k1k2 mod b ≡(a^k2 mod b)^k1 mod b,5中的希冀就可以表示为m^ed mod N m,也就是m^ed m (mod N 。也就是Gemfield希望有个数字e、d、N,可以得到等式m^ed mod N = m。如果说N在上文中已经提到了是个随机数字,那么e和d应该怎么产生呢?尤其是,上文中说过了,e是公开的,所以换句话来说就是,怎么产生e和d并且让别人很难根据c、e、N算出d呢?
  7. 根据前述的欧拉公式:当m与n互质,则m^(φ(n)*k + 1) ≡ m (mod n)。而6中的希冀为m^ed m (mod N,换句话说,只要让ed = φ(n)*k + 1,我们就能实现5中的希望。在数字e选取后,我们就可以根据(φ(n)*k + 1)/e得到d(从1自增k直到该式子可以整除,就像前述python代码演示的那样);那么当m与n不是互质的话呢?(虽然这种概率已经变得很小了,因为n是由两个质数乘积得到的,能和n不互质的数一共也就p+q-1,但不管怎样,还是要证明这种情况下m^ed ≡ m (mod n) )。
  8. m与n不是互质关系的情况下,我们就要动用费马小定理来证明了。证明过程参考RSA (cryptosystem)。gemfield这里就不证明,太累了。


总而言之,非对称加密(比如RSA)能加密再解密的原理就在于:

当 ed = φ(N)*k + 1 的时候,m^ed = m (mod N)

而这么做不会被别人破解的原理就在于:

N是两个大质数的乘积,d是私有的并且通过d = (φ(N)k + 1) / e 计算出来的,别人知道N却很难知道φ(N);

而自己知道d的原理在于:

自己知道N可以分解为p*q,于是φ(N) = (p-1) * (q-1)

这就是为什么我们说,RSA这种非对称加密很难破解是仰仗于大质数分解的难题!

基于本节的知识,你现在可以阅读专栏文章ssh-keygen生成的id_rsa文件的格式 来了解ssh-keygen命令生成的公钥、私钥文件的格式了。

密钥交换

(待续)

编辑于 2018-02-23

文章被以下专栏收录