Skip to content

RSA算法是现代安全技术的基石,通过自己学习,利用JAVA代码实现其功能,项目中只有RSA的核心代码

Notifications You must be signed in to change notification settings

emailtohl/my_rsa_impl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

我的RSA算法理解以及Java实现

我的毕业设计是通过C语言来实现AES算法,从此对密码学具有较大的兴趣,时隔多年后,当自己再重拾编码工作后,第一个锻炼的项目就是完成当年未能实现RSA算法的夙愿。 RSA算法是现代密码技术的基石,有着严密的数学推理,至少到目前为止还没有证明有效的破解方式,我在业余时间我查阅了很多资料,完成了一份自己的Java实现。

本文档既对该算法的来龙去脉进行推导,也给出具体的代码。但还是需要说明的是:

  1. 对于像RSA这种最基础的安全组件,在JDK中已经有了实现,不过这并不妨碍自己的学习;

  2. 既然是对算法进行学习,那么就尽量抛开JDK中现有实现,实现中除了生成大素数太艰深外,其他的如大数幂模运算,欧几里得获取逆元,都可以自己动手实现。

  3. 本文档着重描述算法原理,实现在KeyGenerator,而应用部分的示例可参考pad项目的RSACipher。

  4. 文档编排方式与意图导向编程风格一样,以展示RSA的算法原理为主干脉络,过程中的一些理论基础以及细枝末节仅在后续章节补充。

第一章 RSA算法原理

想象一下时钟,以12为模,超出了12点后,数字就会重新调整到12点以内,如12调整为0,13调整为1,17调整为5,10000调整为4,也就是说不管数字有多大,模12后结果也只能在12范围内。

对一个数字m,进行幂运算后,通常会很大,但再进行模运算,其结果会回到模范围内,如 me mod n = c,c < n,如果再对c做幂模运算,结果是否能还原为之前的m呢?

1.1 一个简单的演算示例

以下用n表示模,明文m表示,m的幂用e表示,假设n = 323,m = 10,e = 7,将明文m进行幂模运算:

me mod n = 107 mod 323 = 243

这就是RSA算法的加密了,它将明文10通过幂模运算映射成了密文243 用c表示,之前的ne被称之为公钥

从模运算的特性来看,不能简单通过逆向运算得到原始的明文。但是能找到一个d = 247,再结合n进行幂模运算:

cd mod n = 243247 mod 323 = 10

这就是RSA算法的解密了,dn被称之为私钥,总结成数学公式:

加密:c = me mod n

解密:m = cd mod n

可以看出公钥私钥各不相同,这就是非对称加密算法最大的特性,可解决通信过程中泄露密钥的问题。

另外,解密时指数特别大,JDK已提供BigInteger#divideAndRemainder支持,我们也可以自行实现5.2 大数的幂模运算

1.2 解密公式的证明

既然通过模运算可以还原明文m,那么是否一定能找到d使得解密公式成立?需要满足三个条件:

  1. n是两个素数pq的乘积,用公式表示: n = pq
  2. m在n的范围内,即m < n
  3. d满足 ed = 1 + u φ(n),其中φ(n)被称之为欧拉函数,表示在[1, n)范围内与n互素的数字的个数

证明如下:

cd mod n = (me)d mod n = med mod n

根据条件3:

med mod n = m1 + u φ(n) mod n = (m(mφ(n))u) mod n

根据2.1 同余式乘法,等式演变为:

(m(mφ(n))u) mod n = ((m mod n) * (mφ(n) mod n)u) mod n

根据条件2,m mod n = m,下面分m与n是否互素分开讨论。

1.2.1 当m与n互素

根据3.2 欧拉定理 mφ(n) mod n = 1,故等式演变为:

((m mod n) * (mφ(n) mod n)u) mod n = (m * 1u) mod n = m

如此就证明了 m = cd mod n 正确性,能将密文c解密成明文m

1.2.2 当m和n不互素

这时候m和n存在公因子,根据条件1n是两个不能再次分解的素数因子:p和q的乘积,所以要么p是m的因子,要么q是m的因子,不妨设p是m的因子,下面将先用反证法证明m与q互素,再应用3.2 欧拉定理将 mφ(q) ≡ 1 (mod q) 推导出 m = cd mod n

1.2.3.1 若p是m的因子则m一定与q互素

p是m的因子,可写成:

m = pk

假设m与q不互素,因q是素数,故m只能是q的倍数,且只能从k中进行因式分解,等式可改为:

m = pk = phq = hpq = hn

这样就得到m > n的结论,与条件1 m < n是矛盾的,所以m一定与q互素

1.2.3.2 推导出cd mod n = m

m一定与q互素,则m和q就存在3.2 欧拉定理的同余式:

mφ(q) ≡ 1 (mod q)

根据2.1 同余式乘法,两边同时进行uφ(p)次方仍然同余相等:

muφ(p)φ(q) ≡ 1uφ(p)φ(q) (mod q)

由于p和q是素数,根据3.3 欧拉等式, φ(p)φ(q) = φ(pq) = φ(n),故同余式改为:

muφ(n) ≡ 1 (mod q)

现将同余等式改成方程式:

muφ(n) = 1 + vq

方程式两边同时乘上m:

muφ(n) + 1 = m + vmq

前面已假设m = pk,所以vmq = vkpq,因n = pq,再设vk = w,方程式改为:

muφ(n) + 1 = m + wn

改成模等式:

muφ(n) + 1 mod n = m

根据条件3 uφ(n) + 1 = ed

med mod n = m

如此就推导出:

cd mod n = m

至此,不论m是否与n互素,均能证明 cd mod n = m 成立。

1.3 三个条件的补充说明

1.2 解密公式的证明证明了RSA解密公式的正确性,但是需要满足三个条件

  • 对于条件1,可在生成密钥时,保证p和q是素数
  • 对于条件2,要求明文数字m在n的范围内,即 m < n
  • 对于条件3,则是获取私钥d的关键,可通过4.3 扩展欧几里得算法推算出d,关键在于φ(n),根据3.3 欧拉等式 φ(n) = φ(p)φ(q) = (p-1)(q-1),只要p,q不泄露,要从n中分解出p,q来却很困难,这就保证了私钥的安全。

实际上,还有一个条件没有提到,那就是e必须是素数,这关系到d是否存在,有关论述在4.2 计算d

以上即RSA的算法描述,其中用到了很多数论原理,如欧拉函数、欧拉定理、同余式乘法等,具体相关原理和证明见后续章节。

第二章 模运算基础

RSA算法建立在数论基础上,下面从基础开始,逐步证明同余式乘法、欧拉定理。

2.1 同余式乘法

若a ≡ b mod m, c ≡ d mod m ,则 ac ≡ bd mod m ,这个定理应用在RSA证明时,用于1.2.3.2同余式两边共同u(p-1)次方的场景中,证明如下:

a = km + b , c = hm + d

ac = (km + b)(hm + d) = khm2 + kmd + bhm + bd

ac = bd + (khm + kd + bh)m,转换成同余式,即:

ac ≡ bd mod m

得证。

2.2 模运算的分配率

模运算就像普通运算一样,它是可交换、可结合、可分配的,在计算中经常用到分配率:

(a + b) mod m = ((a mod m) + (b mod m)) mod m

ab mod m = (a mod m)(b mod m) mod m

在之前RSA算法证明、3.2 欧拉定理的证明以及5.2 大数的幂模运算中都会用到本规律,它们的证明方法是一样的,以乘法为例,证明如下:

(a mod m)(b mod m) mod m = (a - k1m)(b - k2m) - k3m

= ab - (ak2 + k1b - k1k2m - k3)m

= ab mod m

得证。

2.3 互素的传递性

  1. 如果整数a和b 都与c互素,那么ab也与c互素,因为他们都没有公约数。

  2. 如果整数p和q互素,那么(p mod q)后仍然与q互素。

第2点可用反证法:

设 p mod q = r,即:p = r + kq,假设r与q不互素,那么r和q存在公约数d,上面等式可以改写为:p = dr' + kdq' = d(r' + kq')

如此一来d也能整除p,这与p,q互素矛盾。

2.4 消去律

该定理将会用于3.2 欧拉定理的证明中。

若 ac ≡ bc mod m,且c和m互素,则

a ≡ b mod m

证明如下:

由 ac ≡ bc mod m 可知:ac = km + bc , 即:

c(a - b) = km

由于c和m互素,因此c | k。设 k = hc ,则

c(a - b) = hcm , 即

a - b = hm , 即

a ≡ b mod m

得证。

第三章 欧拉定理

欧拉定理是RSA算法的核心,在有了模运算基础后,就可以证明此定理。

3.1 欧拉函数

在数论中,将[1, n)集合中与n互素的个数记为φ(n),被称之为欧拉函数

例如φ(8) = 4,这是因为1,3,5,7这4个数和8互素。

若参数是素数例如7,那么φ(7) = 7 - 1 = 6,这是因为[1,7)集合中都与其互素。

3.2 欧拉定理

如果两个正整数a和n互素,φ(n)是n的欧拉函数,存在下面的等式: aφ(n) ≡ 1 (mod n)

即:aφ(n) = 1 + kn,或:aφ(n) mod n = 1

比如,3和7互素,而7的欧拉函数φ(7)等于6,则 36 mod 7 = 1

证明分两步:

3.2.1证明以下两个集合相等

Zn = {x1, x2, ... , xφ(n)}

S = {ax1 mod n, ax2 mod n, ... , axφ(n) mod n}

从[1,n)集合中,找到与n互素的正整数,组成Zn集合,根据欧拉函数的定义,Zn集合中的元素的个数有φ(n)个。

将Zn集合中的每个元素乘a,再模n,得到S集合,其中a也是与n互素的正整数,S集合也有φ(n)个元素。

3.2.1.1 证明S与Zn集合元素个数相等

在S集合中,因为a与n互素,并且x1, x2, ... , xφ(n)都与n互素,所以根据2.3 互素的传递性ax1 mod n,ax2 mod n,……,axφ(n) mod n,它们都与n互素,所以都属于Zn集合,且S中的元素个数与Zn元素的个数相等,下面证明S集合中的每个元素都互不相等,就能说明Zn = S

3.2.1.2 用反证法证明S集合中的每个元素各不相同

假设axi mod n = axj mod n ,因a,n互素,根据2.4 消去律得知:xi mod n = xj mod n,又,xi 和 xj都属于集合Zn,它们都小于n,所以xi = xj,这个结论与事实矛盾,如此就证明了Zn = S 。

3.2.2 证明1 ≡ aφ(n) (mod n)

因Zn = S,故分别将Zn集合和S集合中的每个元素相乘,根据2.1 同余式乘法它们也应该同余相等: x1 x2 ... xφ(n) ≡ aφ(n) x1 x2 ... xφ(n) mod n

已知x1,x2,...,xφ(n)与n互素,那么x1 x2 ... xφ(n)也与n互素,上式通过2.4 消去律得到:

1 ≡ aφ(n) (mod n)

得证。

3.3 欧拉等式

两个不同的素数p,q,它们的乘积n = pq,则φ(n) = φ(p)φ(q),这被称之为欧拉等式,在1.2.3.2中应用到,证明如下:

在小于n的域中,与n互素的集合中的个数有:

{1,2,3,……,n-1} - {p,2p,……,(q-1)p} – {q,2q,……,(p-1)q}

所以φ(n) = (n-1)-(q-1)–(p-1) = (p-1)(q-1) = φ(p)φ(q)

去掉中间步骤,得到重要结论(本该用中国剩余定理来证明的)

第四章 RSA的密钥

根据第一章的描述,RSA算法需要6个参数,p, q, n, φ(n), e, d ,其中:

  • p,q是随机生成的大素数;
  • n = pq ,是加密和解密计算时的模;
  • φ(n)是n的欧拉函数值;
  • e是加密时中使用的公钥,它与φ(n)互素;
  • d是解密时需要的私钥,它是通过e和φ(n)计算出来的,它们的关系是:ed ≡ 1 (mod φ(n)),也可以称d是e关于模φ(n)的逆元(与乘法倒数类似)。即:e*d mod φ(n) = 1,这个等式还可以写成:ed = 1 + u φ(n)

4.1 获取RSA算法所需参数

  1. 对于p,q的选择,比较困难,小素数(比如1亿)的测试,还可以用蛮力法进行测试:

首先由java生成随机数p,过滤掉偶数,然后依次用2到sqrt(p)之间的数去整除p,如果没有一个数能被整除,说明p是素数。

但是对于成百上千位的大素数来说,这种方式的性能是不可接受的,对于大素数的筛选方法有Miller检验,不过它理论我还没弄懂,所以并没有实现该方法,这里使用JDK,BigInteger自带的:

BigInteger probablePrime(int bitLength, Random rnd);

需要注意的是,该方法返回的可能是素数的,还需要在RSA中进行测试,看看是否能解密还原。

  1. 找到p,q后,就得到n = pq,由于p,q互素,φ(n) = φ(pq) = φ(p)φ(q) = (p - 1)(q - 1),如此也计算出了φ(n)。

  2. 对于公钥e,来说选择就比较容易,只要与φ(n)互素即可,一般e选择素数65537,e要求是素数的原因一直没有提到,在4.2 计算d中有说明。

  3. 现在最重要的是找到私钥d,目前只知道d与e,φ(n)之间存在关系:ed = 1 + u φ(n)

4.2 计算d

将方程 ed = 1 + u φ(n) 改为:

-φ(n)u + ed = 1

这是一个二元一次方程,e,φ(n)作为系数是已知量,而u,d未知变量,只要e互素φ(n),那么它们的最大公约数就是1,根据5.1 裴蜀定理,u,d存在整数解,这就是e要求是素数的原因。

令a = - φ(n),e = b,u = x,d = y,可将方程式改为传统二元一次方程模样:

ax + by = 1

再参考5.3.1 算法描述即可推导出d,此处展示代码的实现。

class Euclid {
  private BigInteger fn, e, x, y, d;// x,y成员变量,用于保存generateD()计算时的中间值
  Euclid(BigInteger fn, BigInteger e) {// fn和e即已知量,fn即算法中的a,e即算法中的b
    this.fn = fn;
    this.e = e;
    generateD();
  }

  private void generateD() {
    BigInteger gcd = extendEuclid(fn, e);// 在调用扩展欧几里得算法时,计算出x,y值
    LOG.debug("gcd = " + gcd);// 先打印查看最大公约数是否为1,保证无异常
    LOG.debug("x = " + x);
    LOG.debug("y = " + y);
    /*
      * 调用了扩展欧几里得方法后,x,y满足: (φ(n) * x) + (e * y) = 1
      * 这里不能完全保证y的正负性,如果y是负数,则需转换为同余正数,例如:-3 ≡ 2 (mod 5)
      */
    while (y.compareTo(ZERO) < 0)
      y = y.add(fn);

    d = y;
    LOG.debug("d = " + d);
    LOG.debug("e * d (mod φ(n)) = " + (e.multiply(d).remainder(fn)));
  }

  /**
   * 下面的方法使用了扩展欧几里得算法获取逆元,用于RSA中获取密钥d所用
   * 注意:通过扩展欧几里得算法得到的x,y可能为负数,最后要把它加上模的倍数,直到成正数
   *
   * @param a 欧几里得算法中的迭代参数a
   * @param b 欧几里得算法中的迭代参数b
   * @return 最大公约数
   */
  private BigInteger extendEuclid(BigInteger a, BigInteger b) {
    if (a.compareTo(b) < 0) {
      return extendEuclid(b, a);
    }
    if (ZERO.equals(b)) {
      x = BigInteger.ONE;
      y = BigInteger.ZERO;
      return a;
    }
    BigInteger gcd = extendEuclid(b, a.remainder(b));
    BigInteger temp = x;
    x = y;
    y = temp.subtract(a.divide(b).multiply(y));
    return gcd;
  }

  public BigInteger getD() {
    return d;
  }
}

第五章 裴蜀定理和扩展欧几里得算法

5.1 裴蜀定理

gcd(a,b)是a,b的最大公约数,存在整数x和y使得ax + by = gcd(a,b)

为什么x和y一定会存在整数解?下面将将分三步来证明裴蜀定理:

5.1.1 第一步

设ax + by是一个正整数的集合,该集合存在最小的正整数,设为d,先证明ax + by集合中任意整数都是d的倍数

可以用反证法证明d能整除ax + by集合中的所有值:

当x = x0,y = y0时,有等式:ax0 + by0 = d

当x = m,y = n时,有等式:am + bn = e

当然e ≥ d,假设d不能整除e,对e做带余除法,必定存在 p,r 使 e = pd + r,显然,r < d 但是通过下面的推导却会得出r > d矛盾的结果:

r = e - pd = (am + bn) - p(ax0 + by0) = (m - px0)a + (n - py0)b

观察上面等式的首尾,说明存在整数 m - px0, n-py0 使 ax + by = r < d,这与d的最小性矛盾。

所以d整除e,即d能整除ax + by集合中的任意值。

5.1.2 第二步

证明d是a和b的公约数

令x = 1,y = 0,则ax + by = a,所以d能整除a

同理d也能整除b,这就说明d是a,b的公约数。

5.1.3 第三步

实际上,d就是a和b的最大公约数,证明如下:

对于a和b的任意公约数d’,存在a = kd’,b = ld’,有等式成立:

d = kd’x + ld’y = d’(kx + ly)

所以a,b的任意公约数d’都能整除d,即d就是a和b的最大公约数(最大公约数等于各公约数的积)

特例:当a,b互素时,d = 1,这就说明了对于互素的a和b,一定存在x,y满足 ax + by = 1

5.2 欧几里德算法

欧几里德算法又称辗转相除法,用于计算两个整数a,b的最大公约数。算法基于下面这个等式:

gcd(a,b) = gcd(b,a mod b)

证明:

设gcd(a,b) = g,那么可写成:a = mg,b = ng,这里的m和n一定是互素的,否则g就不是a和b的最大公约数。

a和b存在关系式:r = a - kb = mg - kng = (m - kn)g

现在比较这两个式子:

r = (m - kn)g

b = ng

可以看出,g是r和b的公约数,另外,因m与n互素,所以(m - kn)与n互素,如此就说明g也是r和b的最大公约数,所以证明了gcd(a,b) = gcd(b,r) = gcd(b,a mod b)

5.3 扩展欧几里得算法

RSA获取私钥d的过程,实际上是解二元一次方程,该方程在5.1 裴蜀定理中,是最大公约数为1时的特殊形式,所以存在x, y的整数解。要解开此方程,还需要借助5.2 欧几里德算法中最大公约数串联的特性,结合起来就是扩展欧几里得算法

5.3.1 算法描述

设x0,y0是二元一次方程:ax + by = gcd(a, b) 的一组解,算法最终就是要求得x0,y0

根据5.1 裴蜀定理,存在x1,y1是二元一次方程:bx + (a mod b)y = gcd(b, a mod b) 的一组解,又根据5.2 欧几里德算法 gcd(a, b) = gcd(b, a mod b),所以:

ax0 + by0 = bx1 + (a mod b)y1

这里的a mod b在程序中可以写成 a - (a/b)*b (a/b指的是a除以b取整的意思,例如System.out.println(20/7),显示的结果是2) 所以等式可以改写成:

ax0 + by0 = bx1 + (a-(a/b)b)y1 = ay1 + bx1 - (a/b)by1 = a(y1) + b(x1-(a/b)y1)

对比等式左右两边发现迭代关系如下:

x0 = y1

y0 = x1-(a/b)*y1

在欧几里得算法中,递归地辗转相除,直到 an mod bn = 0,此时gcd(an, bn) = an

根据5.1 裴蜀定理,存在xn,yn是二元一次方程:anxn + bnyn = gcd(an, bn) 的一组解,所以有:

anxn + bnyn = an

该等式成立的必要条件是 xn = 1, yn = 0,这就得到了递归最后一层的结果,此时递归将逐层返回,并迭代计算出每层的x,y,最后求得x0,y0

第五章 代码实现

长篇理论终于翻篇了,下面补充说明一下Java代码中一些实现。

5.1 获取大素数p,q

大素数校验理论较为艰深,我的代码中并未实现,直接调用JDK中的:

BigInteger probablePrime(int bitLength, Random rnd);

5.2 大数的幂模运算

加密解密时有非常大量的幂模运算,JDK中也提供相应的方法:

BigInteger modPow(BigInteger exponent, BigInteger m);

不过这里,我们可以自己实现,幂模运算也遵循乘法分配率,所以对于大数的幂模运算,可以先将底数做模运算后,再做指数运算,这样可以将数值运算保持在较小的数域范围内。

蒙哥马利算法计算大数的幂模运算的思路是不断将指数进行除2分解,直到指数分解到1为止,当然每次分解指数时,同时也在计算底数的平方。

以m9 mod n可以这样分解为例,可以这样分解:

m9 mod n = ((m2 mod n)9/2 m ) mod n

每次运算,指数减半,同时计算底数的平方,然后再将中间值做模运算,如此底数一直保持在小于模数的范围内,不仅计算不会溢出,同时大指数也会迅速地减小。

从上面式子也看到了,如果指数是奇数,那么还需要乘上一个剩余的底数m,指数不断地对半分,直到为1,这时候就可以直接返回底数了,采用递归方法来实现。

代码如下:

static BigInteger powModByMontgomery(BigInteger bottomNumber, BigInteger exponent, BigInteger module) {
  if (ONE.equals(exponent)) {// 如果指数为1,那么直接返回底数
    return bottomNumber.remainder(module);
  }
  /*下面判断exponent的奇偶性,只要判断它最后一个bit位即可,1是奇数,0是偶数
    getLowestSetBit()方法可以返回最右端的一个1的索引,例如84的二进制是01010100,最右边1的索引就是2*/
  if (exponent.getLowestSetBit() == 0) {
    /*如果指数是奇数,那么底数做平方,指数减半后,还应该乘以剩下的一个底数
    下面对指数的除2处理是通过移位运算完成的,指数除2取整,相当于做右移一位操作,“exponent >>= 1”操作相当于“exponent /= 2”,但是效率会更高*/
    return bottomNumber.multiply(powModByMontgomery(squareAndMod(bottomNumber, module), exponent.shiftRight(1), module)).remainder(module);
  }
  return powModByMontgomery(squareAndMod(bottomNumber, module), exponent.shiftRight(1), module);
}

private static BigInteger squareAndMod(BigInteger bottomNumber, BigInteger module) {
  return bottomNumber.multiply(bottomNumber).remainder(module);
}

5.3 加密与解密

终于,所有的准备工作已完成,剩下的就是简单的加密和解密。下面的一段测试代码将直接说明加密与解密的过程:

private String plainText = "hello wolrd!";

private static boolean test(BigInteger e, BigInteger d, BigInteger n) {
  // 先将明文转为数字
  byte[] bytes = plainText.getBytes();
  BigInteger mm = new BigInteger(bytes);
  // 用公钥 e,n 加密
  BigInteger cc = powModByMontgomery(mm, e, n);
  // 用私钥 d,n 解密
  BigInteger dm = powModByMontgomery(cc, d, n);
  if (mm.compareTo(dm) != 0) {
    LOG.debug("Test failed");
    return false;
  } else {
    LOG.debug("Test passed ");
  }
  return true;
}

About

RSA算法是现代安全技术的基石,通过自己学习,利用JAVA代码实现其功能,项目中只有RSA的核心代码

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages