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

来源:互联网 发布:linux get命令 ftp 编辑:程序博客网 时间:2024/06/05 17:53

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

对称加密的例子如另一篇文章中的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)解密了你的数据的人,有可能篡改你的数据。
因此,这个过程是有风险的。你的数据保密性和真实完整性得不到保障。
所以,一般情况下,如果你要保障你的数据保密性不会用这样的形式来。而是用公钥加密,私钥解密。
那如果要保证通讯双方数据的保密性,你要怎么做?

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

数字签名

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

签名

首先,你要签名。
签名,首先你要准备几个参数:你拥有的私钥,这个是代表这数据是你的;待签名的数据;
/** * 数字签名 * @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加解密和数字签名的介绍。
在这个过程中,有几个要注意的地方:

注意点

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

3 0