自己实现简单的RSA秘钥生成与加解密(Java )

最近在学习PKI,顺便接触了一些加密算法。对RSA着重研究了一下,自己也写了一个简单的实现RSA算法的Demo,包括公、私钥生成,加解密的实现。虽然比较简单,但是也大概囊括了RSA加解密的核心思想与流程。这里写下来与大家分享一下。

  RSA概述:

   RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,已被ISO推荐为公钥数据加密标准。 RSA的数学基础是大整数因子分解问题,其说明如下:

  • 给定两个素数p、q,计算乘积pq=n很容易

  • 给定整数n,求n的素因数p、q使得n=pq十分困难

  RSA的密码体制:

   设n=pq,其中p和q是大素数。设P=E=Zn,且定义K={(n,p,q,a,b):ab≡1(modΦ(n)) }其中 Φ(n)=(p-1)(q-1)。对于K={(n,p,q,a,b)} 定义加密 E(x)=xb mod n; 解密D(y)=ya mod n;(x,y∈Zn),值n,b组成了公钥,p、q和a组成了私钥

  •  RSA生成秘钥步骤   
  1. 随机生成两个大素数p、q,并计算他们的乘积n
  2. 计算k=(p-1)(q-1)
  3. 生成一个小于k且与k互质的数b,一般可使用65537
  4. 通过扩展欧几里得算法求出a关于k的反模a。

  代码如下:

首先,写出三个封装类,PrivateKey.java PublicKey.java RsaKeyPair.java (JDK中的实现高度抽象,这里我们就简单的封装一下)  

 1 package com.khalid.pki;
 2
 3 import java.math.BigInteger;
 4
 5 public class PublicKey {
 6
 7     private final BigInteger n;
 8
 9     private final BigInteger b;
10
11     public PublicKey(BigInteger n,BigInteger b){
12         this.n=n;
13         this.b=b;
14     }
15
16     public BigInteger getN() {
17         return n;
18     }
19
20     public BigInteger getB() {
21         return b;
22     }
23 }

 1 package com.khalid.pki;
 2
 3 import java.math.BigInteger;
 4
 5 public class PrivateKey {
 6
 7     private final BigInteger n;
 8
 9     private final BigInteger a;
10
11     public PrivateKey(BigInteger n,BigInteger a){
12         this.n=n;
13         this.a=a;
14     }
15
16     public BigInteger getN() {
17         return n;
18     }
19
20     public BigInteger getA() {
21         return a;
22     }
23 }

 1 package com.khalid.pki;
 2
 3 public class RsaKeyPair {
 4
 5     private final PrivateKey privateKey;
 6
 7     private final PublicKey publicKey;
 8
 9     public RsaKeyPair(PublicKey publicKey,PrivateKey privateKey){
10         this.privateKey=privateKey;
11         this.publicKey=publicKey;
12     }
13
14     public PrivateKey getPrivateKey() {
15         return privateKey;
16     }
17
18     public PublicKey getPublicKey() {
19         return publicKey;
20     }
21 }

生成大素数的方法没有自己实现,借用了BigInteger类中的probablePrime(int bitlength,SecureRandom random)方法

SecureRandom random=new SecureRandom();
random.setSeed(new Date().getTime());
BigInteger bigPrimep,bigPrimeq;
while(!(bigPrimep=BigInteger.probablePrime(bitlength,random)).isProbablePrime(1)){
            continue;
        }//生成大素数p

while(!(bigPrimeq=BigInteger.probablePrime(bitlength,random)).isProbablePrime(1)){
            continue;
        }//生成大素数q

计算k、n、b的值

1 BigInteger n=bigPrimep.multiply(bigPrimeq);//生成n
2         //生成k
3 BigInteger k=bigPrimep.subtract(BigInteger.ONE).multiply(bigPrimeq.subtract(BigInteger.ONE));
4         //生成一个比k小的b,或者使用65537
5 BigInteger b=BigInteger.probablePrime(bitlength-1, random);

核心计算a的值,扩展欧几里得算法如下

  注意第二个方法 cal 其实还可以传递第三个参数,模的值,但是我们这里省略了这个参数,因为在RSA中模是1

 1     private static BigInteger x; //存储临时的位置变量x,y 用于递归
 2
 3     private static BigInteger y;
 4
 5
 6     //欧几里得扩展算法
 7     public static BigInteger ex_gcd(BigInteger a,BigInteger b){
 8         if(b.intValue()==0){
 9             x=new BigInteger("1");
10             y=new BigInteger("0");
11             return a;
12         }
13         BigInteger ans=ex_gcd(b,a.mod(b));
14         BigInteger temp=x;
15         x=y;
16         y=temp.subtract(a.divide(b).multiply(y));
17         return ans;
18
19     }
20
21     //求反模
22     public static BigInteger cal(BigInteger a,BigInteger k){
23         BigInteger gcd=ex_gcd(a,k);
24         if(BigInteger.ONE.mod(gcd).intValue()!=0){
25             return new BigInteger("-1");
26         }
27         //由于我们只求乘法逆元 所以这里使用BigInteger.One,实际中如果需要更灵活可以多传递一个参数,表示模的值来代替这里
28         x=x.multiply(BigInteger.ONE.divide(gcd));
29         k=k.abs();
30         BigInteger ans=x.mod(k);
31         if(ans.compareTo(BigInteger.ZERO)<0) ans=ans.add(k);
32         return ans;
33
34     }

我们在生成中只需要

1 BigInteger a=cal(b,k);

就可以在Log级别的时间内计算出a的值

将以上代码结合包装成的 RSAGeneratorKey.java

 1 package com.khalid.pki;
 2
 3 import java.math.BigInteger;
 4 import java.security.SecureRandom;
 5 import java.util.Date;
 6
 7 public class RSAGeneratorKey {
 8
 9     private static BigInteger x; //存储临时的位置变量x,y 用于递归
10
11     private static BigInteger y;
12
13
14     //欧几里得扩展算法
15     public static BigInteger ex_gcd(BigInteger a,BigInteger b){
16         if(b.intValue()==0){
17             x=new BigInteger("1");
18             y=new BigInteger("0");
19             return a;
20         }
21         BigInteger ans=ex_gcd(b,a.mod(b));
22         BigInteger temp=x;
23         x=y;
24         y=temp.subtract(a.divide(b).multiply(y));
25         return ans;
26
27     }
28
29     //求反模
30     public static BigInteger cal(BigInteger a,BigInteger k){
31         BigInteger gcd=ex_gcd(a,k);
32         if(BigInteger.ONE.mod(gcd).intValue()!=0){
33             return new BigInteger("-1");
34         }
35         //由于我们只求乘法逆元 所以这里使用BigInteger.One,实际中如果需要更灵活可以多传递一个参数,表示模的值来代替这里
36         x=x.multiply(BigInteger.ONE.divide(gcd));
37         k=k.abs();
38         BigInteger ans=x.mod(k);
39         if(ans.compareTo(BigInteger.ZERO)<0) ans=ans.add(k);
40         return ans;
41
42     }
43
44     public static RsaKeyPair generatorKey(int bitlength){
45         SecureRandom random=new SecureRandom();
46         random.setSeed(new Date().getTime());
47         BigInteger bigPrimep,bigPrimeq;
48         while(!(bigPrimep=BigInteger.probablePrime(bitlength, random)).isProbablePrime(1)){
49             continue;
50         }//生成大素数p
51
52         while(!(bigPrimeq=BigInteger.probablePrime(bitlength, random)).isProbablePrime(1)){
53             continue;
54         }//生成大素数q
55
56         BigInteger n=bigPrimep.multiply(bigPrimeq);//生成n
57         //生成k
58         BigInteger k=bigPrimep.subtract(BigInteger.ONE).multiply(bigPrimeq.subtract(BigInteger.ONE));
59         //生成一个比k小的b,或者使用65537
60         BigInteger b=BigInteger.probablePrime(bitlength-1, random);
61         //根据扩展欧几里得算法生成b
62         BigInteger a=cal(b,k);
63         //存储入 公钥与私钥中
64         PrivateKey privateKey=new PrivateKey(n, a);
65         PublicKey  publicKey=new PublicKey(n, b);
66
67         //生成秘钥对 返回密钥对
68         return new RsaKeyPair(publicKey, privateKey);
69     }
70 }

编写一个简单的JUnit测试代码,没有把结果以字节流编码显示 ,比较懒。。。。

 1 package com.khalid.pki;
 2
 3 import org.junit.Test;
 4
 5 public class RSATest {
 6
 7     @Test
 8     public void testGeneratorKey(){
 9         RsaKeyPair keyPair=RSAGeneratorKey.generatorKey(256);
10         System.out.println("n的值是:"+keyPair.getPublicKey().getN());
11         System.out.println("公钥b:"+keyPair.getPublicKey().getB());
12         System.out.println("私钥a:"+keyPair.getPrivateKey().getA());
13     }
14
15
16 }

这样秘钥对的生成工作就完成了

 加密与解密

  加密与解密的根本就是使用E(x)=xb mod n D(y)=ya mod n这两个公式,所以我们首先要将数据转化为最底层的二进制串,在转换为BigInteger进行计算,将结果做成Base64码

代码如下

 1 package com.khalid.pki;
 2
 3 import java.io.UnsupportedEncodingException;
 4 import java.math.BigInteger;
 5 import java.util.Arrays;
 6
 7 import org.apache.commons.codec.binary.Base64;
 8
 9 public class RSAUtil {
10
11     public static String encrypt(String source,PublicKey key,String charset){
12         byte[] sourceByte = null;
13         try {
14             sourceByte = source.getBytes(charset);
15         } catch (UnsupportedEncodingException e) {
16             // TODO Auto-generated catch block
17             e.printStackTrace();
18         }
19         BigInteger temp=new BigInteger(1,sourceByte);
20         BigInteger encrypted=temp.modPow(key.getB(), key.getN());
21         return Base64.encodeBase64String(encrypted.toByteArray());
22         }
23
24     public static String decrypt(String cryptdata,PrivateKey key,String charset) throws UnsupportedEncodingException{
25         byte[] byteTmp=Base64.decodeBase64(cryptdata);
26         BigInteger cryptedBig=new BigInteger(byteTmp);
27         byte[] cryptedData=cryptedBig.modPow(key.getA(), key.getN()).toByteArray();
28         cryptedData=Arrays.copyOfRange(cryptedData, 1, cryptedData.length);//去除符号位的字节
29         return new String(cryptedData,charset);
30
31     }
32 }

 很坑爹的一点是要记得BigInteger是有符号位的。重组String时要记得去除符号位,不然中文的时候会莫名其妙在第一位多出空格。

在编写一个测试代码就Ok了

 1 package com.khalid.pki;
 2
 3 import java.io.UnsupportedEncodingException;
 4 import org.junit.Test;
 5
 6 public class RSATest {
 7
 8
 9     @Test
10     public void testRSA() throws UnsupportedEncodingException{
11         //生成KeyPair
12         RsaKeyPair keyPair=RSAGeneratorKey.generatorKey(1024);
13         // 元数据
14         String source = new String("哈哈哈哈哈~~~嘿嘿嘿嘿嘿~~---呵呵呵呵呵!");
15
16         System.out.println("加密前:"+source);
17         //使用公钥加密
18         String cryptdata=RSAUtil.encrypt(source, keyPair.getPublicKey(),"UTF-8");
19         System.out.println("加密后:"+cryptdata);
20         //使用私钥解密
21         try {
22             String result=RSAUtil.decrypt(cryptdata, keyPair.getPrivateKey(),"UTF-8");
23             System.out.println("解密后:"+result);
24             System.out.println(result.equals(source));
25         } catch (UnsupportedEncodingException e) {
26             // TODO Auto-generated catch block
27             e.printStackTrace();
28         }
29
30     }
31 }

测试结果。bingo。

时间: 2024-11-08 10:55:56

自己实现简单的RSA秘钥生成与加解密(Java )的相关文章

APICloud集成支付宝--RSA秘钥生成详解

RSA 和 OpenSSL 介绍 记得大学里有门课叫<电子支付与安全>,这里面就讲了双重秘钥加密,主要内容如下: RSA 是一种非对称的签名算法,即签名密钥(私钥)与验签密钥(公钥)是不一样的, 私钥用于签名,公钥用于验签.在与支付宝交易中,会有 2 对公私钥,即商户公私钥,支付宝公私钥.使用这种算法可以起到防止数据被篡改的功能,保证支付订单和支付结果不可抵赖(商户私钥只有商户知道). OpenSSL 是基于众多的密码算法.公钥基础设施标准以及 SSL 协议安全开 发包.通过 OpenSSL

一台电脑存放多个git账户的多个rsa秘钥(转)

如何在一个电脑上存储多个git账户生成的多份rsa秘钥,实现多个账户同时使用配置的情况?今天,不幸又再次遇到这个问题. 问题描述 公司最近在开发一款开源产品,项目被托管在github上,但是公司内部一直使用的是gitlab,而且早就配了gitlab的rsa秘钥.之前的做法是重新生成一个github秘钥,覆盖掉之前的gitlab秘钥.每次切换过程就意味每次需要重新生成一份秘钥.只有两个账户的情况还能勉强接受下,某一天再想搞个小号刷副本时,这酸爽真的没法形容. 解决办法 今天总算想了一个办法,解决方

git秘钥生成

#修改git配置 vi .gitconfig #生成秘钥 ssh-keygen -t rsa -C "邮箱地址" #查看秘钥 cat id_rsa.pub

Git秘钥生成以及Gitlab配置(附以下问题解决方法:Key is invalid Fingerprint cannot be generated)

在进行Git密钥配置时,总是提示: “The form contains the following errors:Key is invalidFingerprint cannot be generated” 如下图: 查找了不少资料,大部分说粘贴的字符与实际生成的不一致,例如windows环境下,如果有换行习惯带"\r\n",去除即可.几次尝试都不可以. 最后发现是粘贴的内容有问题,只黏贴了密钥,没有前贴前面的“ssh-rsa” 和后面的邮箱. 谨记:要粘贴pub密钥里面的所有内容(

WSL、Git on Windows 、Putty等的创建的rsa秘钥与连接linux的使用。

1. 在windows 上面可以使用多种方式创建公钥和私钥 这里从一开始说: 1.1 windows subsystem linux 的方式最简单了 cmd 命令行 下 进入 WSL 输入命令 bash 然后 输入 ssh-keygen 创建私钥创建一个 证书存放位置. 然后就可以生成了 如上就是简单生成的 私钥 (这个秘钥对我来说没用.) 其实可以看一下 ssh-copy-id 的默认文件总是去找 /root/.ssh 目录下面的 其实 我这个证书秘钥文件 是当时我学习git 时 用git创建

openssl -- RSA秘钥格式 PEM/PKCS#8

1.PEM私钥格式文件 -----BEGIN RSA PRIVATE KEY----- -----END RSA PRIVATE KEY----- 2. PKCS8私钥格式格式 -----BEGIN PRIVATE KEY----- -----END PRIVATE KEY----- 2. PEM公钥格式文件 -----BEGIN PUBLIC KEY----- -----END PUBLIC KEY----- 3. PEM RSAPublicKey公钥格式文件 -----BEGIN RSA P

day02 秘钥生成,免密访问命令

hadoop免密登陆: 生成秘钥: ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys cat ~/id_dsa.pub >> ~/.ssh/authorized_keys scp id_dsa.pub [email protected]:~ /etc/hosts,主机名和IP配置文件:  vi /etc/hosts回车,切换到编辑模式'i',手动添加 12

ios下使用rsa算法与php进行加解密通讯

首先了解一下几个相关概念,以方便后面遇到的问题的解决: RSA算法:1977年由Ron Rivest.Adi Shamirh和LenAdleman发明的,RSA就是取自他们三个人的名字.算法基于一个数论:将两个大素数相乘非常容易,但要对这个乘积的结果进行因式分解却非常困难,因此可以把乘积公开作为公钥.该算法能够抵抗目前已知的所有密码攻击.RSA算法是一种非对称算法,算法需要一对密钥,使用其中一个加密,需要使用另外一个才能解密.我们在进行RSA加密通讯时,就把公钥放在客户端,私钥留在服务器. DE

百度地图API秘钥生成步骤

百度API 原文地址:https://www.cnblogs.com/bbc66/p/9581011.html