最近在学习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。

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

  1. 多个git账户生成多份rsa秘钥实现多个账户同时使用配置

    下文分享一个多个git账户生成多份rsa秘钥实现多个账户同时使用配置例子了,这个例子非常的好用对于有多个git的朋友有不小的帮助. 使用过git的童鞋应该对id_rsa秘钥不陌生,总得用github吧 ...

  2. 开源工具 DotnetRSA 快速生成和转换RSA秘钥

    一.简介 DotnetRSA 是一个利用 .NET Core 2.1 开发的 .NET Global Tool,是可以想npm全局安装一样,安装在你的系统中,只需敲一行命令便可以快速生成RSA加密算法 ...

  3. 利用SHA-1算法和RSA秘钥进行签名验签(带注释)

    背景介绍 1.SHA 安全散列算法SHA (Secure Hash Algorithm)是美国国家标准和技术局发布的国家标准FIPS PUB 180-1,一般称为SHA-1.其对长度不超过264二进制 ...

  4. 一台电脑存放多个git账户的多个rsa秘钥

    未命名.html div.oembedall-githubrepos{border:1px solid #DDD;border-radius:4px;list-style-type:none;marg ...

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

    如何在一个电脑上存储多个git账户生成的多份rsa秘钥,实现多个账户同时使用配置的情况?今天,不幸又再次遇到这个问题. 问题描述 公司最近在开发一款开源产品,项目被托管在github上,但是公司内部一 ...

  6. Ubuntu ssh秘钥生成

    一,秘钥生成传送 ssh-keygen 选项: -b:指定密钥长度: -e:读取openssh的私钥或者公钥文件: -C:添加注释: -f:指定用来保存密钥的文件名: -i:读取未加密的ssh-v2兼 ...

  7. RSA - 原理、特点(加解密及签名验签)及公钥和私钥的生成

    Wiki - RSA加密演算法 Wiki - 欧拉函数 Wiki - 模反元素 ASN.1 格式标准 RSA算法原理(二) 注意: RSA 加密或签名后的结果是不可读的二进制,使用时经常会转为 BAS ...

  8. Rsa加解密Java、C#、php通用代码 密钥转换工具

    之前发了一篇"TripleDes的加解密Java.C#.php通用代码",后面又有项目用到了Rsa加解密,还是在不同系统之间进行交互,Rsa在不同语言的密钥格式不一样,所以过程中主 ...

  9. 使用OpenSSL生成RSA秘钥对并对文件加解密

    生成RSA私钥 openssl genrsa -out rsa.key 1024 生成RSA公钥 openssl rsa -in rsa.key -pubout -out pub.key 创建明文文件 ...

随机推荐

  1. Mockplus推出真正无限制终身版,做原型就要一辈子!

    如今提到原型工具,各位设计师和PM爸爸们一定不会对Mockplus感到陌生吧?事实上,从一开始的默默无闻,到在UXPA大赛上崭露头角,再到被Adobe XD 列为主要竞品,如今,摩客君已经在全球范围内 ...

  2. PTA第一次作业和第二次作业

    PTA的第一次作业第一题: #include <stdio.h> int main (void) { int grade,i,N ,a=0,b=0,c=0,d=0,e=0; printf( ...

  3. VS2010 MFC 使用GDI+给图片添加汉字

    1.配置GDI+ VS2010自带GDI+,直接使用. (1)首先要添加头文件和库 #pragma comment( lib, "gdiplus.lib" ) #include & ...

  4. ajax序列化表单,再也不用通过data去一个个的传值了

    jQuery的serialize()方法通过序列化表单值,创建URL编码文本字符串,我们就可以选择一个或多个表单元素,也可以直接选择form将其序列化 这样,我们就可以把序列化的值传给ajax()作为 ...

  5. 第三章 形容词(Les adjectifs)

    ★形容词的性(Le genre de l'adjectif ) ()一般规则是在阳性形容词后加-e:         français ➞francaise法国的         content ➞c ...

  6. vs2010 EF4.0 访问mysql

    需要安装mysql-connector-net-6.3.5 6.8.9的安装完后在dbfirst里找不到对应的提供程序 链接字符串里需要 指定下编码(如果不是gbk的话) <add name=& ...

  7. Linux命令:ps

    Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信 ...

  8. C++之类和对象课后习题1

    建立一个对象数组,内放5个学生的(学号,成绩),设立一个函数max,用指向对象的指针作函数参数,在max函数中找出5个学生的最高成绩者,并输出其学号. #include<iostream> ...

  9. UVa 11636 Hello World! (水题思维)

    题意:给你一个数,让你求需要复制粘贴多少次才能达到这个数. 析:这真是一个水题,相当水,很容易知道每次都翻倍,只要大于等于给定的数就ok了. 代码如下: #include <iostream&g ...

  10. Android继承BaseAdapter时要重写的函数的说明

    原文来自:http://www.2cto.com/kf/201405/299601.html,我自己做了一些修改 Android中继承BaseAdapter后需要重写四个函数,但一般还要写一个构造函数 ...