Java中不依赖于第三方库使用OpenSSL生成的RSA公私钥进行数据加解密

来源:互联网 发布:软件无线通信 芯片 编辑:程序博客网 时间:2024/05/01 13:50

本文出处:http://blog.csdn.net/chaijunkun/article/details/7275632,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。


RSA是什么:RSA公钥加密算法是1977年由Ron Rivest、Adi Shamirh和LenAdleman在(美国麻省理工学院)开发的。RSA取名来自开发他们三者的名字。RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的所有密码攻击,已被ISO推荐为公钥数据加密标准。目前该加密方式广泛用于网上银行、数字签名等场合。RSA算法基于一个十分简单的数论事实:将两个大素数相乘十分容易,但那时想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。

OpenSSL是什么::众多的密码算法、公钥基础设施标准以及SSL协议,或许这些有趣的功能会让你产生实现所有这些算法和标准的想法。果真如此,在对你表示敬佩的同时,还是忍不住提醒你:这是一个令人望而生畏的过程。这个工作不再是简单的读懂几本密码学专著和协议文档那么简单,而是要理解所有这些算法、标准和协议文档的每一个细节,并用你可能很熟悉的C语言字符一个一个去实现这些定义和过程。我们不知道你将需要多少时间来完成这项有趣而可怕的工作,但肯定不是一年两年的问题。OpenSSL就是由Eric A. Young和Tim J. Hudson两位绝世大好人自1995年就开始编写的集合众多安全算法的算法集合。通过命令或者开发库,我们可以轻松实现标准的公开算法应用。


我的一个假设应用背景:

随着移动互联网的普及,为移动设备开发的应用也层出不穷。这些应用往往伴随着用户注册与密码验证的功能。”网络传输“、”应用程序日志访问“中的安全性都存在着隐患。密码作为用户的敏感数据,特别需要开发者在应用上线之前做好安全防范。处理不当,可能会造成诸如商业竞争对手的恶意攻击、第三方合作商的诉讼等问题。


RSA算法虽然有这么多好处,但是在网上找不到一个完整的例子来说明如何操作。下面我就来介绍一下,我们的目标是——不用第三方的jar包实现RSA加解密(不是没有蛀牙):

一、使用OpenSSL来生成私钥和公钥

我使用的是Linux系统,已经安装了OpenSSL软件包,此时请验证你的机器上已经安装了OpenSSL,运行命令应当出现如下信息:

[plain] view plaincopy
  1. [root@chaijunkun ~]# openssl version -a  
  2. OpenSSL 1.0.0-fips 29 Mar 2010  
  3. built on: Wed Jan 25 02:17:15 GMT 2012  
  4. platform: linux-x86_64  
  5. options:  bn(64,64) md2(int) rc4(16x,int) des(idx,cisc,16,int) blowfish(idx)   
  6. compiler: gcc -fPIC -DOPENSSL_PIC -DZLIB -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -DKRB5_MIT -m64 -DL_ENDIAN -DTERMIO -Wall -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -Wa,--noexecstack -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DWHIRLPOOL_ASM  
  7. OPENSSLDIR: "/etc/pki/tls"  
  8. engines:  aesni dynamic   

先来生成私钥:

[plain] view plaincopy
  1. [root@chaijunkun ~]# openssl genrsa -out rsa_private_key.pem 1024  
  2. Generating RSA private key, 1024 bit long modulus  
  3. .......................++++++  
  4. ..++++++  
  5. e is 65537 (0x10001)  
这条命令让openssl随机生成了一份私钥,加密长度是1024位。加密长度是指理论上最大允许”被加密的信息“长度的限制,也就是明文的长度限制。随着这个参数的增大(比方说2048),允许的明文长度也会增加,但同时也会造成计算复杂度的极速增长。一般推荐的长度就是1024位(128字节)。

我们来看一下私钥的内容:

[plain] view plaincopy
  1. [root@chaijunkun ~]# cat rsa_private_key.pem   
  2. -----BEGIN RSA PRIVATE KEY-----  
  3. MIICWwIBAAKBgQChDzcjw/rWgFwnxunbKp7/4e8w/UmXx2jk6qEEn69t6N2R1i/L  
  4. mcyDT1xr/T2AHGOiXNQ5V8W4iCaaeNawi7aJaRhtVx1uOH/2U378fscEESEG8XDq  
  5. ll0GCfB1/TjKI2aitVSzXOtRs8kYgGU78f7VmDNgXIlk3gdhnzh+uoEQywIDAQAB  
  6. AoGAaeKk76CSsp7k90mwyWP18GhLZru+vEhfT9BpV67cGLg1owFbntFYQSPVsTFm  
  7. U2lWn5HD/IcV+EGaj4fOLXdM43Kt4wyznoABSZCKKxs6uRciu8nQaFNUy4xVeOfX  
  8. PHU2TE7vi4LDkw9df1fya+DScSLnaDAUN3OHB5jqGL+Ls5ECQQDUfuxXN3uqGYKk  
  9. znrKj0j6pY27HRfROMeHgxbjnnApCQ71SzjqAM77R3wIlKfh935OIV0aQC4jQRB4  
  10. iHYSLl9lAkEAwgh4jxxXeIAufMsgjOi3qpJqGvumKX0W96McpCwV3Fsew7W1/msi  
  11. suTkJp5BBvjFvFwfMAHYlJdP7W+nEBWkbwJAYbz/eB5NAzA4pxVR5VmCd8cuKaJ4  
  12. EgPLwsjI/mkhrb484xZ2VyuICIwYwNmfXpA3yDgQWsKqdgy3Rrl9lV8/AQJAcjLi  
  13. IfigUr++nJxA8C4Xy0CZSoBJ76k710wdE1MPGr5WgQF1t+P+bCPjVAdYZm4Mkyv0  
  14. /yBXBD16QVixjvnt6QJABli6Zx9GYRWnu6AKpDAHd8QjWOnnNfNLQHue4WepEvkm  
  15. CysG+IBs2GgsXNtrzLWJLFx7VHmpqNTTC8yNmX1KFw==  
  16. -----END RSA PRIVATE KEY-----  
内容都是标准的ASCII字符,开头一行和结尾一行有明显的标记,真正的私钥数据是中间的不规则字符。

接下来根据私钥生成公钥:

[plain] view plaincopy
  1. [root@chaijunkun ~]# openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout  
  2. writing RSA key  
再来看一下公钥的内容:
[plain] view plaincopy
  1. [root@chaijunkun ~]# cat rsa_public_ley.pem   
  2. -----BEGIN PUBLIC KEY-----  
  3. MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChDzcjw/rWgFwnxunbKp7/4e8w  
  4. /UmXx2jk6qEEn69t6N2R1i/LmcyDT1xr/T2AHGOiXNQ5V8W4iCaaeNawi7aJaRht  
  5. Vx1uOH/2U378fscEESEG8XDqll0GCfB1/TjKI2aitVSzXOtRs8kYgGU78f7VmDNg  
  6. XIlk3gdhnzh+uoEQywIDAQAB  
  7. -----END PUBLIC KEY-----  
这时候的私钥还不能直接被使用,需要进行PKCS#8编码
[plain] view plaincopy
  1. [root@chaijunkun ~]# openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt  
命令中指明了输入私钥文件为rsa_private_key.pem,输出私钥文件为pkcs8_rsa_private_key.pem,不采用任何二次加密(-nocrypt)

再来看一下,编码后的私钥文件是不是和之前的私钥文件不同了:

[plain] view plaincopy
  1. [root@chaijunkun ~]# cat pkcs8_rsa_private_key.pem   
  2. -----BEGIN PRIVATE KEY-----  
  3. MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKEPNyPD+taAXCfG  
  4. 6dsqnv/h7zD9SZfHaOTqoQSfr23o3ZHWL8uZzINPXGv9PYAcY6Jc1DlXxbiIJpp4  
  5. 1rCLtolpGG1XHW44f/ZTfvx+xwQRIQbxcOqWXQYJ8HX9OMojZqK1VLNc61GzyRiA  
  6. ZTvx/tWYM2BciWTeB2GfOH66gRDLAgMBAAECgYBp4qTvoJKynuT3SbDJY/XwaEtm  
  7. u768SF9P0GlXrtwYuDWjAVue0VhBI9WxMWZTaVafkcP8hxX4QZqPh84td0zjcq3j  
  8. DLOegAFJkIorGzq5FyK7ydBoU1TLjFV459c8dTZMTu+LgsOTD11/V/Jr4NJxIudo  
  9. MBQ3c4cHmOoYv4uzkQJBANR+7Fc3e6oZgqTOesqPSPqljbsdF9E4x4eDFuOecCkJ  
  10. DvVLOOoAzvtHfAiUp+H3fk4hXRpALiNBEHiIdhIuX2UCQQDCCHiPHFd4gC58yyCM  
  11. 6Leqkmoa+6YpfRb3oxykLBXcWx7DtbX+ayKy5OQmnkEG+MW8XB8wAdiUl0/tb6cQ  
  12. FaRvAkBhvP94Hk0DMDinFVHlWYJ3xy4pongSA8vCyMj+aSGtvjzjFnZXK4gIjBjA  
  13. 2Z9ekDfIOBBawqp2DLdGuX2VXz8BAkByMuIh+KBSv76cnEDwLhfLQJlKgEnvqTvX  
  14. TB0TUw8avlaBAXW34/5sI+NUB1hmbgyTK/T/IFcEPXpBWLGO+e3pAkAGWLpnH0Zh  
  15. Fae7oAqkMAd3xCNY6ec180tAe57hZ6kS+SYLKwb4gGzYaCxc22vMtYksXHtUeamo  
  16. 1NMLzI2ZfUoX  
  17. -----END PRIVATE KEY-----  
至此,可用的密钥对已经生成好了,私钥使用pkcs8_rsa_private_key.pem,公钥采用rsa_public_key.pem。

二、编写Java代码实际测试

[java] view plaincopy
  1. package net.csdn.blog.chaijunkun;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileNotFoundException;  
  6. import java.io.FileReader;  
  7. import java.io.IOException;  
  8. import java.security.InvalidKeyException;  
  9. import java.security.KeyFactory;  
  10. import java.security.KeyPair;  
  11. import java.security.KeyPairGenerator;  
  12. import java.security.NoSuchAlgorithmException;  
  13. import java.security.SecureRandom;  
  14. import java.security.interfaces.RSAPrivateKey;  
  15. import java.security.interfaces.RSAPublicKey;  
  16. import java.security.spec.InvalidKeySpecException;  
  17. import java.security.spec.PKCS8EncodedKeySpec;  
  18. import java.security.spec.X509EncodedKeySpec;  
  19.   
  20. import javax.crypto.BadPaddingException;  
  21. import javax.crypto.Cipher;  
  22. import javax.crypto.IllegalBlockSizeException;  
  23. import javax.crypto.NoSuchPaddingException;  
  24.   
  25.   
  26. import sun.misc.BASE64Decoder;  
  27.   
  28. public class RSAEncrypt {  
  29.   
  30.     /** 
  31.      * 私钥 
  32.      */  
  33.     private RSAPrivateKey privateKey;  
  34.   
  35.     /** 
  36.      * 公钥 
  37.      */  
  38.     private RSAPublicKey publicKey;  
  39.   
  40.     /** 
  41.      * 获取私钥 
  42.      * @return 当前的私钥对象 
  43.      */  
  44.     public RSAPrivateKey getPrivateKey() {  
  45.         return privateKey;  
  46.     }  
  47.   
  48.     /** 
  49.      * 获取公钥 
  50.      * @return 当前的公钥对象 
  51.      */  
  52.     public RSAPublicKey getPublicKey() {  
  53.         return publicKey;  
  54.     }  
  55.   
  56.     /** 
  57.      * 随机生成密钥对 
  58.      */  
  59.     public void genKeyPair(){  
  60.         KeyPairGenerator keyPairGen= null;  
  61.         try {  
  62.             keyPairGen= KeyPairGenerator.getInstance("RSA");  
  63.         } catch (NoSuchAlgorithmException e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.         keyPairGen.initialize(1024new SecureRandom());  
  67.         KeyPair keyPair= keyPairGen.generateKeyPair();  
  68.         this.privateKey= (RSAPrivateKey) keyPair.getPrivate();  
  69.         this.publicKey= (RSAPublicKey) keyPair.getPublic();  
  70.     }  
  71.   
  72.     /** 
  73.      * 从文件中加载公钥 
  74.      * @param keyFileName 公钥文件名 
  75.      * @return 是否成功 
  76.      * @throws Exception  
  77.      */  
  78.     public void loadPublicKey(String keyFileName) throws Exception{  
  79.         try {  
  80.             File keyFile= new File(keyFileName);  
  81.             BufferedReader br= new BufferedReader(new FileReader(keyFile));  
  82.             String readLine= null;  
  83.             StringBuilder sb= new StringBuilder();  
  84.             while((readLine= br.readLine())!=null){  
  85.                 if(readLine.charAt(0)=='-'){  
  86.                     continue;  
  87.                 }else{  
  88.                     sb.append(readLine);  
  89.                     sb.append('\r');  
  90.                 }  
  91.             }  
  92.             BASE64Decoder base64Decoder= new BASE64Decoder();  
  93.             byte[] buffer= base64Decoder.decodeBuffer(sb.toString());  
  94.             KeyFactory keyFactory= KeyFactory.getInstance("RSA");  
  95.             X509EncodedKeySpec keySpec= new X509EncodedKeySpec(buffer);  
  96.             this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);  
  97.         } catch (FileNotFoundException e) {  
  98.             throw new Exception("公钥文件未找到");  
  99.         } catch (IOException e) {  
  100.             throw new Exception("公钥文件读取错误");  
  101.         } catch (NoSuchAlgorithmException e) {  
  102.             throw new Exception("无此算法");  
  103.         } catch (InvalidKeySpecException e) {  
  104.             throw new Exception("非法公钥");  
  105.         }  
  106.     }  
  107.   
  108.     /** 
  109.      * 从文件中加载私钥 
  110.      * @param keyFileName 私钥文件名 
  111.      * @return 是否成功 
  112.      * @throws Exception  
  113.      */  
  114.     public void loadPrivateKey(String keyFileName) throws Exception{  
  115.         try {  
  116.             File keyFile= new File(keyFileName);  
  117.             BufferedReader br= new BufferedReader(new FileReader(keyFile));  
  118.             String readLine= null;  
  119.             StringBuilder sb= new StringBuilder();  
  120.             while((readLine= br.readLine())!=null){  
  121.                 if(readLine.charAt(0)=='-'){  
  122.                     continue;  
  123.                 }else{  
  124.                     sb.append(readLine);  
  125.                     sb.append('\r');  
  126.                 }  
  127.             }  
  128.             BASE64Decoder base64Decoder= new BASE64Decoder();  
  129.             byte[] buffer= base64Decoder.decodeBuffer(sb.toString());  
  130.             PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(buffer);  
  131.             KeyFactory keyFactory= KeyFactory.getInstance("RSA");  
  132.             this.privateKey= (RSAPrivateKey) keyFactory.generatePrivate(keySpec);  
  133.         } catch (FileNotFoundException e) {  
  134.             throw new Exception("私钥文件未找到");  
  135.         } catch (IOException e) {  
  136.             throw new Exception("私钥文件读取错误");  
  137.         } catch (NoSuchAlgorithmException e) {  
  138.             throw new Exception("无此算法");  
  139.         } catch (InvalidKeySpecException e) {  
  140.             throw new Exception("非法私钥");  
  141.         }  
  142.     }  
  143.   
  144.     /** 
  145.      * 加密过程 
  146.      * @param publicKey 公钥 
  147.      * @param plainTextData 明文数据 
  148.      * @return 
  149.      * @throws Exception 加密过程中的异常信息 
  150.      */  
  151.     public byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception{  
  152.         if(publicKey== null){  
  153.             throw new Exception("加密公钥为空, 请设置");  
  154.         }  
  155.         Cipher cipher= null;  
  156.         try {  
  157.             cipher= Cipher.getInstance("RSA");  
  158.             cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
  159.             byte[] output= cipher.doFinal(plainTextData);  
  160.             return output;  
  161.         } catch (NoSuchAlgorithmException e) {  
  162.             throw new Exception("无此加密算法");  
  163.         } catch (NoSuchPaddingException e) {  
  164.             e.printStackTrace();  
  165.             return null;  
  166.         }catch (InvalidKeyException e) {  
  167.             throw new Exception("加密公钥非法,请检查");  
  168.         } catch (IllegalBlockSizeException e) {  
  169.             throw new Exception("明文长度非法");  
  170.         } catch (BadPaddingException e) {  
  171.             throw new Exception("明文数据已损坏");  
  172.         }  
  173.     }  
  174.   
  175.     /** 
  176.      * 解密过程 
  177.      * @param privateKey 私钥 
  178.      * @param cipherData 密文数据 
  179.      * @return 明文 
  180.      * @throws Exception 解密过程中的异常信息 
  181.      */  
  182.     public byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception{  
  183.         if (privateKey== null){  
  184.             throw new Exception("解密私钥为空, 请设置");  
  185.         }  
  186.         Cipher cipher= null;  
  187.         try {  
  188.             cipher= Cipher.getInstance("RSA");  
  189.             cipher.init(Cipher.DECRYPT_MODE, privateKey);  
  190.             byte[] output= cipher.doFinal(cipherData);  
  191.             return output;  
  192.         } catch (NoSuchAlgorithmException e) {  
  193.             throw new Exception("无此解密算法");  
  194.         } catch (NoSuchPaddingException e) {  
  195.             e.printStackTrace();  
  196.             return null;  
  197.         }catch (InvalidKeyException e) {  
  198.             throw new Exception("解密私钥非法,请检查");  
  199.         } catch (IllegalBlockSizeException e) {  
  200.             throw new Exception("密文长度非法");  
  201.         } catch (BadPaddingException e) {  
  202.             throw new Exception("密文数据已损坏");  
  203.         }         
  204.     }  
  205.   
  206.     /** 
  207.      * 字节数据转字符串专用集合 
  208.      */  
  209.     private static char[] HEX_CHAR= {'0''1''2''3''4''5''6''7''8''9''a''b''c''d''e''f'};  
  210.   
  211.     /** 
  212.      * 字节数据转十六进制字符串 
  213.      * @param data 输入数据 
  214.      * @return 十六进制内容 
  215.      */  
  216.     private String byteArrayToString(byte[] data){  
  217.         StringBuilder stringBuilder= new StringBuilder();  
  218.         for (int i=0; i<data.length; i++){  
  219.             //取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移  
  220.             stringBuilder.append(HEX_CHAR[(data[i] & 0xf0)>>> 4]);  
  221.             //取出字节的低四位 作为索引得到相应的十六进制标识符  
  222.             stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);  
  223.             if (i<data.length-1){  
  224.                 stringBuilder.append(' ');  
  225.             }  
  226.         }  
  227.         return stringBuilder.toString();  
  228.     }  
  229.   
  230.     public static void main(String[] args){  
  231.         RSAEncrypt rsaEncrypt= new RSAEncrypt();  
  232.         //rsaEncrypt.genKeyPair();  
  233.   
  234.         //加载公钥  
  235.         try {  
  236.             rsaEncrypt.loadPublicKey("rsa_public_key.pem");  
  237.             System.out.println("加载公钥成功");  
  238.         } catch (Exception e) {  
  239.             System.err.println(e.getMessage());  
  240.             System.err.println("加载公钥失败");  
  241.         }  
  242.   
  243.         //加载私钥  
  244.         try {  
  245.             rsaEncrypt.loadPrivateKey("pkcs8_rsa_private_key.pem");  
  246.             System.out.println("加载私钥成功");  
  247.         } catch (Exception e) {  
  248.             System.err.println(e.getMessage());  
  249.             System.err.println("加载私钥失败");  
  250.         }  
  251.   
  252.         //测试字符串  
  253.         String encryptStr= "Test String chaijunkun";  
  254.   
  255.         try {  
  256.             //加密  
  257.             byte[] cipher = rsaEncrypt.encrypt(rsaEncrypt.getPublicKey(), encryptStr.getBytes());  
  258.             //解密  
  259.             byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);  
  260.             System.out.println("密文长度:"+ cipher.length);  
  261.             System.out.println(rsaEncrypt.byteArrayToString(cipher));  
  262.             System.out.println("明文长度:"+ plainText.length);  
  263.             System.out.println(rsaEncrypt.byteArrayToString(plainText));  
  264.             System.out.println(new String(plainText));  
  265.         } catch (Exception e) {  
  266.             System.err.println(e.getMessage());  
  267.         }  
  268.     }  
  269. }  
运行上面的代码,会显示如下信息:
[plain] view plaincopy
  1. 加载公钥成功  
  2. 加载私钥成功  
  3. 密文长度:128  
  4. 14 39 1a 05 df b8 3d 05 cc 11 9b b4 26 ca df db 26 45 86 ac 98 33 af b9 65 ec 77 5a d4 a8 49 94 65 71 33 88 71 f5 e1 f3 6e 3a 92 8d c7 18 50 89 88 5e a2 53 86 99 e8 ec 80 ce 10 03 13 f4 aa ed 77 cf 7d 9a 69 51 5c e3 f2 4d ed b9 d4 db 2a a4 5e 2b f0 5a 28 26 66 4f 00 de 03 fb 61 fb df 18 3f fc fb 51 d5 22 e5 e5 f2 fc f5 ef 73 10 c8 54 f6 90 5d 2d 7f ac ff c3 c8 52 44 40 3c a2 6d ad  
  5. 明文长度:22  
  6. 54 65 73 74 20 53 74 72 69 6e 67 20 63 68 61 69 6a 75 6e 6b 75 6e  
  7. Test String chaijunkun  

在main函数中我注释掉了”rsaEncrypt.genKeyPair()“,这个方法是用来随机生成密钥对的(只生成、使用,不存储)。当不使用文件密钥时,可以将载入密钥的代码注释,启用本方法,也可以跑通代码。

加载公钥与加载私钥的不同点在于公钥加载时使用的是X509EncodedKeySpec(X509编码的Key指令),私钥加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。


2012年2月22日补充:在android软件开发的过程中,发现上述代码不能正常工作,主要原因在于sun.misc.BASE64Decoder类在android开发包中不存在。因此需要特别在网上寻找rt.jar的源代码,至于JDK的src.zip中的源代码,这个只是JDK中的部分源代码,上述的几个类的代码都没有。经过寻找并添加,上述代码在android应用中能够很好地工作。其中就包含这个类的对应代码。另外此类还依赖于CEFormatException、CEStreamExhausted、CharacterDecoder和CharacterEncoder类和异常定义。


参考文献:

RSA介绍:http://baike.baidu.com/view/7520.htm

OpenSSL介绍:http://baike.baidu.com/view/300712.htm

密钥对生成:http://www.howforge.com/how-to-generate-key-pair-using-openssl

私钥编码格式转换:http://shuany.iteye.com/blog/730910