java中使用OpenSSL生成的RSA公私钥进行数据加解密


本文出处: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算法虽然有这么多好处,但是在网上找不到一个完整的例子来说明如何操作。下面我就来介绍一下:

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

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

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  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字符,开头一行和结尾一行有明显的标记,真正的私钥数据是中间的不规则字符。

2015年3月24日补充:密钥文件最终将数据通过Base64编码进行存储。可以看到上述密钥文件内容每一行的长度都很规律。这是由于RFC2045中规定:The encoded output stream must be represented in lines of no more than 76 characters each。也就是说Base64编码的数据每行最多不超过76字符,对于超长数据需要按行分割。

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

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. [root@chaijunkun ~]# openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout  
  2. writing RSA key  
再来看一下公钥的内容:

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  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。

2014年5月20日补充:最近又遇到RSA加密的需求了,而且对方要求只能使用第一步生成的未经过PKCS#8编码的私钥文件。后来查看相关文献得知第一步生成的私钥文件编码是PKCS#1格式,这种格式Java其实是支持的,只不过多写两行代码而已:

  1. RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure((ASN1Sequence) ASN1Sequence.fromByteArray(priKeyData));  
  2. RSAPrivateKeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(asn1PrivKey.getModulus(), asn1PrivKey.getPrivateExponent());  
  3. KeyFactory keyFactory= KeyFactory.getInstance("RSA");  
  4. PrivateKey priKey= keyFactory.generatePrivate(rsaPrivKeySpec);  
首先将PKCS#1的私钥文件读取出来(注意去掉减号开头的注释内容),然后使用Base64解码读出的字符串,便得到priKeyData,也就是第一行代码中的参数。最后一行得到了私钥。接下来的用法就没什么区别了。

参考文献:https://community.oracle.com/thread/1529240?start=0&tstart=0


二、编写Java代码实际测试

2012年2月23日补充:在标准JDK中只是规定了JCE(JCE (Java Cryptography Extension) 是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现。它提供对对称、不对称、块和流密码的加密支持,它还支持安全流和密封的对象。)接口,但是内部实现需要自己或者第三方提供。因此我们这里使用bouncycastle的开源的JCE实现包,下载地址:http://bouncycastle.org/latest_releases.html,我使用的是bcprov-jdk16-146.jar,这是在JDK1.6环境下使用的。如果需要其他JDK版本下的实现,可以在之前的下载页面中找到对应版本。

下面来看一下我实现的代码:

  1. package net.csdn.blog.chaijunkun;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.InputStreamReader;  
  7. import java.security.InvalidKeyException;  
  8. import java.security.KeyFactory;  
  9. import java.security.KeyPair;  
  10. import java.security.KeyPairGenerator;  
  11. import java.security.NoSuchAlgorithmException;  
  12. import java.security.SecureRandom;  
  13. import java.security.interfaces.RSAPrivateKey;  
  14. import java.security.interfaces.RSAPublicKey;  
  15. import java.security.spec.InvalidKeySpecException;  
  16. import java.security.spec.PKCS8EncodedKeySpec;  
  17. import java.security.spec.X509EncodedKeySpec;  
  18.   
  19. import javax.crypto.BadPaddingException;  
  20. import javax.crypto.Cipher;  
  21. import javax.crypto.IllegalBlockSizeException;  
  22. import javax.crypto.NoSuchPaddingException;  
  23.   
  24. import org.bouncycastle.jce.provider.BouncyCastleProvider;  
  25.   
  26. import sun.misc.BASE64Decoder;  
  27.   
  28. public class RSAEncrypt {  
  29.       
  30.     private static final String DEFAULT_PUBLIC_KEY=   
  31.         "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQChDzcjw/rWgFwnxunbKp7/4e8w" + "\r" +  
  32.         "/UmXx2jk6qEEn69t6N2R1i/LmcyDT1xr/T2AHGOiXNQ5V8W4iCaaeNawi7aJaRht" + "\r" +  
  33.         "Vx1uOH/2U378fscEESEG8XDqll0GCfB1/TjKI2aitVSzXOtRs8kYgGU78f7VmDNg" + "\r" +  
  34.         "XIlk3gdhnzh+uoEQywIDAQAB" + "\r";  
  35.       
  36.     private static final String DEFAULT_PRIVATE_KEY=  
  37.         "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKEPNyPD+taAXCfG" + "\r" +  
  38.         "6dsqnv/h7zD9SZfHaOTqoQSfr23o3ZHWL8uZzINPXGv9PYAcY6Jc1DlXxbiIJpp4" + "\r" +  
  39.         "1rCLtolpGG1XHW44f/ZTfvx+xwQRIQbxcOqWXQYJ8HX9OMojZqK1VLNc61GzyRiA" + "\r" +  
  40.         "ZTvx/tWYM2BciWTeB2GfOH66gRDLAgMBAAECgYBp4qTvoJKynuT3SbDJY/XwaEtm" + "\r" +  
  41.         "u768SF9P0GlXrtwYuDWjAVue0VhBI9WxMWZTaVafkcP8hxX4QZqPh84td0zjcq3j" + "\r" +  
  42.         "DLOegAFJkIorGzq5FyK7ydBoU1TLjFV459c8dTZMTu+LgsOTD11/V/Jr4NJxIudo" + "\r" +  
  43.         "MBQ3c4cHmOoYv4uzkQJBANR+7Fc3e6oZgqTOesqPSPqljbsdF9E4x4eDFuOecCkJ" + "\r" +  
  44.         "DvVLOOoAzvtHfAiUp+H3fk4hXRpALiNBEHiIdhIuX2UCQQDCCHiPHFd4gC58yyCM" + "\r" +  
  45.         "6Leqkmoa+6YpfRb3oxykLBXcWx7DtbX+ayKy5OQmnkEG+MW8XB8wAdiUl0/tb6cQ" + "\r" +  
  46.         "FaRvAkBhvP94Hk0DMDinFVHlWYJ3xy4pongSA8vCyMj+aSGtvjzjFnZXK4gIjBjA" + "\r" +  
  47.         "2Z9ekDfIOBBawqp2DLdGuX2VXz8BAkByMuIh+KBSv76cnEDwLhfLQJlKgEnvqTvX" + "\r" +  
  48.         "TB0TUw8avlaBAXW34/5sI+NUB1hmbgyTK/T/IFcEPXpBWLGO+e3pAkAGWLpnH0Zh" + "\r" +  
  49.         "Fae7oAqkMAd3xCNY6ec180tAe57hZ6kS+SYLKwb4gGzYaCxc22vMtYksXHtUeamo" + "\r" +  
  50.         "1NMLzI2ZfUoX" + "\r";  
  51.   
  52.     /** 
  53.      * 私钥 
  54.      */  
  55.     private RSAPrivateKey privateKey;  
  56.   
  57.     /** 
  58.      * 公钥 
  59.      */  
  60.     private RSAPublicKey publicKey;  
  61.       
  62.     /** 
  63.      * 字节数据转字符串专用集合 
  64.      */  
  65.     private static final char[] HEX_CHAR= {'0''1''2''3''4''5''6''7''8''9''a''b''c''d''e''f'};  
  66.       
  67.   
  68.     /** 
  69.      * 获取私钥 
  70.      * @return 当前的私钥对象 
  71.      */  
  72.     public RSAPrivateKey getPrivateKey() {  
  73.         return privateKey;  
  74.     }  
  75.   
  76.     /** 
  77.      * 获取公钥 
  78.      * @return 当前的公钥对象 
  79.      */  
  80.     public RSAPublicKey getPublicKey() {  
  81.         return publicKey;  
  82.     }  
  83.   
  84.     /** 
  85.      * 随机生成密钥对 
  86.      */  
  87.     public void genKeyPair(){  
  88.         KeyPairGenerator keyPairGen= null;  
  89.         try {  
  90.             keyPairGen= KeyPairGenerator.getInstance("RSA");  
  91.         } catch (NoSuchAlgorithmException e) {  
  92.             e.printStackTrace();  
  93.         }  
  94.         keyPairGen.initialize(1024new SecureRandom());  
  95.         KeyPair keyPair= keyPairGen.generateKeyPair();  
  96.         this.privateKey= (RSAPrivateKey) keyPair.getPrivate();  
  97.         this.publicKey= (RSAPublicKey) keyPair.getPublic();  
  98.     }  
  99.   
  100.     /** 
  101.      * 从文件中输入流中加载公钥 
  102.      * @param in 公钥输入流 
  103.      * @throws Exception 加载公钥时产生的异常 
  104.      */  
  105.     public void loadPublicKey(InputStream in) throws Exception{  
  106.         try {  
  107.             BufferedReader br= new BufferedReader(new InputStreamReader(in));  
  108.             String readLine= null;  
  109.             StringBuilder sb= new StringBuilder();  
  110.             while((readLine= br.readLine())!=null){  
  111.                 if(readLine.charAt(0)=='-'){  
  112.                     continue;  
  113.                 }else{  
  114.                     sb.append(readLine);  
  115.                     sb.append('\r');  
  116.                 }  
  117.             }  
  118.             loadPublicKey(sb.toString());  
  119.         } catch (IOException e) {  
  120.             throw new Exception("公钥数据流读取错误");  
  121.         } catch (NullPointerException e) {  
  122.             throw new Exception("公钥输入流为空");  
  123.         }  
  124.     }  
  125.   
  126.   
  127.     /** 
  128.      * 从字符串中加载公钥 
  129.      * @param publicKeyStr 公钥数据字符串 
  130.      * @throws Exception 加载公钥时产生的异常 
  131.      */  
  132.     public void loadPublicKey(String publicKeyStr) throws Exception{  
  133.         try {  
  134.             BASE64Decoder base64Decoder= new BASE64Decoder();  
  135.             byte[] buffer= base64Decoder.decodeBuffer(publicKeyStr);  
  136.             KeyFactory keyFactory= KeyFactory.getInstance("RSA");  
  137.             X509EncodedKeySpec keySpec= new X509EncodedKeySpec(buffer);  
  138.             this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);  
  139.         } catch (NoSuchAlgorithmException e) {  
  140.             throw new Exception("无此算法");  
  141.         } catch (InvalidKeySpecException e) {  
  142.             throw new Exception("公钥非法");  
  143.         } catch (IOException e) {  
  144.             throw new Exception("公钥数据内容读取错误");  
  145.         } catch (NullPointerException e) {  
  146.             throw new Exception("公钥数据为空");  
  147.         }  
  148.     }  
  149.   
  150.     /** 
  151.      * 从文件中加载私钥 
  152.      * @param keyFileName 私钥文件名 
  153.      * @return 是否成功 
  154.      * @throws Exception  
  155.      */  
  156.     public void loadPrivateKey(InputStream in) throws Exception{  
  157.         try {  
  158.             BufferedReader br= new BufferedReader(new InputStreamReader(in));  
  159.             String readLine= null;  
  160.             StringBuilder sb= new StringBuilder();  
  161.             while((readLine= br.readLine())!=null){  
  162.                 if(readLine.charAt(0)=='-'){  
  163.                     continue;  
  164.                 }else{  
  165.                     sb.append(readLine);  
  166.                     sb.append('\r');  
  167.                 }  
  168.             }  
  169.             loadPrivateKey(sb.toString());  
  170.         } catch (IOException e) {  
  171.             throw new Exception("私钥数据读取错误");  
  172.         } catch (NullPointerException e) {  
  173.             throw new Exception("私钥输入流为空");  
  174.         }  
  175.     }  
  176.   
  177.     public void loadPrivateKey(String privateKeyStr) throws Exception{  
  178.         try {  
  179.             BASE64Decoder base64Decoder= new BASE64Decoder();  
  180.             byte[] buffer= base64Decoder.decodeBuffer(privateKeyStr);  
  181.             PKCS8EncodedKeySpec keySpec= new PKCS8EncodedKeySpec(buffer);  
  182.             KeyFactory keyFactory= KeyFactory.getInstance("RSA");  
  183.             this.privateKey= (RSAPrivateKey) keyFactory.generatePrivate(keySpec);  
  184.         } catch (NoSuchAlgorithmException e) {  
  185.             throw new Exception("无此算法");  
  186.         } catch (InvalidKeySpecException e) {  
  187.             throw new Exception("私钥非法");  
  188.         } catch (IOException e) {  
  189.             throw new Exception("私钥数据内容读取错误");  
  190.         } catch (NullPointerException e) {  
  191.             throw new Exception("私钥数据为空");  
  192.         }  
  193.     }  
  194.   
  195.     /** 
  196.      * 加密过程 
  197.      * @param publicKey 公钥 
  198.      * @param plainTextData 明文数据 
  199.      * @return 
  200.      * @throws Exception 加密过程中的异常信息 
  201.      */  
  202.     public byte[] encrypt(RSAPublicKey publicKey, byte[] plainTextData) throws Exception{  
  203.         if(publicKey== null){  
  204.             throw new Exception("加密公钥为空, 请设置");  
  205.         }  
  206.         Cipher cipher= null;  
  207.         try {  
  208.             cipher= Cipher.getInstance("RSA"new BouncyCastleProvider());  
  209.             cipher.init(Cipher.ENCRYPT_MODE, publicKey);  
  210.             byte[] output= cipher.doFinal(plainTextData);  
  211.             return output;  
  212.         } catch (NoSuchAlgorithmException e) {  
  213.             throw new Exception("无此加密算法");  
  214.         } catch (NoSuchPaddingException e) {  
  215.             e.printStackTrace();  
  216.             return null;  
  217.         }catch (InvalidKeyException e) {  
  218.             throw new Exception("加密公钥非法,请检查");  
  219.         } catch (IllegalBlockSizeException e) {  
  220.             throw new Exception("明文长度非法");  
  221.         } catch (BadPaddingException e) {  
  222.             throw new Exception("明文数据已损坏");  
  223.         }  
  224.     }  
  225.   
  226.     /** 
  227.      * 解密过程 
  228.      * @param privateKey 私钥 
  229.      * @param cipherData 密文数据 
  230.      * @return 明文 
  231.      * @throws Exception 解密过程中的异常信息 
  232.      */  
  233.     public byte[] decrypt(RSAPrivateKey privateKey, byte[] cipherData) throws Exception{  
  234.         if (privateKey== null){  
  235.             throw new Exception("解密私钥为空, 请设置");  
  236.         }  
  237.         Cipher cipher= null;  
  238.         try {  
  239.             cipher= Cipher.getInstance("RSA"new BouncyCastleProvider());  
  240.             cipher.init(Cipher.DECRYPT_MODE, privateKey);  
  241.             byte[] output= cipher.doFinal(cipherData);  
  242.             return output;  
  243.         } catch (NoSuchAlgorithmException e) {  
  244.             throw new Exception("无此解密算法");  
  245.         } catch (NoSuchPaddingException e) {  
  246.             e.printStackTrace();  
  247.             return null;  
  248.         }catch (InvalidKeyException e) {  
  249.             throw new Exception("解密私钥非法,请检查");  
  250.         } catch (IllegalBlockSizeException e) {  
  251.             throw new Exception("密文长度非法");  
  252.         } catch (BadPaddingException e) {  
  253.             throw new Exception("密文数据已损坏");  
  254.         }         
  255.     }  
  256.   
  257.       
  258.     /** 
  259.      * 字节数据转十六进制字符串 
  260.      * @param data 输入数据 
  261.      * @return 十六进制内容 
  262.      */  
  263.     public static String byteArrayToString(byte[] data){  
  264.         StringBuilder stringBuilder= new StringBuilder();  
  265.         for (int i=0; i<data.length; i++){  
  266.             //取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移  
  267.             stringBuilder.append(HEX_CHAR[(data[i] & 0xf0)>>> 4]);  
  268.             //取出字节的低四位 作为索引得到相应的十六进制标识符  
  269.             stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]);  
  270.             if (i<data.length-1){  
  271.                 stringBuilder.append(' ');  
  272.             }  
  273.         }  
  274.         return stringBuilder.toString();  
  275.     }  
  276.   
  277.   
  278.     public static void main(String[] args){  
  279.         RSAEncrypt rsaEncrypt= new RSAEncrypt();  
  280.         //rsaEncrypt.genKeyPair();  
  281.   
  282.         //加载公钥  
  283.         try {  
  284.             rsaEncrypt.loadPublicKey(RSAEncrypt.DEFAULT_PUBLIC_KEY);  
  285.             System.out.println("加载公钥成功");  
  286.         } catch (Exception e) {  
  287.             System.err.println(e.getMessage());  
  288.             System.err.println("加载公钥失败");  
  289.         }  
  290.   
  291.         //加载私钥  
  292.         try {  
  293.             rsaEncrypt.loadPrivateKey(RSAEncrypt.DEFAULT_PRIVATE_KEY);  
  294.             System.out.println("加载私钥成功");  
  295.         } catch (Exception e) {  
  296.             System.err.println(e.getMessage());  
  297.             System.err.println("加载私钥失败");  
  298.         }  
  299.   
  300.         //测试字符串  
  301.         String encryptStr= "Test String chaijunkun";  
  302.   
  303.         try {  
  304.             //加密  
  305.             byte[] cipher = rsaEncrypt.encrypt(rsaEncrypt.getPublicKey(), encryptStr.getBytes());  
  306.             //解密  
  307.             byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);  
  308.             System.out.println("密文长度:"+ cipher.length);  
  309.             System.out.println(RSAEncrypt.byteArrayToString(cipher));  
  310.             System.out.println("明文长度:"+ plainText.length);  
  311.             System.out.println(RSAEncrypt.byteArrayToString(plainText));  
  312.             System.out.println(new String(plainText));  
  313.         } catch (Exception e) {  
  314.             System.err.println(e.getMessage());  
  315.         }  
  316.     }  
  317. }  

代码中我提供了两种加载公钥和私钥的方式。

按流来读取:适合在android应用中按ID索引资源得到InputStream的方式;

按字符串来读取:就像代码中展示的那样,将密钥内容按行存储到静态常量中,按String类型导入密钥。


运行上面的代码,会显示如下信息:

[plain] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. 加载公钥成功  
  2. 加载私钥成功  
  3. 密文长度:128  
  4. 35 b4 6f 49 69 ae a3 85 a2 a5 0d 45 75 00 23 23 e6 70 69 b4 59 ae 72 6f 6d d3 43 e1 d3 44 85 eb 04 57 2c 46 3e 70 09 4d e6 4c 83 50 c7 56 75 80 c7 e1 31 64 57 c8 e3 46 a7 ce 57 31 ac cd 21 89 89 8f c1 24 c1 22 0c cb 70 6a 0d fa c9 38 80 ba 2e e1 29 02 ed 45 9e 88 e9 23 09 87 af ad ab ac cb 61 03 3c a1 81 56 a5 de c4 79 aa 3e 48 ee 30 3d bc 5b 47 50 75 9f fd 22 87 9e de b1 f4 e8 b2  
  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类和异常定义。


2012年2月23日补充:起初,我写这篇文章是想不依赖于任何第三方包来实现RSA的加密与解密,然而后续遇到了问题。由于在加密方法encrypt和解密方法decrypt中都要建立一个Cipher对象,这个对象只能通过getInstance来获取实例。它有两种:第一个是只指定算法,不指定提供者Provider的;第二个是两个都要指定的。起初没有指定,代码依然能够跑通,但是你会发现,每次加密的结果都不一样。后来分析才知道Cipher对象使用的公私钥是内部自己随机生成的,不是代码中指定的公私钥。奇怪的是,这种不指定Provider的代码能够在android应用中跑通,而且每次加密的结果都相同。我想,android的SDK中除了系统的一些开发函数外,自己也实现了JDK的功能,可能在它自己的JDK中已经提供了相应的Provider,才使得每次加密结果相同。当我像网上的示例代码那样加入了bouncycastle的Provider后,果然每次加密的结果都相同了。


参考文献:

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

JCE介绍:http://baike.baidu.com/view/1855103.htm

版权声明:本文为博主原创文章,未经博主允许不得转载。

16
1
猜你在找
JDBC视频教程
Cocos2d-Lua手游开发基础篇
Java Swing、JDBC开发桌面级应用
R语言知识体系概览
.NET平台和C#编程从入门到精通
src="http://blog.csdn.net/common/ad.html?t=4&containerId=ad_cen&frmId=ad_frm_0" style="border-width: 0px; overflow: hidden; width: 988px; height: 90px;" scrolling="no" id="ad_frm_0" frameborder="0">
查看评论
25楼 sinat_30047989 2015-07-23 09:37发表 [回复]
我这边用您的代码在android端把加密后的数据用Base64编码成字符串后经过网络传递到java端后,java端先用Base64解密,然后再用私钥解密会报java.lang.Exception: 密文数据已损坏的错误,楼主知道怎么处理吗?
Re: JackCousins 5天前 18:43发表 [回复]
回复sinat_30047989:首先能否调通各自的加解密?例如android自己和服务器自己能否实现加解密?其次再看下服务器端读取密文的长度。如果您采用的是2048位加密,数据分片应为256字节。多了或者少了都会抛出数据损坏异常
24楼 landerson 2015-07-20 17:47发表 [回复]
老大,有整过python用私匙加密,然后在安卓里用公匙解密不?
搞了老大天,解出来都是乱码。已经是有PKCS1
Re: JackCousins 5天前 18:40发表 [回复]
回复landerson:Python我不熟悉,您说的我没有做过
23楼 陨落烟雨 2015-07-08 11:00发表 [回复]
class "org.bouncycastle.asn1.ASN1ObjectIdentifier"'s signer information does not match signer information of other classes in the same package
楼主请问这个是什么导致的!
Re: JackCousins 2015-07-09 08:43发表 [回复]
回复AniuLincion:根据提示应该是类org.bouncycastle.asn1.ASN1ObjectIdentifier签名错误的问题。尝试重新下载bouncycastle的Provider包或者查看pom配置中是否还引入了其它版本的包
22楼 天莫邪 2015-03-12 17:00发表 [回复]
正在做RSA,C#服务器和android交互加密解密。谢谢楼主分享。
21楼 playboykpk 2015-02-13 22:50发表 [回复]
大神你好,请教一下,不知道为什么我的公钥给安卓客户端可以使用,但是给IOS客户端就无法加密,网上好像有说是IOS使用的编码格式不一样,但是一直找不到解决的办法,不知道你对这块有没有研究,谢谢!
Re: JackCousins 2015-02-16 18:08发表 [回复]
回复playboykpk:对IOS的API不了解,不过查询的几个关键词无非就是PKCS#1,RSA之类的。翻墙谷歌找找吧,Sorry,没能帮到你
20楼 AwayNeverComeBack 2014-11-11 16:52发表 [回复]
大哥您好,我用java产生的钥匙给IOS端加密,返回后我用私钥解乱码,去掉解密方法中的new BouncyCastleProvider()后会出现密文以损坏,求解,谢谢大哥
Re: JackCousins 2014-11-12 10:01发表 [回复]
回复AwayNeverComeBack:在解密时第一句话Cipher.getInstance("RSA", ....中,第一个参数用来指定算法,你可以试试将参数改为RSA/BCB/PKCS1Padding,看能否解密成功
Re: AwayNeverComeBack 2014-11-12 10:57发表 [回复]
回复chaijunkun:谢谢大哥,但是还是不行,改成NONE,或者ECB都不行,密文数据以损坏,IOS端和这边匹配,昨天实验只有一对钥匙好用,除了那一对就都不行。。。麻烦大哥了
19楼 zhangke19891001 2014-10-31 15:02发表 [回复]
大哥你好,最近遇到了一个RSA加密的问题,我做android客户端,服务器要求客户端发送密码时密码用RSA加密并且RSA_PKCS1_PADDING填充,并将加密的结果base64编码,我试了好多方法都不行,从服务器返回的数据一直是解密错误,麻烦问一下这是怎么回事,我菜鸟新手,谢谢大哥了
Re: JackCousins 2014-10-31 20:15发表 [回复]
回复zhangke19891001:PKCS#1编码的私钥加解密需要原始数据的对齐字节值为11。举个例子,假设密钥长度是2048位,即256字节,由于使用PKCS#1,因此每次加密数据的最大长度为256-11=245字节,加密后数据长度仍然为256字节。不知道这个问题你注意到了没
Re: zhangke19891001 2014-11-03 10:17发表 [回复]
回复chaijunkun:大哥,有没有可能是服务器端和客户端的base64编码不一样?
Re: JackCousins 2014-11-03 14:53发表 [回复]
回复zhangke19891001:base64是公开算法,只要原始数据一样,编码后的结果也是一样的。首先你确定服务器端是用Java写的吗?如果是,用的base64是apache的codec组件吗?如果是你公司自己开发的服务器和客户端,可以跟后端组协商,首先后端能否完成加密和解密双向操作,如果可以,你申请把公钥和私钥都拿到,在安卓设备上自己做加解密,如果你这边也能双向操作再进行联调。另外注意引用bouncy castle的安全包
Re: zhangke19891001 2014-11-03 15:54发表 [回复]
回复chaijunkun:大哥,我们服务器用的是C++做得,公钥和私钥我都能拿到,请问如果我在android这边可以实现加密和解密,请问如何联调,谢谢大哥
Re: JackCousins 2014-11-04 10:01发表 [回复]
回复zhangke19891001:这样的话先约定一个明文数据(例如:abc123)让服务端记录下加密后的密文原始十六进制和Base64编码之后的数据,然后你将Base64编码后的数据解码,看结果和密文十六进制数据是否一致。如果一致就说明Base64没有问题。然后用相同的密钥在你那边加密同样的明文数据,看十六进制是否一致。将你生成的密文交给服务器,看对方是否能解开。反正要一点点定位问题。
Re: zhangke19891001 2014-11-03 09:57发表 [回复]
回复chaijunkun:大哥您好,我的公钥长度是1024,您上面文章写的这几行代码和我这个问题有关吗,
RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure((ASN1Sequence)

ASN1Sequence.fromByteArray(priKeyData));
RSAPrivateKeySpec rsaPrivKeySpec = new RSAPrivateKeySpec(asn1PrivKey.getModulus(),
asn1PrivKey.getPrivateExponent());
还有,能不能请大哥帮我看看我的这个问题,我都困在这个问题好几天了,实在没办法了,谢谢大哥了
Re: zhangke19891001 2014-11-04 15:23发表 [回复]
回复zhangke19891001:大哥,服务器的私钥是C++用openssl生成的,格式是您上面文章写的未经PKCS#8编码的那种格式,也就是您说的PKCS#1格式,而且是RSA_PKCS1_PADDING填充, 那我客户端java应该如何加密才能让服务器顺利解密?
18楼 天地一蟲 2014-09-17 17:30发表 [回复]
楼主您好!阅读了您的文章,受益匪浅,但是我在做签名过程中,出现了如下错误:DERApplicationSpecific cannot be cast to ASN1Sequence
不知您有没有遇到过同样的情况,我初步怀疑是版本的问题,不知道您处理pkcs#1格式的私钥时,用的是什么版本的jar文件?
我使用的是jdk1.7和bcprov-jdk16-1.46.jar,jdk1.6也使用过,但是没法解决这个问题。望楼主指定一二,十分感谢!
Re: JackCousins 2014-09-18 09:45发表 [回复]
回复charlie555:你是不是做类型强转了才会抛这个异常呀?建议你弄清楚密钥是怎么生成到,如果是用openssl命令生成的,要知道生成的编码格式是哪一种。
Re: 天地一蟲 2014-09-18 10:00发表 [回复]
回复chaijunkun:使用RSAPrivateKeyStructure asn1PrivKey = new RSAPrivateKeyStructure((ASN1Sequence) ASN1Sequence.fromByteArray(privateKey)); 转换的时候抛出的异常!密钥是由C++端下发的,我手上只有一个密钥串,C++端也是使用OpenSSL生成的公私钥。同样的,我用C++提供的公钥验签就没有问题!很不解!
Re: JackCousins 2014-09-19 09:42发表 [回复]
回复charlie555:用C++公钥验签?你提供的代码是加载私钥的呀,是不是私钥的格式不是PKCS#1的?
17楼 RockoZZ 2014-08-23 11:27发表 [回复]
顶一个,我想问下博主以下的另外两种获取密钥的的方法一般在哪些场景使用,刚学RSA加密不是太懂,thanks
/*通过公钥byte[](publicKey.getEncoded())将公钥还原*/
//获取公钥
public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException,
InvalidKeySpecException
{
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
//获取私钥
.....
/*使用N、e值还原公钥,使用N、d值还原私钥*/
//使用N、e值还原公钥
囧 代码过多发不出。。。
//使用N、d值还原私钥
......
Re: JackCousins 2014-08-25 09:38发表 [回复]
回复BBLD_:首先要了解RSA加解密的理论基础,至少知道公式。公式中n为明文,c为密文。
加密:
n^e = c (mod N)
解密:
c^d = n (mod N)
(N,e)是公钥,(N,d)是私钥。N是两个很大的质数乘积。也就是说常规RSA是通过公钥加密、私钥解密的(其实用私钥尝试加密也是可以的,但是主要用于签名,公钥尝试去解密的过程实际上就是验证签名)
换句话说,如果你拥有了N、e、d这三个参数就可以实现加解密。你说的第一种方法实际上是通过一种标准格式的数据(在什么位置,多大长度,保存了哪个参数都是规定好的)来让程序自动提取这些参数。而第二种方式就更为直接了,直接设置N、e、d。所以从本质上来说是没什么不同的。不过生产环境中推荐使用第一种。
16楼 Jonix 2014-08-20 08:41发表 [回复]
使用openssl生成的密钥,用C++ with openssl 库生成的密文,自己可解,用你的代码java端解不了。
但java生成的密文,c++可解。

原因不明 ...
Re: JackCousins 2014-08-21 09:25发表 [回复]
回复Jonix:由于公私钥都能加密和解密,请具体说一下应用场景,C++用哪个密钥加密的,另外Java端用什么加密的?
Re: Jonix 2014-08-21 12:00发表 [回复]
回复chaijunkun:密钥全总是用 openssl 生成的。
C++用公钥加密,java用私钥解密。
不过反过来也试过,C++用私钥加,java用公钥解,同样不行。
但C++自己加解是可以的。
Java自己加解也是可以的。
另外:网上另一篇:
http://www.cnblogs.com/fengfeng/archive/2013/08/22/3274676.html
这个文章我原样下来,也是不行的。
初步怀疑是Java的rsa算法可能也有版本之分,然后它的版本比较旧。
Re: JackCousins 2014-08-21 13:14发表 [回复]
回复Jonix:用OpenSSL生成私钥要注意密钥的编码制式,在加载私钥的时候要严格按照编码制式来(例如PKCS#8、PKCS#1)。另外在文中你搜索“cipher= Cipher.getInstance("RSA", new BouncyCastleProvider());”,尝试使用BouncyCastleProvider和不使用两种情况下哪个可以跨语言加解密。之前开发过一个和Flash播放器之间用RSA加解密的项目,我这边用了这个Provider反而对方解不了,不用就好了。
Re: Jonix 2014-08-21 13:52发表 [回复]
回复chaijunkun:这个类不认识,请问怎么引入?

我是搞C++的,对java很不了解,而且我在linux下用的。
另外忘提了,我用的是openJDK 1.7.0.55

麻烦了,如果有类似的博文也可以,谢谢
Re: JackCousins 2014-08-21 15:33发表 [回复]
回复Jonix:如果Java项目使用maven,增加如下依赖就可以了
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
如果没有,去官网下载。具体使用方式在本文中搜索BouncyCastleProvider
Re: Jonix 2014-08-21 16:03发表 [回复]
回复chaijunkun:搞定了,非常感谢。
这个类一加上就OK了。
15楼 qq_17761359 2014-07-16 21:14发表 [回复]
请教一个问题:
openssl生成的密钥对,给我的java端一个base64的公钥,我用java代码做了RSA+DES方式进行加密(数据用DES加密,DES_KEY 用PUBLIC_KEY进行加密后传输),传输到php服务端,进行解密(会同时传输MD5(A|B|C|D)生成的签名。
因为第一次接触加密问题,请求给服务端的数据,密钥校验失败。
Re: JackCousins 2014-07-17 16:47发表 [回复]
回复qq_17761359:我跟Flash播放器进行RSA联调的时候也出现了这个问题。如果密钥的方式失败的话,尤其是跨语言联调,建议传输公私钥采用原始RSA参数的形式,任何支持RSA加解密的语言都会提供设置:n【公私钥都有的】,d【公钥包含的】和e【私钥包含的】这三个参数。
14楼 飘逝的落叶纷飞 2014-07-11 17:14发表 [回复]
有个问题想问楼主,由X509EncodedKeySpec生成公钥,其参数pubKeyText是怎样获得的呢?格式又是怎样的呢?楼主能不能给个示例。
楼主有没有试过由C# RSACryptoServiceProvider类产生公钥字符串,然后由X509EncodedKeySpec生成java类型的RSA公钥,再对数据进行加密。
Re: JackCousins 2014-07-12 22:55发表 [回复]
回复dslinmy:C#没研究过
Re: JackCousins 2014-07-12 22:54发表 [回复]
回复dslinmy:你说的这种逆过来的操作我没有研究过,如果你有兴趣可以把RSA中的各个参数提取出来(n【公私钥都有的】,d【公钥包含的】和e【私钥包含的】),转换为十六进制看一下。
Re: 飘逝的落叶纷飞 2014-07-13 08:12发表 [回复]
回复chaijunkun:正在尝试中...
13楼 Ringo胡 2014-07-10 17:51发表 [回复]
楼主你好,想请问你关于
"这时候的私钥还不能直接被使用,需要进行PKCS#8编码:
[root@chaijunkun ~]# openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt
".
我在做支付宝的接口,已经有一个不能使用的私钥,有什么方法在java中直接把这个私钥转成PKCS8格式的私钥呢?
两年了,,,不知道你还有没有在留意这个文章...
Re: JackCousins 2014-07-10 20:57发表 [回复]
回复hu85730336:一直在更新自己的文章,你说的问题我已经补充了,请在正文中查找“2014年5月20日补充”
Re: Ringo胡 2014-07-11 09:03发表 [回复]
回复chaijunkun:多谢楼主,,,之前没看懂这段,,,原来RSA加密就是把PKCS1转成PKCS8,多谢!
Re: JackCousins 2014-07-11 09:26发表 [回复]
回复hu85730336:额。。。RSA不是“把PKCS1转成PKCS8”,那段描述只是说可以采用不同的私钥格式:)
12楼 keaixiaozhu7688 2014-06-30 14:55发表 [回复]
大神请问下单独做解密是不是只需要调用decrypt方法就行了,main方法里 byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);
这个cipher可以直接用客户端提交上来的密文.getbytes()吗?谢谢
Re: JackCousins 2014-06-30 17:20发表 [回复]
回复keaixiaozhu7688:是的,解密直接调用decrypt方法就可以了。密文方面我建议你在客户端先做个Base64编码之后再用HTTP提交,收到内容后进行Base64解码再解密。
Re: keaixiaozhu7688 2014-06-30 17:40发表 [回复]
回复chaijunkun:加载私钥成功
密文长度:128
9c 76 55 cd 24 56 65 07 ff cf ca 48 a6 16 2e 46 13 bf c7 86 4f 0e d1 6e eb 40 2b f7 f0 f3 98 64 f0 15 8a b6 7b 9f f0 cb 8f 2c 64 21 4e fe d9 2e b4 35 fd 40 22 d7 4d 39 3b 73 a9 09 7b 62 33 4c a5 bf f7 f9 01 71 6a 18 ae 1a e0 64 f2 90 56 b2 f2 71 c9 bb 99 0e e5 df 28 c7 5e 42 9a 74 03 d8 4f 29 f8 2c db d9 a9 a2 ea 2b f3 3a 0b 15 bf af 1c 8a a0 cf d8 5e 85 4f 63 f1 4d 1c a2 4f 04 4b
明文长度:128
93 5a 64 7a 06 70 d9 21 8b 80 3d 69 10 eb 4f d5 ed a1 93 3e 28 d5 97 1b 0a d3 e3 66 6c 86 40 d3 f6 4c b6 14 36 7f 06 2b e9 47 be 97 8d 27 74 06 38 ae a9 b1 fc 73 99 cb 9b de 60 7b fb 4c 4d c7 67 22 15 8a bd 1b a9 34 6d 15 58 2b d5 60 f3 45 2b 12 77 00 67 ec 77 91 54 ce 24 56 eb 8c 8c ff fd 6c 88 97 27 e6 8a 26 23 5e 38 01 d9 3b 16 3b aa 1f 58 06 61 3c ba e6 c4 92 82 8c c0 7d 80 f7
?Zdzp?!??=i?O枕??>(??
鱼fl?@遇L?6+?G???'t8?┍?s????`{?LM?g"???4mX+?`?E+wg?w?T?$V?????l??'??&#^8?;;?Xa<烘?????}??
Re: keaixiaozhu7688 2014-06-30 17:45发表 [回复]
回复keaixiaozhu7688:服务端代码:
String name = request.getParameter("name");
BASE64Decoder base64Decoder= new BASE64Decoder();
RSAEncrypt rsaEncrypt= new RSAEncrypt();
try {
File file = new File("/Users/stevepan/pkcs8_rsa_private_key.pem");
InputStream in = new FileInputStream(file);
rsaEncrypt.loadPrivateKey(in);
System.out.println("加载私钥成功");
} catch (Exception e) {
System.err.println(e.getMessage());
System.err.println("加载私钥失败");
}
try {
//解密
byte[] cipher = base64Decoder.decodeBuffer(name);
byte[] plainText = rsaEncrypt.decrypt(rsaEncrypt.getPrivateKey(), cipher);
System.out.println("密文长度:"+ cipher.length);
System.out.println(RSAEncrypt.byteArrayToString(cipher));
System.out.println("明文长度:"+ plainText.length);
System.out.println(RSAEncrypt.byteArrayToString(plainText));
System.out.println(new String(plainText));
} catch (Exception e) {
System.err.println(e.getMessage());
}
Re: keaixiaozhu7688 2014-06-30 17:45发表 [回复]
回复keaixiaozhu7688:我是在iOS上用公钥文件public_key.der加密,服务器用pkcs8_rsa_private_key.pem文件解密,客户端提交密文之前做了base64,
Re: JackCousins 2014-07-02 09:40发表 [回复]
回复keaixiaozhu7688:您想表达什么?是解密失败吗?还是加密失败?
11楼 stevenmtk 2014-06-23 19:23发表 [回复]
按照楼主的步骤操作了一遍,受益匪浅。
但是用Java解密openssl用RSA生成的加密文件时,遇到了一个很奇怪的乱码问题,步骤如下:

1. 生成公私钥:
openssl genrsa -out rsa_private_key.pem 2048
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
openssl pkcs8 -topk8 -in rsa_private_key.pem -out pkcs8_rsa_private_key.pem -nocrypt

2. 生成源文件和加密文件:
echo "hello world" > data.txt
openssl rsautl -encrypt -in data.txt -inkey rsa_public_key.pem -pubin -out data.encrypted.txt

3. 在Java中解密该文件(data.encrypted.txt):
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey); // privateKey按楼主的方法得到
result = cipher.doFinal(encryptData);
String text = new String(result, "UTF-8);"

4. 发现问题:
生成的text在最后包含“hello world”字段,但在"hello world"之前还有许多乱码,不知道这些多余的信息是怎样产生的?楼主能否实验并解答一下呢?

非常感谢!

P.S.
1. openssl对源文件进行的签名,在Java中验证是成功的,说明Java中生成的key应该是没有问题的。
Re: JackCousins 2014-06-24 02:26发表 [回复]
回复stevenmtk:刚才做了一下实验,解密得到的字符串最后是明文,前面有很多类似于“���U�>�p2F�”的乱码,因为采用2048位加密,单次理论数据长度可达256字节。你原始明文长度很显然不够256字节,默认情况下,加密命令会在明文前加入填充字符。名词叫做“填充模式”,查阅相关资料,有如下四个参数:-pkcs、 -oaep、 -ssl、 -raw、-x931:采用的填充模式,上述四个值分别代表:PKCS#1.5(缺省值)、 PKCS#1 OAEP、 SSLv2、X931里面特定的填充模式,或者不填充。如果要签名,只有-pkcs和-raw可以使用。怀疑是因为填充引起的。
Re: stevenmtk 2014-06-24 11:41发表 [回复]
回复chaijunkun:感谢回复,您的怀疑是正确的,确实是填充模式导致的影响。原因如下:
Java代码中Cipher.getInstance("RSA");实际得到的为“Cipher.getInstance("RSA/ECB/NoPadding");”无填充模式,所以用来解包含填充的密文,前面会出现乱码。
改为Cipher.getInstance("RSA/ECB/PKCS1Padding");即可解密openssl生成的密文,不再有乱码。

为了验证这个猜想,我做了以下实验:
1. openssl -raw的方式加密的密文,在Java中用Cipher.getInstance("RSA")和Cipher.getInstance("RSA/ECB/NoPadding")都能够正确解密,但用Cipher.getInstance("RSA/ECB/PKCS1Padding")不能正确解密
2. openssl -pkcs(默认)方式加密的密文,在Java中可以用"RSA/ECB/PKCS1Padding"正确解密,而"RSA"或"RSA/ECB/NoPadding"的方式解密,明文前面都会出现乱码。
3. 剩余的-oaep、 -ssl、 -x931几种方式还没有做尝试
10楼 mlg_wing 2014-06-23 15:02发表 [回复]
你好,有没有IOS客户端的RSA加密解密demo,和服务端对应的,第一个搞RSA很迷茫
Re: JackCousins 2014-06-24 02:26发表 [回复]
回复mlg_wing:没有,我做Java的
9楼 Jymn_Chen 2014-06-08 01:45发表 [回复]
非常详细,非常使用,谢谢分享
8楼 vaintwyt 2014-04-06 15:47发表 [回复]
楼主,文章写得很详细。赞!

我想请教一下,密钥以String类型存储时,格式是怎么样的?一定要加上“\r”吗(windows下是\r\n?)~~还有就是,我用txt打开密钥文件(.pem),貌似里面没有明显的换行,不知道该在那个地方加“\r”。。
Re: vaintwyt 2014-04-06 19:16发表 [回复]
回复vaintwyt:原来不加“\r”也是可以的~~~

感谢楼主的好文!
Re: JackCousins 2014-04-07 21:47发表 [回复]
回复vaintwyt:呵呵,刚想回复你。其实加回车换行是为了好看,跟Linux里面cat出来的内容保持一致。如果不喜欢放到代码里,可以把密钥内容做成模板文件,例如Freemarker,Velocity都可以。
7楼 feiyangxiaomi 2014-03-20 17:00发表 [回复]
楼主您好,我测试使用openssl的密钥长度:
System.out.println(publicKey.getEncoded().length+"*****"); System.out.println(AES.byteArrayToString(publicKey.getEncoded()));
结果是162 bytes,我查阅了一些资料,说这是使用的DER编码。但是我现在有128 bytes密钥长度,来自于一个Ukey,我想使用这个128 bytes去加密数据,发送给Ukey。但是问题是,下面publicKey长度为128 bytes会出错,如果长度为162bytes就没问题。
KeyFactory keyFactory= KeyFactory.getInstance("RSA","BC");
X509EncodedKeySpec keySpec= new X509EncodedKeySpec(publicKey);
this.publicKey= (RSAPublicKey) keyFactory.generatePublic(keySpec);
Re: JackCousins 2014-03-20 23:11发表 [回复]
回复feiyangxiaomi:我觉得有可能你的密钥原来就不是采用的X509格式进行保存的。具体采用的格式,跟UKey的厂家进行沟通,或者参看一下Demo程序,看用的什么方式进行解密的。实际上所谓RSA算法无非起关键作用的是(n,e1)为公钥,(n,e2)为私钥的两个参数。只要把这两个参数从证书文件中解出来,就不需要用KeySpec了,直接使用java.security.interfaces.RSAPublicKey;即可进行解密
Re: feiyangxiaomi 2014-03-21 13:48发表 [回复]
回复chaijunkun:做出来了,RSAPublicKeySpec keySpec= new RSAPublicKeySpec(new BigInteger( bytepublicKey), new BigInteger("10001", 16));中bytepublicKey就是自己装载Ukey生成的128 bytes公钥。加密出来的数据和Ukey加密出来的是一样的。Ukey解密自然不是问题。
其中在Ukey中使用了#define RSA_E_0x10001 0b00000011 类型,其中私钥类型采用的noCRT。以后有问题请多多指教。。
6楼 mydearfht 2014-03-06 17:39发表 [回复]
您好,
我想请教下,如果我想在页面用公钥解密密码然后提交到后台,页面的js该如何写呢?
我试着用别人写好的加密方法进行加密,得到的结果和你的不一样.
能给一点提示吗?
Re: JackCousins 2014-03-07 13:37发表 [回复]
回复mydearfht:刚才我在网上看了一下js实现rsa算法的例子。感觉通过这种方式做加密太麻烦了。有种东西叫做HTTPS,需要隐私保护的地方完全可以使用这种东西。传输通道已经做好了加密。能给你省去不少麻烦。
5楼 小李程序猿 2013-08-20 09:53发表 [回复]
你好,我还是有个问题,我们现在使用的ssh做服务端,安卓和服务端都是靠json传输信息的,服务器这段也是靠struts的json插件完成数据转换的,那我在使用这个rsa算法的时候,该怎么拦截struts传出的json然后加密呢?
Re: JackCousins 2013-08-20 13:35发表 [回复]
回复lywwyd1314:首先我这里要做一个方向性的建议,json作为数据交换格式,可能内容会很长,而RSA这种安全算法是比较复杂的,1024位的加密也就限制了加密消息长度不能超过1024位(128字节)。我建议你不要对整个JSON进行加密解密,而是采用摘要算法将某些关键字段作为算子从而得出哈希后再进行加解密,这样既保证防止数据篡改,性能也大为提高。

其次你说的如何拦截的问题,我认为你大可以将JSON看做字符串,然后在客户端放入RequestBody中POST到服务器,服务器去整体截取RequestBody作为String来转换为JSON对象。我习惯用SpringMVC,至于Struts如何实现,建议你还是查一下相关资料。
Re: 小李程序猿 2013-08-20 23:31发表 [回复]
回复chaijunkun:我现在没有使用struts的json扩展,而是使用的最原始的printwriter。。已经实现加密了,但只能加密很短的字符串。。
还有啊,你的方法返回的是byte[]数组,那我返回给安卓设备的时候,好像不能为byte[]的,然后我又调用了byteArrayToString方法,现在已经传输给了安卓,但是问题是怎么从String转回来呢?那个算法真心不太懂哦…………
Re: JackCousins 2013-08-21 11:38发表 [回复]
回复lywwyd1314:对于byte[]字节数组的返回类型,往往都是原始数据,这些数据中如果强制对应ASCII字符,就会发现有好多不可见字符。HTTP协议最擅长的是传输文本。所以推荐你使用Base64算法对byte[]数据进行编码。这样得出的肯定是可见字符,然后传输到客户端再用Base64解密。如果你对Base64算法不熟悉可以用现成的apache-commons-codec包,org.apache.commons.codec.binary.Base64组件可以很方便地实现编解码。
Re: 小李程序猿 2013-08-20 23:27发表 [回复]
回复chaijunkun:恩,晚上的时候果然碰到你说的长度的问题了,从数据库中取出来的数据太长了,不能加密,但是我对你说的那个摘要算法还不是太了解,什么是关键字段呢,上网查的时候,也有人说得出hash之后再加密,但是这点真的不是太了解呢,因为这些加密算法的问题,原来没有写过,现在只不过是用到了才会写。。。
Re: JackCousins 2013-08-21 11:46发表 [回复]
回复lywwyd1314:其实我说的只是一个思路,不知道你做过oauth接入没。很多系统都采用签名的机制。将请求参数a=xx&b=xxx&amp;c=x都拿出来,按照参数名称英文字母表顺序排列拼接字符串再带上参数如:参数1:参数1值,参数2:参数2值,后面再加上一个固定的字符串,最后取MD5,这个就是摘要,写成公式就是:MD5(k1:v1,k2:v2,kn:vn+"const string"),因为MD5是定长的,用它再做RSA加解密就容易了。这只是一个实例,加密解密的过程在于设计,但无非也就是签名、哈希、加密这几步骤。我只能帮你到这了,不能说得太透了
Re: 小李程序猿 2013-09-04 07:22发表 [回复]
回复chaijunkun:恩 看过一些oauth2.0的,然后看了你的提示,我现在想到的解决方案是先在设备端做一个对接签名,然后等服务端和设备端牵手成功之后再把未加密的那些数据传给设备端,不知道这个方式怎么样?你觉着合适吗?
我看那个rsa加密也挺慢的了。还是不要加密太多东西吧……
Re: JackCousins 2013-09-04 13:27发表 [回复]
回复lywwyd1314:确实RSA加密的速度很慢了。我觉得民用级别,考虑使用HTTPS来传输数据就可以了,没必要自己搞一套加密的算法把内容全部加密。费力不讨好的。而且HTTPS是标准。参考文献更多。专注于业务开发,那些HTTPS的配置交给运维人员去解决就好。
Re: 小李程序猿 2013-09-04 07:24发表 [回复]
回复lywwyd1314:其实我们经理的意思 是把数据库里的一些需要展示给用户的数据全都加密。我都搞不懂这有什么好加密的…………哎……反正都是要让别人看到的,加密了也没用啊
4楼 lattimore 2013-07-23 15:03发表 [回复]
再请教个问题。
是不是可以用私钥加密公钥解密阿?
thanks
Re: JackCousins 2013-07-24 10:26发表 [回复]
回复lattimore:用私钥加密公钥解密是可以的,网上有人说私钥加密公钥解密解不开,这绝对是错误的。可以解开,也可以看到数据。但是私钥包含更多的信息,强烈不赞成将私钥公布出去的做法。实在不行可以做两对密钥。当A发消息给B时,A用B的公钥加密;当B发消息给A时,B用A的公钥加密。
Re: lattimore 2013-07-26 21:04发表 [回复]
回复chaijunkun:Thanks a lot!
3楼 lattimore 2013-07-23 15:01发表 [回复]
好文,正需要!
2楼 songbai1211 2013-01-12 00:24发表 [回复]
非常感谢楼主的分享精神啊,哎,现在大家都做加密,没一点分享精神,网上找来找去都是tmd的同样的那一两个文章,弄的都块蛋疼了,再次谢谢楼主,
Re: JackCousins 2013-01-15 09:52发表 [回复]
回复songbai1211:人人为我,我为人人。不客气
1楼 huchangyue 2012-08-07 11:20发表 [回复]
您好,大虾!我想请教您一个问题。如果生成的私钥文件是带密码的,用java怎么来解析带密码的私钥文件?求赐教!
Re: JackCousins 2012-08-08 11:15发表 [回复]
回复huchangyue:这个问题开始的时候我也研究来着,后来没弄出来,就放弃了。这部分国内的资料比较少,还是得去国外的网站上看看,用Google多搜搜吧。
Re: huchangyue 2012-08-09 09:37发表 [回复]
回复chaijunkun:您好,我还想请教你一个问题。如果生成的私钥文件不进行PKCS#8编码,java怎么解析?或者是不使用命令编码,用代码怎么来进行PKCS#8编码?
Re: JackCousins 2012-08-09 16:14发表 [回复]
回复huchangyue:针对不同加密的私钥文件java应该有提供相应的KeySpec。这些类都存在于java.security.spec包中。如果想知道哪一个适合自己,还应该多查查资料。我提供的代码也只是个入门,文档还是离不开的。多看看官方资料吧。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
src="http://blog.csdn.net/common/ad.html?t=5&containerId=ad_bot&frmId=ad_frm_1" style="border-width: 0px; overflow: hidden; width: 988px; height: 0px;" scrolling="no" id="ad_frm_1" frameborder="0">
  • 个人资料
    • 访问:436393次
    • 积分:4965
    • 等级:
    • 排名:第2560名
    • 原创:56篇
    • 转载:12篇
    • 译文:8篇
    • 评论:375条
  • 推荐文章
  • src="http://blog.csdn.net/common/ad.html?t=12&containerId=ad_commend&frmId=ad_frm_2" style="border-width: 0px; overflow: hidden; width: 266px; height: 200px;" scrolling="no" id="ad_frm_2" frameborder="0">
  • 最新评论
src="http://ad.csdn.net/log.ashx?t=view&adtype=ms1408&adurl=http%3A%2F%2Fad.csdn.net%2Fadsrc%2Fhp-iss-cloud-zhiding-banner-peisong-960-90-30k.swf&r_m=358" style="width: 1px; height: 1px; position: absolute; visibility: hidden;">
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值