RSA原理,java实现RSA

各种加密算法

不可逆性算法:加密后的结果,不可逆向算出明文。如md5,加密结果固定,不安全,弱密码可以通过穷举法反推出明文。
对称加密算法:加密和解密使用同一个密码。如AES,加密结果固定。
非对加密算法:加密和解密使用不同的密码。如RSA,每次加密结果是不同的。

根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。

因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。

1、互质关系

质数:又称素数。除了1和它自身外,不能被其他自然数整除的数叫做质数。

如果两个正整数,除了1以外,没有其他公因子,我们就称这两个数是互质关系(coprime)。
比如,15和32没有公因子,所以它们是互质关系。这说明,不是质数也可以构成互质关系。
关于互质关系,有下结论:

1. 任意两个质数构成互质关系,比如13和61。
2. 一个数是质数,另一个数只要不是前者的倍数,两者就构成互质关系,比如3和10。
3. 如果两个数之中,较大的那个数是质数,则两者构成互质关系,比如97和57。
4. 1和任意一个自然数是都是互质关系,比如1和99。
5. p是大于1的整数,则p和p-1构成互质关系,比如57和56。
6. p是大于1的奇数,则p和p-2构成互质关系,比如17和15。

2、欧拉函数

任意正整数n,在小于等于n的正整数之中,有多少个与n构成互质关系?(比如,在1到8之中,有多少个数与8构成互质关系?)
计算这个值的方法就叫做欧拉函数,以φ(n)表示。
1、如果n是质数,则 φ(n) = n-1 。因为质数与小于它的每一个数,都构成互质关系。

2、如果n可以分解成两个互质的整数之积,n = p1 × p2
则 φ(n) = φ(p1p2) = φ(p1)φ(p2)
如果p和q都为质数,且n=p*q。(这个后面RSA会用到)
φ(n) = φ(pq) = φ(p)φ(q) = (p-1)(q-1)
3、任意一个大于1的正整数,都可以写成一系列质数


这个公式要求,p1、p2、p3...必须为质数。p1、p2、p3...之间不能相等,相等的需要合并。

在1到8之中,与8形成互质关系的是1、3、5、7,所以 φ(n) = 4。
可以根据欧拉函数计算:
φ(8) = φ(2^3) = 8(1-1/2) = 4
φ(7) = φ(7^1) = 7(1-1/7) = 6

比如,1323的欧拉函数,计算过程如下:
φ(1323) = φ(3^3 × 7^2) = 1323(1-1/3)(1-1/7) = 756

3、欧拉定理(很重要)

如果两个正整数a和n互质,则n的欧拉函数 φ(n) 可以让下面的等式成立
欧拉定理的证明比较复杂,这里就省略了。

也就是说,a的φ(n)次方,除以n的余数为1。或者说,a的φ(n)次方减去1,可以被n整除。


比如,7和10互质,根据欧拉函数知道:φ(10) = φ(2^1 × 5^1) = 10(1-1/2)(1-1/5) = 4
所以7的4次方(2401)除以10(2401%10=1),余数为1。
或者说,7和10互质,而10的欧拉函数φ(10)等于4,所以7的4次方(2401)减去1,可以被10整除(2400/10=240)。

欧拉定理有一个特殊情况:

假设正整数a与质数p互质,因为质数p的φ(p)等于p-1,则欧拉定理可以写成

这就是著名的费马小定理,它是欧拉定理的特例。
欧拉定理是RSA算法的核心。理解了这个定理,就可以理解RSA。

4、模反元素(很重要)

如果两个正整数a和n互质,那么一定可以找到整数b,使得 ab-1 被n整除,或者说ab被n除的余数是1。

欧拉定理可以用来证明模反元素必然存在。

可以看到,a的 φ(n)-1 次方,就是b。 

这时,b就叫做a的"模反元素"。ab = kn + 1

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

5、密钥生成的步骤

我们通过一个例子,来理解RSA算法。假设黄蓉要与郭靖进行加密通信,她该怎么生成公钥和私钥呢?

1、第一步随机选择两个不相等的质数p和质数q
黄蓉选择了61和53。(实际应用中,这两个质数越大,就越难破解。)

2、第二步,计算p和q的乘积n。n = p * q
黄蓉就把61和53相乘。n = 61×53 = 3233
n的长度就是密钥长度。3233写成二进制是110010100001,一共有12位,所以这个密钥就是12位。
实际应用中,RSA密钥一般是1024位,重要场合则为2048位。

3、第三步,计算n的欧拉函数φ(n)。
根据公式:φ(n) = (p-1)(q-1)
黄蓉算出φ(3233)等于60×52,即3120。

4、第四步,随机选择一个整数e,条件是1< e < φ(n),且e与φ(n) 互质
黄蓉就在1到3120之间,随机选择了17。(实际应用中,常常选择65537。)

5、第五步,计算e对于φ(n)的模反元素d。
因为e与φ(n) 互质,必然存在 ed ≡ 1 (mod φ(n))
也就是必然存在一个"模反元素"d,可以使得ed被φ(n)除的余数为1。
ed ≡ 1 (mod φ(n))  这个式子等价于  ed  = kφ(n) + 1
于是,找到模反元素d,实质上就是对下面这个二元一次方程求解。(-k = y)
ex + φ(n)y = 1
已知 e=17, φ(n)=3120,17x + 3120y = 1
这个方程可以用"扩展欧几里得算法"求解,此处省略具体过程。总之,黄蓉算出一组整数解为 (x,y)=(2753,-15),即 d=2753。
至此所有计算完成。

6、第六步,将n和e封装成公钥,n和d封装成私钥
在黄蓉的例子中,n=3233,e=17,d=2753,所以公钥就是 (3233,17),私钥就是(3233, 2753)。
实际应用中,公钥和私钥的数据都采用ASN.1格式表达(实例)。

6、RSA算法的可靠性

回顾上面的密钥生成步骤,一共出现六个数字:

p (质数)
q (质数)
n = pq
φ(n) = (p-1)(q-1)
e (e与φ(n) 互质)
d (模反元素)

这六个数字之中,公钥用到了两个(n和e),其余四个数字都是不公开的。其中最关键的是d,因为n和d组成了私钥,一旦d泄漏,就等于私钥泄漏。

那么,有无可能在已知n和e的情况下,推导出d?
(1)ed≡1 (mod φ(n))。只有知道e和φ(n),才能算出d。
(2)φ(n)=(p-1)(q-1)。只有知道p和q,才能算出φ(n)。
(3)n=pq。只有将n因数分解,才能算出p和q。

结论:如果n可以被因数分解,d就可以算出,也就意味着私钥被破解

可是,大整数的因数分解,是一件非常困难的事情。目前,除了暴力破解,还没有发现别的有效方法。维基百科这样写道:

对极大整数做因数分解的难度决定了RSA算法的可靠性。
换言之,对一极大整数做因数分解愈困难,RSA算法愈可靠。

假如有人找到一种快速因数分解的算法,那么RSA的可靠性就会极度下降。
但找到这样的算法的可能性是非常小的。
今天只有短的RSA密钥才可能被暴力破解。
到2008年为止,世界上还没有任何可靠的攻击RSA算法的方式。

只要密钥长度足够长,用RSA加密的信息实际上是不能被解破的。

举例来说,你可以对3233进行因数分解(61×53),但是你没法对下面这个整数进行因数分解。

12301866845301177551304949
58384962720772853569595334
79219732245215172640050726
36575187452021997864693899
56474942774063845925192557
32630345373154826850791702
61221429134616704292143116
02221240479274737794080665
351419597459856902143413

它等于这样两个质数的乘积:

33478071698956898786044169
84821269081770479498371376
85689124313889828837938780
02287614711652531743087737
814467999489
×
36746043666799590428244633
79962795263227915816434308
76426760322838157396665112
79233373417143396810270092
798736308917

事实上,这大概是人类已经分解的最大整数(232个十进制位,768个二进制位)。比它更大的因数分解,还没有被报道过,因此目前被破解的最长RSA密钥就是768位。

即使使用当前最先进的计算机和算法,分解一个1024位的二进制数可能需要数百或数千年的时间。

因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。

7、加密和解密

有了公钥和密钥,就能进行加密和解密了。

(1)加密要用公钥 (n,e)

假设郭靖要向黄蓉发送加密信息m,他就要用黄蓉的公钥 (n,e) 对m进行加密。这里需要注意,m必须是整数(字符串可以取ascii值或unicode值),且m必须小于n。

所谓"加密",就是算出下式的 c = m^e % n

m^e ≡ c (mod n)

m的e次方除以n的余数为c,黄蓉的公钥是 (n=3233, e=17),郭靖的m假设是65,那么可以算出下面的等式:

65 ^ 17 ≡ 2790 (mod 3233)

 于是,c等于2790,郭靖就把2790发给了黄蓉。

(2)解密要用私钥(n,d)

黄蓉拿到郭靖发来的2790以后,就用自己的私钥(3233, 2753) 进行解密。

所谓"解密",就是算出下式的 m = c^d % n

c^d ≡ m (mod n)

也就是说,c的d次方除以n的余数为m。现在,c等于2790,私钥是(n=3233, d=2753),那么,黄蓉算出

2790 ^ 2753 ≡ 65 (mod 3233)

 因此,黄蓉知道了郭靖加密前的原文就是65。

至此,"加密--解密"的整个过程全部完成。

我们可以看到,如果不知道d,就没有办法从c求出m。而前面已经说过,要知道d就必须分解n,这是极难做到的,所以RSA算法保证了通信安全。

你可能会问,公钥(n,e) 只能加密小于n的整数m,那么如果要加密大于n的整数,该怎么办?
有两种解决方法:一种是把长信息分割成若干段短消息,每段分别加密;
另一种是先选择一种"对称性加密算法"(比如DES),用这种算法的密钥加密信息,再用RSA公钥加密DES密钥。

8、私钥解密的证明

先了解 (a+b)的n次方展开式

比如 

 最后,我们来证明,为什么用私钥解密,一定可以正确地得到m。

根据加密规则

证明这个解密规则

根据加密规则 m^e ≡ c (mod n) 可以写成下面的形式

c = m^e - kn

将c代入要我们要证明的那个解密规则

 由展开式可知,左侧式子为:

观察可知,等式左边的多项式拆开以后,只要是有kn的项都能被n整除,所以可以去掉所有含有kn的项,即等同于求证

由于

所以

将ed代入 m^(ed) ≡ m (mod n)

接下来,分成两种情况证明上面这个式子。

(1)m与n互质

根据欧拉定理,此时

根据加密规则我们有m < n,所以给等式同时乘m,得到

原式得到证明。

(2)m与n不是互质关系

一个数是质数,另一个数只要不是前者的倍数,两者就构成互质关系。

当m与n不互质时(m < n),由于n=p * q,那么最大公约数 gcd(m,n) = p 或者 gcd(m,n)=q
也就是由于p和q都是质数,m与p*q的乘积n不是互质关系,所以m必然等于kp或kq

以 m = kp为例

由于q为质数,假设m和q不互质,则m一定为q的倍数,即 m = tq
也即 kp = tq,因为p和q为质数,要想等式kp = tq成立,所以k一定为q的倍数k=xq,t一定为p的倍数t=yp。
又由于 m<n,所以 kp<pq 且 tq<pq,即 k<q 且 t<p 这与k为q的倍数,t为p的倍数矛盾。

所以:m与q必然互质,则根据费马小定理

类似之前的推导

 

根据之前的式子我们可以进行如下推导

 

 改写这个等式到

两边乘上 m 得到

 最后一步

 原式得证!

9、java实现RSA加密解密

总结RSA一共出现六个数字,关系如下:
1、p (质数)
2、q (质数)
3、n = pq
4、φ(n) = (p-1)(q-1)
5、e (e与φ(n) 互质)
6、d (模反元素)

补充:
n的值也是密钥长度
1 < e < φ(n)
ed % φ(n) = 1
(n,e)作为公钥对、(n,d)作为私钥对,至于p和q,一旦生成密钥就应该及时销毁,因为它除了可以让人窃取了之后直接破解之外,没有任何其他的作用。
加密:c = m^e % n
解密:m = c^d % n

m为要加密的字符,c为加密后的字符。

e/d可以互换,等式上依然成立,也就是说从数学原理上公钥私钥可以互换,
但一般公钥的指数很短,这样破解就会变的很容易,在这种意义上,公钥私钥是不可以互换的。

RSA实现原理 

package com.study;


import java.math.BigInteger;
import java.security.SecureRandom;

public class RSA {

    public static void main(String[] args) {
        int keySize = 1024;
        SecureRandom secureRandom = new SecureRandom();
        //返回一个随机keySize个二进制位的质数
        BigInteger p = BigInteger.probablePrime(keySize, secureRandom);// p
        BigInteger q = BigInteger.probablePrime(keySize, secureRandom);// q
        BigInteger e = BigInteger.probablePrime(keySize, secureRandom);// 1 < e < φ(n)=(p-1)(q-1)
        BigInteger n = p.multiply(q);// n = p * q
        BigInteger $n = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));// φ(n) = (p-1) * (q-1)
        // 计算模反元素d
        BigInteger d = e.modInverse($n);// ed % φ(n) = 1

        int bitCount = n.bitCount();
        System.out.println("密钥长度:" + bitCount);

        byte[] m = "123".getBytes();//待加密字符串包含汉字会出错,这里一般转为Base64字符串
        System.out.println("原文:" + new String(m));

        // 加密,(e, n)作为公钥对,密文 = 明文 ^ e % n
        byte[] encode = new BigInteger(m).modPow(e, n).toByteArray();//c = m^e % n
        System.out.println("加密后:" + new String(encode));

        // 解密,(d, n)作为私钥对,明文 = 密文 ^ d % n
        byte[] decode = new BigInteger(encode).modPow(d, n).toByteArray();//m = c^d % n
        System.out.println("解密后:" + new String(decode));
    }
}

RSA工具类 

package com.study;


import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;

/**
 * 非对称加密
 * 这里做原理测试,严谨做法请全部使用java.security.*包中的方法
 * @date 2021年2月25日 上午11:57:50
 */
public class RSAUtil {
    private static String eKey = "151921844699844218625546358250800749508910705507524843756059881719275328142249347338662967910549681516701135610032563906823087458332856347403497255833803914470548393528857802677073855357424853525446795552666069410782210088655958433239963391921691519753221355294472481388195650287943708467010997083592442224449";
    private static String nKey = "21015778319079801013636644732280407738646686863859704730860530471742034302439446099559856006495968540098067886977283858406700078745076818504809975256316707583191530030035227886481529984617466421103167678081339979192676425588581620789960498175191576143828583493125639533691896963139122435888674976036028946775823973694365899051656973710675213381319048045821668060450202382711854637624354027090185568477316005955341489937442168529277014964465834825480355545096182745587197641696088898670457454648608984815688803047261815100609204284878747225942419781059141586261525054921205854115375653064651993267889252585496250445919";
    private static String dKey = "10825682069936784739614554801476068844406424729722417277170170377196969143469016770323844279277162799169758936390983016072302205270449399290146173168367611517029548075563414814334349295476896934436387838923660943344521768172396152617206174699268012017583344630494305692013998599393555648309926333747504968663526863294217777474421998958681664285295167108822287757075810670966531279262487766256143261595915745344511331349215309682559840573517848106523755624882204802122425621204888576944327022834100544160631205394108537556253887697705813809985242312576395677586346705951829474963156055703287868942128584443320672465193";

    public static void main(String[] args) {
        //生成公钥对和私钥对
        // getKey();
        String code = "你好, RSA!";
        System.out.println("原文:" + code);
        String encrypt = encrypt(eKey, nKey, code);
        System.out.println("加密后:" + encrypt);
        String decrypt = decrypt(dKey, nKey, encrypt);
        System.out.println("解密后:" + decrypt);
    }

    /**
     * 生成密钥对
     * @date 2021年2月25日 下午4:28:36
     */
    public static void getKey() {
        int keySize = 1024;// 越大越安全,但速度减慢,一般使用1024或2048
        SecureRandom secureRandom = new SecureRandom();
        BigInteger p = BigInteger.probablePrime(keySize, secureRandom);// p
        BigInteger q = BigInteger.probablePrime(keySize, secureRandom);// q
        BigInteger e = BigInteger.probablePrime(keySize, secureRandom);// 1 < e < φ(n)=(p-1)(q-1)
        BigInteger n = p.multiply(q);// n = p * q
        BigInteger $n = p.subtract(BigInteger.ONE).multiply(q.subtract(BigInteger.ONE));// φ(n) = (p-1) * (q-1)
        // 计算模反元素d
        BigInteger d = e.modInverse($n);// ed % φ(n) = 1
        int bitCount = n.bitCount();
        System.out.println("密钥长度:" + bitCount);
        System.out.println("nKey:" + n);
        System.out.println("eKey:" + e);
        System.out.println("dKey:" + d);
    }

    /**
     * 加密
     * @param eKey
     * @param nKey
     * @param v
     * @return
     * @date 2021年2月25日 下午1:08:19
     */
    public static String encrypt(String eKey, String nKey, String v) {
        v = Base64.getEncoder().encodeToString(v.getBytes(StandardCharsets.UTF_8));// 汉字不支持,可以先编码
        BigInteger e = new BigInteger(eKey);
        BigInteger n = new BigInteger(nKey);
        byte[] encode = new BigInteger(v.getBytes(StandardCharsets.UTF_8)).modPow(e, n).toByteArray();
        return Base64.getEncoder().encodeToString(encode);
    }

    /**
     * 解密
     * @param dKey
     * @param nKey
     * @param v
     * @return
     * @date 2021年2月25日 下午1:08:19
     */
    public static String decrypt(String dKey, String nKey, String v) {
        BigInteger d = new BigInteger(dKey);
        BigInteger n = new BigInteger(nKey);
        byte[] decode = new BigInteger(Base64.getDecoder().decode(v)).modPow(d, n).toByteArray();
        decode = Base64.getDecoder().decode(decode);
        return new String(decode, StandardCharsets.UTF_8);

    }
}

参考:

RSA算法原理 - 知乎

RSA算法原理_w3cschool

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值