非对称加解密——RSA加密、解密以及数字签名

对称与非对称加解密,最主要区别在于:对称加密,加解密的密钥是一致的;非对称加密,加解密的密钥是不一致的;

对称加密的例子如另一篇文章中的DES加解密、3DES加解密。

这里要介绍的是非对称加解密中,应用最广泛的一种:RSA。

RSA简介

RSA的由来,你可以简单的百度到,它是由三位大神在1978年提出的一种高安全性的算法。

具体可看百度百科:RSA

在使用中,主要有三个步骤:RSA公私密钥对生成、加密、解密。

RSA中涉及概念的明晰

1、RSA公私密钥对,是哪里来的?
2、RSA比较容易让初接触者搞混的是,它是怎么加密、怎么解密,为什么要这么干?
3、RSA的数字签名是几个意思?
4、数字签名和RSA加解密要一起用吗?
我们一一来解答下这几个问题:
1)、RSA的公私密钥对,可以由一个类生成:KeyPairGenerator。看字面意思就知道这是密钥对生成的意思。具体如何生成,后续介绍;
2)、RSA的加密、解密:RSA有两种形式的加解密。
          No.1:假如有待加密数据Data,那么可以用公钥加密Data,得到加密后的数据encryptData。这个时候,可以用私钥解密这个encryptData;
          No.2:假如有待加密数据Data,那么可以用私钥加密Data,得到加密后的数据encryptData。这个时候,可以用公钥解密这个encryptData;
3)、RSA的数字签名,简单来说,就是私钥加密、公钥解密的一个过程。数字签名,包含签名、验证。你用私钥加密,这就是个签名过程,你用公钥解密,就是个验证过程。
         数字签名的作用,是保证数据的真实性和完整性。
4)、数字签名和RSA的加解密不一定要一起使用,它们实际是独立的。

RSA加解密实例解析

对于上面提出的RSA的问题和解答,我们接着用实例来加以说明。

RSA公私密钥对的生成

/**
	 * 生成公私密钥对
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static void generateKeys() throws NoSuchAlgorithmException, IOException {
		 /** RSA算法要求有一个可信任的随机数源 */
		SecureRandom secureRandom=new SecureRandom();
		 /** 为RSA算法创建一个KeyPairGenerator对象 */
		KeyPairGenerator keyPairGenerator=KeyPairGenerator.getInstance(TAG);
		/** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
		keyPairGenerator.initialize(1024, secureRandom);
		/** 生成密匙对 */
		KeyPair keyPair=keyPairGenerator.generateKeyPair();
		/** 得到公钥 */
		java.security.Key publicKey=keyPair.getPublic();
		/** 得到私钥 */
		java.security.Key privateKey=keyPair.getPrivate();
		
		//对象流形式写入公钥
		ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream(PUBLCIKEY));
	    outputStream.writeObject(publicKey);
	    outputStream.flush();
	    outputStream.close();
	    //对象流形式写入私钥
	    ObjectOutputStream outputStream1=new ObjectOutputStream(new FileOutputStream(PRIVATEKEY));
	    outputStream1.writeObject(privateKey);
	    outputStream1.flush();
	    outputStream1.close();
	}
以上函数,每一行基本都添加了说明。
(1)用KeyPairGenerator生成一个公私密钥对(使用时,需要获取一个对象,这里的TAG=RSA);
(2)生成的过程中我们需要一个随机数源、初始化时需要一个大小限制(这里设置为1024,可在512-65536之间浮动,希望数字没记错);
(3)密钥对生成后,我们用对象流形式保存公私密钥对。为什么用对象流形式保存,因为后续使用起来,你会感觉更方便。

RSA公钥加密,私钥解密

这是最常用到的模式。为什么这么使用,因为在RSA的密钥分配中,你是将私钥自己保留着,而将公钥公开。私钥具有唯一性,只有自己知道。公钥是广泛分布的,可以认为大家都知道的。
当你想让其他人给你发送一条加密数据的时候,你首先把公钥给他,他用公钥加密数据,并把公钥加密后的数据发送给你。你这就可以用私钥来解密了。而如果其他人拿到他发送的数据,是没有办法解密的,因为私钥只在你手中。
这就保证了数据不会被外人解析。
具体的实现代码:
/**
	 * 公钥加密
	 * @param str  待加密数据
	 * @return        加密后的数据
	 * @throws ClassNotFoundException
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] encrypt(String str) throws ClassNotFoundException, IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
	{
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PUBLCIKEY));
		java.security.Key publicKey2= (java.security.Key)objectInputStream.readObject();
		objectInputStream.close();
		 /** 得到Cipher对象来实现对源数据的RSA加密 */
		Cipher cipher=Cipher.getInstance(TAG);
		cipher.init(Cipher.ENCRYPT_MODE, publicKey2);
		

		byte[] encryptedData=str.getBytes();
		int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段加密  doFinal
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedDatas = out.toByteArray();
        out.close();
        
		return encryptedDatas;
	}
	
	/**
	 * 私钥解密
	 * @param encryString 公钥加密后的数据
	 * @return                    解密后数据
	 * @throws FileNotFoundException
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] decrypt(byte[] encryString) throws FileNotFoundException, IOException, ClassNotFoundException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException
	{
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PRIVATEKEY));
		java.security.Key privatekey= (java.security.Key)objectInputStream.readObject();
		objectInputStream.close();
		
		Cipher cipher=Cipher.getInstance(TAG);
		cipher.init(Cipher.DECRYPT_MODE, privatekey);
		
		
		byte[] decryptedData=encryString;
		int inputLen = decryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(decryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(decryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedDatas = out.toByteArray();
        out.close();
		
	    return decryptedDatas;
	}

这是代码中的公钥加密、私钥解密的写法。
其实,仔细看这两段代码,你可以看到:
(1)它们的重点都在Cipher这个类,所不同的是加密和解密的初始化是不一样的。ENCRYPT_MODE,加密;DECRYPT_MODE,解密;
(2)加解密的重点可以归纳为:Cipher获取对象(Cipher.getInstance(TAG))、Cipher初始化(cipher.init)、Cipher加解密(cipher.doFinal);
(3)为什么我要对数据进行分段加解密:因为当你加密的字符串超过117或者解密数据超过128时,会出现错误,如: Data must not be longer than 128 bytes 。
通过以上的操作,你其实已经可以做一个测试实验了。生成一对公私密钥对,然后用公钥加密,私钥解密。你会发现,结果如你所想。

RSA私钥加密,公钥解密

我们通过公钥加密,私钥解密,已经实现了。那么,我们反过来用吗,用私钥加密,公钥解密?
答案是可以的。
模式和以前类似,我们贴两段代码来see see:
/**
	 * 获取私钥加密后的数据
	 * @param data   待加密数据
	 * @return  私钥加密后的数据
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public static byte[] encryptByPrivateKey(String data) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException
	{
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PRIVATEKEY));
		java.security.Key privatekey= (java.security.Key)objectInputStream.readObject();
		objectInputStream.close();
		 /** 得到Cipher对象来实现对源数据的RSA加密 */
		Cipher cipher=Cipher.getInstance(TAG);
		cipher.init(Cipher.ENCRYPT_MODE, privatekey);
		

		byte[] encryptedData=data.getBytes();
		int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密  doFinal
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptedDatas = out.toByteArray();
        out.close();
        
		return encryptedDatas;
	}
	
	/**
	 * 用公钥解密数据
	 * @param encryptData  加密后的数据
	 * @return                      公钥解密后的数据
	 * @throws NoSuchPaddingException 
	 * @throws NoSuchAlgorithmException 
	 * @throws IOException 
	 * @throws InvalidKeyException 
	 * @throws ClassNotFoundException 
	 * @throws BadPaddingException 
	 * @throws IllegalBlockSizeException 
	 */
	public static byte[] decryptByPublicKey(byte[] encryptedData) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException
	{
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PUBLCIKEY));
		java.security.Key publicKey2= (java.security.Key)objectInputStream.readObject();
		objectInputStream.close();
		
		Cipher cipher=Cipher.getInstance(TAG);
		cipher.init(Cipher.DECRYPT_MODE, publicKey2);
		
		int inputLen = encryptedData.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密  doFinal
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] decryptedDatas = out.toByteArray();
        out.close();
        
		return decryptedDatas;
		
	}
	
你会发现,私钥加密、公钥解密的写法和上面的公钥加密、私钥解密没什么大的区别,只是换了个顺序而已。你也可以解密出正确的数据。
那这就出现了两个担忧:
(1)公钥是公开的!你用私钥加密后,发出的数据,如果有保密性要求,这么做是无法保密的,所有知道公钥的人,都可以解密你的数据;
(2)解密了你的数据的人,有可能篡改你的数据。
因此,这个过程是有风险的。你的数据保密性和真实完整性得不到保障。
所以,一般情况下,如果你要保障你的数据保密性不会用这样的形式来。而是用公钥加密,私钥解密。
那如果要保证通讯双方数据的保密性,你要怎么做?
非对称加解密——RSA加密、解密以及数字签名_第1张图片
那这个私钥加密、公钥解密是用来干嘛的呢?数据的真实性、完整性如何得到保障呢?
这就需要后面要说的数字签名来解决和应用了。

数字签名

数字签名,目的其实与传统签名意义相差不大。它主要是为了证明,数据的真实性以及完整性。在传统签名中,你在一个刷卡单上签字,说明这个是得到你授权的真实交易。数字签名,是在数据的角度做了这个操作,保障数据是真实的。通过数据签名的验证,可以保障数据是没有被篡改和真实性。
那如何做这个数字签名呢?
我们分两个步骤来:

签名

首先,你要签名。
签名,首先你要准备几个参数:你拥有的私钥,这个是代表这数据是你的;待签名的数据;
/**
	 * 数字签名
	 * @param str  加密过的数据
	 * @return       私钥对信息生成数字签名
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws SignatureException
	 * @throws InvalidKeyException
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 * @throws ClassNotFoundException 
	 */
	public static byte[] sign(byte[] encryptData) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException, FileNotFoundException, IOException, ClassNotFoundException
	{
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PRIVATEKEY));
		PrivateKey privatekey3= (PrivateKey)objectInputStream.readObject();
		objectInputStream.close();
		/*
		PKCS8EncodedKeySpec pkcs8EncodedKeySpec=new PKCS8EncodedKeySpec(privatekey2.getEncoded());
		KeyFactory keyFactory=KeyFactory.getInstance(TAG);
		PrivateKey pKey=keyFactory.generatePrivate(pkcs8EncodedKeySpec);
		*/
		Signature signature=Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privatekey3);
		signature.update(encryptData);
		byte[] result= signature.sign();
		return result;
	}
代码分为两块:获取到私钥,用私钥初始化,加载想要签名的数据,最后返回签名数据。
那要如何验证呢?

验证

验证,你也需要提供几个参数:公钥、想要验证签名的数据、签名数据
我们也先看代码:
/**
	 * 数字签名正确性验证
	 * @param encryptData  加密后的数据
	 * @param sign              数字签名
	 * @param publicKey      公钥
	 * @return                      数字签名验签结果(true or false)
	 * @throws SignatureException
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws InvalidKeyException
	 * @throws IOException 
	 * @throws FileNotFoundException 
	 * @throws ClassNotFoundException 
	 */
	public static boolean verifySign(byte[] encryptData,byte[] sign) throws SignatureException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException, FileNotFoundException, IOException, ClassNotFoundException
	{
		
		ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PUBLCIKEY));
		PublicKey publicKey2= (PublicKey)objectInputStream.readObject();
		objectInputStream.close();
		/*
		X509EncodedKeySpec keySpec=new X509EncodedKeySpec(publicKey2.getEncoded());
		KeyFactory keyFactory=KeyFactory.getInstance(TAG);
		PublicKey publicKey3= keyFactory.generatePublic(keySpec);
		*/
		Signature signature=Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicKey2);
		signature.update(encryptData);
		return signature.verify(sign);
	}
这里代码也分为两块:获取到公钥、签名验证。
签名验证中,首先你用公钥初始化验证的类,然后加载想要验证的数据,最后用verify方法(参数是签名数据)来验证结果。这里返回的是true或者false;

运行效果

我们调用以上的方法,看看实际运行效果:
 String baseString="你是RSA吗?";
         System.out.println("原始字符串为:"+baseString);
         
		generateKeys();
        System.out.println("公私秘钥对生成成功...");

        System.out.println("公钥加密,私钥解密流程开始...");
		byte[] encryptByte=encrypt(baseString);
		System.out.println("加密后的密文为:"+new String( encryptByte));
        String decryptString=new String(decrypt(encryptByte));
		System.out.println("解密后的明文为:"+decryptString);
		System.out.println("公钥加密,私钥解密流程结束...\r\n");
		
		 System.out.println("私钥加密,公钥解密流程开始...");
		 byte[] privateEncryptData=encryptByPrivateKey(baseString);
		 System.out.println("私钥加密后的数据为:"+new String(privateEncryptData));
		 byte[] publicDecryptData=decryptByPublicKey(privateEncryptData);
		 System.out.println("公钥解密-(私钥加密后的数据)为:"+new String(publicDecryptData));
		 System.out.println("私钥加密,公钥解密流程结束...\r\n");


		 System.out.println("数字签名流程开始...");
		 byte[] signresult= sign(privateEncryptData);
		 System.out.println("数字签名为:"+new String(signresult));
         boolean temp=verifySign(privateEncryptData, signresult);
         System.out.println("数字签名验签结果为:"+temp);
         System.out.println("数字签名流程结束...");

运行效果:
非对称加解密——RSA加密、解密以及数字签名_第2张图片


以上,就是文章主要讲的RSA加解密和数字签名的介绍。
在这个过程中,有几个要注意的地方:

注意点

1、Data must not be longer than 128 bytes 。这个就是上面提到的,加解密的长度限制问题。在做加解密时,要控制好待加密、待解密数据长度的控制,不要超过范围。用分段法解决该问题。

2、Data must start with zero。这个是你在byte[]型转String型,然后再转回byte[]型过程中,可能出现的编码问题。最好的办法是,只在显示的时候用String型,其他时候,数据传输等,用byte[]型即可。


源码

这里附上源码地址:

http://download.csdn.net/detail/yangzhaomuma/9387355

你可能感兴趣的:(Java,加解密,Android技巧)