第二周作业——RSA算法的实现

算法已学习到第一章"数字的算法",这次对RSA的知识做个总结.

作业要求: 计科1111-1114班第二周讲义、课外作业


几个基本概念的理解:

RSA

RSA算法是一种非对称密码算法,所谓非对称,就是指该算法需要一对密钥,使用其中一个加密,则需要用另一个才能解密。
RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
在传送信息时,常采用传统加密方法与公开密钥加密方法相结合的方式,即信息采用改进的DES或IDEA对话密钥加密,然后使用RSA密钥加密对话密钥和信息摘要。对方收到信息后,用不同的密钥解密并可核对信息摘要。

数字签名

数字签名(又称公钥数字签名、电子签章)是一种类似写在纸上的普通的物理签名,但是使用了公钥加密领域的技术实现,用于鉴别数字信息的方法。一套数字签名通常定义两种互补的运算,一个用于签名,另一个用于验证。数字签名是只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。
数字签名技术用发送者的私钥对摘要信息进行加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用HASH函数对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。
数字签名是个使用私钥加密的过程,数字签名验证是个使用公钥解密的过程。


DES

DES(Data Encryption Standard,数据加密标准) 使用一个 56 位的密钥以及附加的 8 位奇偶校验位(每组的第8位作为奇偶校验位),产生最大 64 位的分组大小。这是一个迭代的分组密码,使用称为 Feistel 的技术,其中将加密的文本块分成两半。使用子密钥对其中一半应用循环功能,然后将输出与另一半进行“异或”运算;接着交换这两半,这一过程会继续下去,但最后一个循环不交换。DES 使用 16 轮循环,使用异或,置换,代换,移位操作四种基本运算。

公钥

公钥是与私钥算法一起使用的密钥对的非秘密的那一半。公钥通常用于加密会话密钥、验证数字签名,或加密可以用相应的私钥解密的数据。公钥和私钥是通过一种算法得到的一个密钥对(即一个公钥和一个私钥)其中的一个向外界公开,称为公钥;另一个自己保留,称为私钥。通过这种算法得到的密钥对能保证在世界范围内是唯一的。使用这个密钥对的时候,如果用其中一个密钥加密一段数据,必须用另一个密钥解密。比如用公钥加密数据就必须用私钥解密,如果用私钥加密也必须用公钥解密,否则解密将不会成功。

私钥

私钥加密算法使用单个私钥来加密和解密数据。由于具有密钥的任意一方都可以使用该密钥解密数据,因此必须保护密钥不被未经授权的代理得到。私钥加密又称为对 称加密,因为同一密钥既用于加密又用于解密。私钥加密算法非常快(与公钥算法相比),特别适用于对较大的数据流执行加密转换。通常,私钥算法(称为块密码)用于一次加密一个数据块。


二. 算法作业: 实现RSA算法(书籍P40)

2.1: 判断一个正整数是否为质数

函数签名:  int isPrime(long a)
输入:一个长整数a
输出:返回1(为质数),返回0(非质数)
	/**
	 * 2.1: 判断一个正整数是否为质数的算法。
	 * 		输入:一个长整数a
	 * 		输出:返回1(为质数),返回0(非质数)
	 */
	public static int isPrime(long a){
		if( a<=1 ){
			return 0;
		}
		if( a==2 ){
			return 1;
		}
		
		int sqrt = (int) Math.sqrt(a);
		//为了调高性能,先判断a是否为偶数或是否能开平方
		if(a%2==0 || a%sqrt==0){
			return 0;
		}
		//跳过所有偶数,只测试a是否能被奇数整除
		for(int i=3;i<Math.sqrt(a);){
			if(a%i == 0){
				return 0;
			}
			i += 2;
		}
		return 1;
	}
验证结果:


2.2: 随机生成一个n bit位的长整数

函数签名:  long createRndInteger(int n)
输入:随机数bit位的长度为n(解释:3bit位,则最大为111,最小为100;n bit位,则该数字二进制长度为n)
输出:返回该随机数
	/**
	 * 	2.2: 随机生成一个n bit位的长整数。
	 * 		输入:随机数bit位的长度为n(解释:3bit位,则最大为111,最小为100;n bit位,则该数字二进制长度为n)
	 * 		输出:返回该随机数
	 */
	public static  long createRndInteger(int n){
		Random rnd = new Random();
		if(n <= 0){
			return 0;
		}
		if(n==1){
			return rnd.nextInt(2);
		}
		if(n>=64){
			throw new RuntimeException("long类型的最大值为9223372036854775807(2^64-1),n最大只能为63!");
		}
		//n>=2 时, 二进制数的首位为1
		String strBin = "1";
		for(int i=1;i<n;i++){
			strBin += rnd.nextInt(2);
		}
		System.err.println(n + "位二进制数为: " + strBin);

		//将二进制字符串转换成十进制数
		return Long.parseLong(strBin, 2);
	}
验证结果:


2.3:随机生成一个n bit位的长质数

函数签名:  long createRndPrime(int n)
输入:随机质数的bit位长度为n
输出:nbit位长度的质数
关键为使用随机化算法判断一个长整数是否为素数(P33)。
	/**
	 * 2.3:随机生成一个n bit位的长质数。
	 * 	  	输入:随机质数的bit位长度为n
	 * 		输出:nbit位长度的质数	    	
	 * 		关键为使用随机化算法判断一个长整数是否为素数(P33)。
	 */
	public static long createRndPrime(int n){
		if(n <=1 ){
			throw new RuntimeException("n必须大于1...");
		}
		if(n >=64 ){
			throw new RuntimeException("n必须小于64...");
		}
		if(n==2){
			return createRndInteger(n);
		}
		
		long longPrime;
		do{
			longPrime = createRndInteger(n);
			//如果随机生成的长整数为偶数,则加1使之为奇数
			if(longPrime%2 == 0){
				longPrime += 1;
			}
		}while(isPrime(longPrime)==0);//生成的长整数不是质数,重新生成长整数		
		
		return longPrime;
		
		/**
		 * 调用JDK自带的随机生成长质数的方法: 
		 * 		java.math.BigInteger.probablePrime(int bitLength, Random rnd)
		 */
	}


验证结果:


2.4:公开密钥(N,e)的生成算法 

关键为怎样选择一个与(p-1)(q-1)互质的数e。
	/**
	 * 	2.4:公开密钥(N,e)的生成算法 
	 * 		关键为怎样选择一个与(p-1)(q-1)互质的数e。			
	 */
	public static void  createPK(int n){
		if(n>=32){
			throw new RuntimeException("n必须小于32...");
		}
		//随机产生两个n bit且不同的素数p和q, 令N=pq
		p = createRndPrime(n);
		do{
			q = createRndPrime(n);
		}while(p==q);
		
		N = p * q;
		
		//选取与 tmp=(p-1)(q-1) 互素的整数e (若取e为奇数, 且e不能整除tmp,则tmp和e互素)
		long tmp = (p-1)*(q-1);
		
		//从3开始找不能整除tmp的奇数 
		e = 3;
		do{
			e += 2;
		}while(tmp%e == 0);		
		System.err.println("公钥产生成功!  p="+p+" ,q="+q+" ,(N,e) = "+ "(" + N + "," + e + ")"); 		
	}

验证结果:


2.5:保密密钥(N,d)的生成算法

关键是运用扩展Euclid算法,生成e模(p-1)(q-1)的逆元,见书籍25页。
	/**
	 * 2.5:保密密钥(N,d)的生成算法
	 * 		关键是运用扩展Euclid算法,生成e模(p-1)(q-1)的逆元,见书籍25页。
	 * 		d为e模(p-1)(q-1)操作的逆, 即 d = (e^-1) % (p-1)(q-1)
	 */
	public static void createSK(){
		long tmp = (p-1)*(q-1);
		//根据扩展欧几里得算法求 d = ax+by 的解
		long ext[] = extendedEuclid( tmp, e);
		System.err.println("(x,y,d) = ("+ext[0]+", "+ext[1]+", "+ext[2]+") ,(p-1)(q-1) = "+tmp);
		// 取y的绝对值
		long  y = Math.abs(ext[1]);
 		//d = (tmp/y + 1 )*y - tmp;
		d = tmp-y;
		System.err.println("私钥产生成功!  p="+p+" ,q="+q+" ,(N,d) = "+ "(" + N + "," + d + ")"); 		
	}
	
	/**
	 * 扩展欧几里得算法 : gcd(a,b) = d = ax+by 
	 * @param a
	 * @param b
	 * @return (x,y,d)
	 */
	public static long[] extendedEuclid(long a, long b){
		// 用ext数组存储(x,y,d)
		long[] ext = new long[3];
		if(b==0){
			ext[0] = 1;	ext[1] = 0;	ext[2] = a;
			return ext;
		}
		ext = extendedEuclid(b, a%b);
		//递推
		long x = ext[1], y =  ext[0] - (long) Math.floor(a/b) * x, d = ext[2]; 
		ext[0] = x; ext[1] = y; ext[2] = d;
		return ext;
	}
验证结果:


2.6:RSA加密算法

对消息m=25进行加密,生成密文c。
	/**
	 * 2.6:RSA加密算法
	 *  	对消息m=25进行加密,生成密文c: 	c = m^e % N
	 */
	public static long encrypt (long m){		
		// 调用 modexp方法进行加密, 防止 m^e 太大溢出
		return (long) modexp(m, e, N); 
	}
	
	
	/**
	 * 模的指数运算: 将 x^y 的计算转换成多项式的计算
	 * @param x n-bit long x
	 * @param y 指数
	 * @param N 被模的数
	 * @return (x ^ y') mod N
	 */
	public static double modexp(double x, double y, long N){
		if(y == 0){
			return 1;
		}
		/*
		 * 		 |- [ x^(Math.floor(y/2)) ]^2 , 当y为偶数时
		 * x^y = |
		 * 		 |- [ x * x^(Math.floor(y/2)) ]^2 , 当y为奇数时
		 */
		double z = modexp( x, Math.floor(y/2), N);
		if(y%2 == 0){
			return Math.pow(z, 2) % N;
		}else{
			return Math.pow(z, 2)*x % N;
		}
	}


2.7:RSA解密算法

对密文c进行解密。

	/**
	 * 2.7:RSA解密算法
	 * 		对密文c进行解密: m = c^d % N
	 */
	public static long decrypt(long c){
		// 调用 modexp方法进行解密, 防止 c^d 太大溢出
		return (long) modexp(c, d, N); 
	}


测试代码:

	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		System.err.println("请输入原文: ");
		long m = scanner.nextLong();
		System.err.println("******************************************** ");
		
		System.err.println("[产生公钥] 请输入产生素数p和q的位数n(n<=31):");
		int n = scanner.nextInt();
		createPK(n);		
		System.err.println("******************************************** ");
		
		System.err.println("[产生私钥] ");
		createSK();		
		System.err.println("******************************************** ");
		
		//加密
		System.err.println(" --------------加密----------------- ");
		System.err.println("原文: "+m);
		long c = (long) encrypt(m);//密文
		System.err.println("密文: "+c);
		
		//解密
		System.err.println(" --------------解密----------------- ");
		long mm = decrypt(c);
		System.err.println("解密后的原文: "+mm);
		
	}

加密和解密验证结果:

当原文=28,产生素数p和q的位数n=5时,结果正确:



当原文=28,产生素数p和q的位数n=6时,结果错误:



当原文=123,产生素数p和q的位数n=4时,结果正确:



结果正确与否与原文大小和产生素数p和q的位数n有关,这是为什么呢?!继续研究中...


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值