打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
非对称加解密——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公私密钥对的生成

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

RSA公钥加密,私钥解密

这是最常用到的模式。为什么这么使用,因为在RSA的密钥分配中,你是将私钥自己保留着,而将公钥公开。私钥具有唯一性,只有自己知道。公钥是广泛分布的,可以认为大家都知道的。
当你想让其他人给你发送一条加密数据的时候,你首先把公钥给他,他用公钥加密数据,并把公钥加密后的数据发送给你。你这就可以用私钥来解密了。而如果其他人拿到他发送的数据,是没有办法解密的,因为私钥只在你手中。
这就保证了数据不会被外人解析。
具体的实现代码:
  1. /**  
  2.      * 公钥加密  
  3.      * @param str  待加密数据  
  4.      * @return        加密后的数据  
  5.      * @throws ClassNotFoundException  
  6.      * @throws IOException  
  7.      * @throws NoSuchAlgorithmException  
  8.      * @throws NoSuchPaddingException  
  9.      * @throws InvalidKeyException  
  10.      * @throws IllegalBlockSizeException  
  11.      * @throws BadPaddingException  
  12.      */  
  13.     public static byte[] encrypt(String str) throws ClassNotFoundException, IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException  
  14.     {  
  15.         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PUBLCIKEY));  
  16.         java.security.Key publicKey2= (java.security.Key)objectInputStream.readObject();  
  17.         objectInputStream.close();  
  18.          /** 得到Cipher对象来实现对源数据的RSA加密 */  
  19.         Cipher cipher=Cipher.getInstance(TAG);  
  20.         cipher.init(Cipher.ENCRYPT_MODE, publicKey2);  
  21.           
  22.   
  23.         byte[] encryptedData=str.getBytes();  
  24.         int inputLen = encryptedData.length;  
  25.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  26.         int offSet = 0;  
  27.         byte[] cache;  
  28.         int i = 0;  
  29.         // 对数据分段加密  doFinal  
  30.         while (inputLen - offSet > 0) {  
  31.             if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {  
  32.                 cache = cipher.doFinal(encryptedData, offSet, MAX_ENCRYPT_BLOCK);  
  33.             } else {  
  34.                 cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);  
  35.             }  
  36.             out.write(cache, 0, cache.length);  
  37.             i++;  
  38.             offSet = i * MAX_ENCRYPT_BLOCK;  
  39.         }  
  40.         byte[] encryptedDatas = out.toByteArray();  
  41.         out.close();  
  42.           
  43.         return encryptedDatas;  
  44.     }  
  45.       
  46.     /**  
  47.      * 私钥解密  
  48.      * @param encryString 公钥加密后的数据  
  49.      * @return                    解密后数据  
  50.      * @throws FileNotFoundException  
  51.      * @throws IOException  
  52.      * @throws ClassNotFoundException  
  53.      * @throws InvalidKeyException  
  54.      * @throws NoSuchAlgorithmException  
  55.      * @throws NoSuchPaddingException  
  56.      * @throws IllegalBlockSizeException  
  57.      * @throws BadPaddingException  
  58.      */  
  59.     public static byte[] decrypt(byte[] encryString) throws FileNotFoundException, IOException, ClassNotFoundException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException  
  60.     {  
  61.         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PRIVATEKEY));  
  62.         java.security.Key privatekey= (java.security.Key)objectInputStream.readObject();  
  63.         objectInputStream.close();  
  64.           
  65.         Cipher cipher=Cipher.getInstance(TAG);  
  66.         cipher.init(Cipher.DECRYPT_MODE, privatekey);  
  67.           
  68.           
  69.         byte[] decryptedData=encryString;  
  70.         int inputLen = decryptedData.length;  
  71.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  72.         int offSet = 0;  
  73.         byte[] cache;  
  74.         int i = 0;  
  75.         // 对数据分段解密  
  76.         while (inputLen - offSet > 0) {  
  77.             if (inputLen - offSet > MAX_DECRYPT_BLOCK) {  
  78.                 cache = cipher.doFinal(decryptedData, offSet, MAX_DECRYPT_BLOCK);  
  79.             } else {  
  80.                 cache = cipher.doFinal(decryptedData, offSet, inputLen - offSet);  
  81.             }  
  82.             out.write(cache, 0, cache.length);  
  83.             i++;  
  84.             offSet = i * MAX_DECRYPT_BLOCK;  
  85.         }  
  86.         byte[] decryptedDatas = out.toByteArray();  
  87.         out.close();  
  88.           
  89.         return decryptedDatas;  
  90.     }  

这是代码中的公钥加密、私钥解密的写法。
其实,仔细看这两段代码,你可以看到:
(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:
  1. /**  
  2.      * 获取私钥加密后的数据  
  3.      * @param data   待加密数据  
  4.      * @return  私钥加密后的数据  
  5.      * @throws IOException  
  6.      * @throws ClassNotFoundException  
  7.      * @throws NoSuchAlgorithmException  
  8.      * @throws NoSuchPaddingException  
  9.      * @throws InvalidKeyException  
  10.      * @throws IllegalBlockSizeException  
  11.      * @throws BadPaddingException  
  12.      */  
  13.     public static byte[] encryptByPrivateKey(String data) throws IOException, ClassNotFoundException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException  
  14.     {  
  15.         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PRIVATEKEY));  
  16.         java.security.Key privatekey= (java.security.Key)objectInputStream.readObject();  
  17.         objectInputStream.close();  
  18.          /** 得到Cipher对象来实现对源数据的RSA加密 */  
  19.         Cipher cipher=Cipher.getInstance(TAG);  
  20.         cipher.init(Cipher.ENCRYPT_MODE, privatekey);  
  21.           
  22.   
  23.         byte[] encryptedData=data.getBytes();  
  24.         int inputLen = encryptedData.length;  
  25.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  26.         int offSet = 0;  
  27.         byte[] cache;  
  28.         int i = 0;  
  29.         // 对数据分段解密  doFinal  
  30.         while (inputLen - offSet > 0) {  
  31.             if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {  
  32.                 cache = cipher.doFinal(encryptedData, offSet, MAX_ENCRYPT_BLOCK);  
  33.             } else {  
  34.                 cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);  
  35.             }  
  36.             out.write(cache, 0, cache.length);  
  37.             i++;  
  38.             offSet = i * MAX_ENCRYPT_BLOCK;  
  39.         }  
  40.         byte[] encryptedDatas = out.toByteArray();  
  41.         out.close();  
  42.           
  43.         return encryptedDatas;  
  44.     }  
  45.       
  46.     /**  
  47.      * 用公钥解密数据  
  48.      * @param encryptData  加密后的数据  
  49.      * @return                      公钥解密后的数据  
  50.      * @throws NoSuchPaddingException   
  51.      * @throws NoSuchAlgorithmException   
  52.      * @throws IOException   
  53.      * @throws InvalidKeyException   
  54.      * @throws ClassNotFoundException   
  55.      * @throws BadPaddingException   
  56.      * @throws IllegalBlockSizeException   
  57.      */  
  58.     public static byte[] decryptByPublicKey(byte[] encryptedData) throws NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidKeyException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException  
  59.     {  
  60.         ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(PUBLCIKEY));  
  61.         java.security.Key publicKey2= (java.security.Key)objectInputStream.readObject();  
  62.         objectInputStream.close();  
  63.           
  64.         Cipher cipher=Cipher.getInstance(TAG);  
  65.         cipher.init(Cipher.DECRYPT_MODE, publicKey2);  
  66.           
  67.         int inputLen = encryptedData.length;  
  68.         ByteArrayOutputStream out = new ByteArrayOutputStream();  
  69.         int offSet = 0;  
  70.         byte[] cache;  
  71.         int i = 0;  
  72.         // 对数据分段解密  doFinal  
  73.         while (inputLen - offSet > 0) {  
  74.             if (inputLen - offSet > MAX_DECRYPT_BLOCK) {  
  75.                 cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);  
  76.             } else {  
  77.                 cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);  
  78.             }  
  79.             out.write(cache, 0, cache.length);  
  80.             i++;  
  81.             offSet = i * MAX_DECRYPT_BLOCK;  
  82.         }  
  83.         byte[] decryptedDatas = out.toByteArray();  
  84.         out.close();  
  85.           
  86.         return decryptedDatas;  
  87.           
  88.     }  
  89.       
你会发现,私钥加密、公钥解密的写法和上面的公钥加密、私钥解密没什么大的区别,只是换了个顺序而已。你也可以解密出正确的数据。
那这就出现了两个担忧:
(1)公钥是公开的!你用私钥加密后,发出的数据,如果有保密性要求,这么做是无法保密的,所有知道公钥的人,都可以解密你的数据;
(2)解密了你的数据的人,有可能篡改你的数据。
因此,这个过程是有风险的。你的数据保密性和真实完整性得不到保障。
所以,一般情况下,如果你要保障你的数据保密性不会用这样的形式来。而是用公钥加密,私钥解密。
那如果要保证通讯双方数据的保密性,你要怎么做?

那这个私钥加密、公钥解密是用来干嘛的呢?数据的真实性、完整性如何得到保障呢?
这就需要后面要说的数字签名来解决和应用了。

数字签名

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

签名

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

验证

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

运行效果

我们调用以上的方法,看看实际运行效果:
  1. String baseString="你是RSA吗?";  
  2.         System.out.println("原始字符串为:"+baseString);  
  3.           
  4.     generateKeys();  
  5.        System.out.println("公私秘钥对生成成功...");  
  6.   
  7.        System.out.println("公钥加密,私钥解密流程开始...");  
  8.     byte[] encryptByte=encrypt(baseString);  
  9.     System.out.println("加密后的密文为:"+new String( encryptByte));  
  10.        String decryptString=new String(decrypt(encryptByte));  
  11.     System.out.println("解密后的明文为:"+decryptString);  
  12.     System.out.println("公钥加密,私钥解密流程结束...\r\n");  
  13.       
  14.      System.out.println("私钥加密,公钥解密流程开始...");  
  15.      byte[] privateEncryptData=encryptByPrivateKey(baseString);  
  16.      System.out.println("私钥加密后的数据为:"+new String(privateEncryptData));  
  17.      byte[] publicDecryptData=decryptByPublicKey(privateEncryptData);  
  18.      System.out.println("公钥解密-(私钥加密后的数据)为:"+new String(publicDecryptData));  
  19.      System.out.println("私钥加密,公钥解密流程结束...\r\n");  
  20.   
  21.   
  22.      System.out.println("数字签名流程开始...");  
  23.      byte[] signresult= sign(privateEncryptData);  
  24.      System.out.println("数字签名为:"+new String(signresult));  
  25.         boolean temp=verifySign(privateEncryptData, signresult);  
  26.         System.out.println("数字签名验签结果为:"+temp);  
  27.         System.out.println("数字签名流程结束...");  

运行效果:



以上,就是文章主要讲的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

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
iOS中使用RSA对数据进行加密解密
Android传输数据时加密详解
RSA加密解密及数字签名Java实现
非对称加密 (公钥加密私钥解密或者公钥解密私钥加密)
常用加密算法的Java实现总结(二)
Java中常用的加密方法(JDK)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服