Java 进行 RSA 加解密时不得不考虑到的那些事儿

标签: java rsa 解密 | 发表时间:2016-09-02 11:35 | 作者:
出处:http://m635674608.iteye.com

1. 加密的系统不要具备解密的功能,否则 RSA 可能不太合适

公钥加密,私钥解密。加密的系统和解密的系统分开部署,加密的系统不应该同时具备解密的功能,这样即使黑客攻破了加密系统,他拿到的也只是一堆无法破解的密文数据。否则的话,你就要考虑你的场景是否有必要用 RSA 了。

2. 可以通过修改生成密钥的长度来调整密文长度

生成密文的长度等于密钥长度。密钥长度越大,生成密文的长度也就越大,加密的速度也就越慢,而密文也就越难被破解掉。著名的"安全和效率总是一把双刃剑"定律,在这里展现的淋漓尽致。我们必须通过定义密钥的长度在"安全"和"加解密效率"之间做出一个平衡的选择。

3. 生成密文的长度和明文长度无关,但明文长度不能超过密钥长度

不管明文长度是多少,RSA 生成的密文长度总是固定的。
但 是明文长度不能超过密钥长度。比如 Java 默认的 RSA 加密实现不允许明文长度超过密钥长度减去 11(单位是字节,也就是 byte)。也就是说,如果我们定义的密钥(我们可以通过 java.security.KeyPairGenerator.initialize(int keysize) 来定义密钥长度)长度为 1024(单位是位,也就是 bit),生成的密钥长度就是 1024位 / 8位/字节 = 128字节,那么我们需要加密的明文长度不能超过 128字节 -
11 字节 = 117字节。也就是说,我们最大能将 117 字节长度的明文进行加密,否则会出问题(抛诸如 javax.crypto.IllegalBlockSizeException: Data must not be longer than 53 bytes 的异常)。
而 BC 提供的加密算法能够支持到的 RSA 明文长度最长为密钥长度。

4. byte[].toString() 返回的实际上是内存地址,不是将数组的实际内容转换为 String

警惕 toString 陷阱:Java 中数组的 toString() 方法返回的并非数组内容,它返回的实际上是数组存储元素的类型以及数组在内存的位置的一个标识。
大部分人跌入这个误区而不自知,包括一些写了多年 Java 的老鸟。比如这篇博客《 How To Convert Byte[] Array To String In Java》中的代码

  1. public class TestByte  
  2. {      
  3.     public static void main(String[] argv) {  
  4.    
  5.             String example = "This is an example";  
  6.             byte[] bytes = example.getBytes();  
  7.    
  8.             System.out.println("Text : " + example);  
  9.             System.out.println("Text [Byte Format] : " + bytes);  
  10.             System.out.println("Text [Byte Format] : " + bytes.toString());  
  11.    
  12.             String s = new String(bytes);  
  13.             System.out.println("Text Decryted : " + s);  
  14.    
  15.    
  16.     }  
  17. }  


输出:
Text : This is an example
Text [Byte Format] : [B@187aeca
Text [Byte Format] : [B@187aeca
Text Decryted : This is an example
以及这篇博客《 RSA Encryption Example》中的代码

  1. final byte[] cipherText = encrypt(originalText, publicKey);  
  2. System.out.println("Encrypted: " +cipherText.toString());  


输出:
[B@4c3a8ea3
这 些输出其实都是字节数组在内存的位置的一个标识,而不是作者所认为的字节数组转换成的字符串内容。如果我们对密钥以 byte[].toString() 进行持久化存储或者和其他一些字符串打 json 传输,那么密钥的解密者得到的将只是一串毫无意义的字符,当他解码的时候很可能会遇到 "javax.crypto.BadPaddingException" 异常。

5. 字符串用以保存文本信息,字节数组用以保存二进制数据

java.lang.String 保存明文,byte 数组保存二进制密文,在 java.lang.String 和 byte[] 之间不应该具备互相转换。如果你确实必须得使用 java.lang.String 来持有这些二进制数据的话,最安全的方式是使用 Base64(推荐 Apache 的 commons-codec 库的 org.apache.commons.codec.binary.Base64):

  1. // use String to hold cipher binary data  
  2. Base64 base64 = new Base64();   
  3. String cipherTextBase64 = base64.encodeToString(cipherText);  
  4.   
  5. // get cipher binary data back from String  
  6. byte[] cipherTextArray = base64.decode(cipherTextBase64);  

 

6. 每次生成的密文都不一致证明你选用的加密算法很安全

一个优秀的加密必须每次生成的密文都不一致,即使每次你的明文一样、使用同一个公钥。因为这样才能把明文信息更安全地隐藏起来。
Java 默认的 RSA 实现是 "RSA/None/PKCS1Padding"(比如 Cipher cipher = Cipher.getInstance("RSA");句,这个 Cipher 生成的密文总是不一致的),Bouncy Castle 的默认 RSA 实现是 "RSA/None/NoPadding"。
为什么 Java 默认的 RSA 实现每次生成的密文都不一致呢,即使每次使用同一个明文、同一个公钥?这是因为 RSA 的 PKCS #1 padding 方案在加密前对明文信息进行了随机数填充。
你可以使用以下办法让同一个明文、同一个公钥每次生成同一个密文,但是你必须意识到你这么做付出的代价是什么。比如,你可能使用 RSA 来加密传输,但是由于你的同一明文每次生成的同一密文,攻击者能够据此识别到同一个信息都是何时被发送。

  1. Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());  
  2. final Cipher cipher = Cipher.getInstance("RSA/None/NoPadding", "BC");  

 

7. 可以通过调整算法提供者来减小密文长度

Java 默认的 RSA 实现 "RSA/None/PKCS1Padding" 要求最小密钥长度为 512 位(否则会报 java.security.InvalidParameterException: RSA keys must be at least 512 bits long 异常),也就是说生成的密钥、密文长度最小为 64 个字节。如果你还嫌大,可以通过调整算法提供者来减小密文长度:

  1. Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());  
  2. final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");  
  3. keyGen.initialize(128);  

 

如此这般得到的密文长度为 128 位(16 个字节)。但是这么干之前请先回顾一下本文第 2 点所述。

 

8. Cipher 是有状态的,而且是线程不安全的

javax.crypto.Cipher 是有状态的,不要把 Cipher 当做一个静态变量,除非你的程序是单线程的,也就是说你能够保证同一时刻只有一个线程在调用 Cipher。否则你可能会像笔者似的遇到 java.lang.ArrayIndexOutOfBoundsException: too much data for RSA block 异常。遇见这个异常,你需要先确定你给 Cipher 加密的明文(或者需要解密的密文)是否过长;排除掉明文(或者密文)过长的情况,你需要考虑是不是你的 Cipher 线程不安全了。

 

后记

虽然《 RSA Encryption Example》存在一些认识上的误区,但笔者仍然认为它是一篇很不错的入门级文章。结合本文所列内容,笔者将其代码做了一些调整以供参考:

  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileNotFoundException;  
  4. import java.io.FileOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.ObjectInputStream;  
  7. import java.io.ObjectOutputStream;  
  8. import java.security.KeyPair;  
  9. import java.security.KeyPairGenerator;  
  10. import java.security.NoSuchAlgorithmException;  
  11. import java.security.PrivateKey;  
  12. import java.security.PublicKey;  
  13. import java.security.Security;  
  14.   
  15. import javax.crypto.Cipher;  
  16.   
  17. import org.apache.commons.codec.binary.Base64;  
  18.   
  19. /** 
  20.  * @author JavaDigest 
  21.  *  
  22.  */  
  23. public class EncryptionUtil {  
  24.   
  25.     /** 
  26.      * String to hold name of the encryption algorithm. 
  27.      */  
  28.     public static final String ALGORITHM = "RSA";  
  29.   
  30.     /** 
  31.      * String to hold name of the encryption padding. 
  32.      */  
  33.     public static final String PADDING = "RSA/NONE/NoPadding";  
  34.   
  35.     /** 
  36.      * String to hold name of the security provider. 
  37.      */  
  38.     public static final String PROVIDER = "BC";  
  39.   
  40.     /** 
  41.      * String to hold the name of the private key file. 
  42.      */  
  43.     public static final String PRIVATE_KEY_FILE = "e:/defonds/work/20150116/private.key";  
  44.   
  45.     /** 
  46.      * String to hold name of the public key file. 
  47.      */  
  48.     public static final String PUBLIC_KEY_FILE = "e:/defonds/work/20150116/public.key";  
  49.   
  50.     /** 
  51.      * Generate key which contains a pair of private and public key using 1024 
  52.      * bytes. Store the set of keys in Prvate.key and Public.key files. 
  53.      *  
  54.      * @throws NoSuchAlgorithmException 
  55.      * @throws IOException 
  56.      * @throws FileNotFoundException 
  57.      */  
  58.     public static void generateKey() {  
  59.         try {  
  60.   
  61.             Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());  
  62.             final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(  
  63.                     ALGORITHM, PROVIDER);  
  64.             keyGen.initialize(256);  
  65.             final KeyPair key = keyGen.generateKeyPair();  
  66.   
  67.             File privateKeyFile = new File(PRIVATE_KEY_FILE);  
  68.             File publicKeyFile = new File(PUBLIC_KEY_FILE);  
  69.   
  70.             // Create files to store public and private key  
  71.             if (privateKeyFile.getParentFile() != null) {  
  72.                 privateKeyFile.getParentFile().mkdirs();  
  73.             }  
  74.             privateKeyFile.createNewFile();  
  75.   
  76.             if (publicKeyFile.getParentFile() != null) {  
  77.                 publicKeyFile.getParentFile().mkdirs();  
  78.             }  
  79.             publicKeyFile.createNewFile();  
  80.   
  81.             // Saving the Public key in a file  
  82.             ObjectOutputStream publicKeyOS = new ObjectOutputStream(  
  83.                     new FileOutputStream(publicKeyFile));  
  84.             publicKeyOS.writeObject(key.getPublic());  
  85.             publicKeyOS.close();  
  86.   
  87.             // Saving the Private key in a file  
  88.             ObjectOutputStream privateKeyOS = new ObjectOutputStream(  
  89.                     new FileOutputStream(privateKeyFile));  
  90.             privateKeyOS.writeObject(key.getPrivate());  
  91.             privateKeyOS.close();  
  92.         } catch (Exception e) {  
  93.             e.printStackTrace();  
  94.         }  
  95.   
  96.     }  
  97.   
  98.     /** 
  99.      * The method checks if the pair of public and private key has been 
  100.      * generated. 
  101.      *  
  102.      * @return flag indicating if the pair of keys were generated. 
  103.      */  
  104.     public static boolean areKeysPresent() {  
  105.   
  106.         File privateKey = new File(PRIVATE_KEY_FILE);  
  107.         File publicKey = new File(PUBLIC_KEY_FILE);  
  108.   
  109.         if (privateKey.exists() && publicKey.exists()) {  
  110.             return true;  
  111.         }  
  112.         return false;  
  113.     }  
  114.   
  115.     /** 
  116.      * Encrypt the plain text using public key. 
  117.      *  
  118.      * @param text 
  119.      *            : original plain text 
  120.      * @param key 
  121.      *            :The public key 
  122.      * @return Encrypted text 
  123.      * @throws java.lang.Exception 
  124.      */  
  125.     public static byte[] encrypt(String text, PublicKey key) {  
  126.         byte[] cipherText = null;  
  127.         try {  
  128.             // get an RSA cipher object and print the provider  
  129.             Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());  
  130.             final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);  
  131.               
  132.             // encrypt the plain text using the public key  
  133.             cipher.init(Cipher.ENCRYPT_MODE, key);  
  134.             cipherText = cipher.doFinal(text.getBytes());  
  135.         } catch (Exception e) {  
  136.             e.printStackTrace();  
  137.         }  
  138.         return cipherText;  
  139.     }  
  140.   
  141.     /** 
  142.      * Decrypt text using private key. 
  143.      *  
  144.      * @param text 
  145.      *            :encrypted text 
  146.      * @param key 
  147.      *            :The private key 
  148.      * @return plain text 
  149.      * @throws java.lang.Exception 
  150.      */  
  151.     public static String decrypt(byte[] text, PrivateKey key) {  
  152.         byte[] dectyptedText = null;  
  153.         try {  
  154.             // get an RSA cipher object and print the provider  
  155.             Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());  
  156.             final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);  
  157.   
  158.             // decrypt the text using the private key  
  159.             cipher.init(Cipher.DECRYPT_MODE, key);  
  160.             dectyptedText = cipher.doFinal(text);  
  161.   
  162.         } catch (Exception ex) {  
  163.             ex.printStackTrace();  
  164.         }  
  165.   
  166.         return new String(dectyptedText);  
  167.     }  
  168.   
  169.     /** 
  170.      * Test the EncryptionUtil 
  171.      */  
  172.     public static void main(String[] args) {  
  173.   
  174.         try {  
  175.   
  176.             // Check if the pair of keys are present else generate those.  
  177.             if (!areKeysPresent()) {  
  178.                 // Method generates a pair of keys using the RSA algorithm and  
  179.                 // stores it  
  180.                 // in their respective files  
  181.                 generateKey();  
  182.             }  
  183.   
  184.             final String originalText = "12345678901234567890123456789012";  
  185.             ObjectInputStream inputStream = null;  
  186.   
  187.             // Encrypt the string using the public key  
  188.             inputStream = new ObjectInputStream(new FileInputStream(  
  189.                     PUBLIC_KEY_FILE));  
  190.             final PublicKey publicKey = (PublicKey) inputStream.readObject();  
  191.             final byte[] cipherText = encrypt(originalText, publicKey);  
  192.   
  193.             // use String to hold cipher binary data  
  194.             Base64 base64 = new Base64();  
  195.             String cipherTextBase64 = base64.encodeToString(cipherText);  
  196.   
  197.             // get cipher binary data back from String  
  198.             byte[] cipherTextArray = base64.decode(cipherTextBase64);  
  199.   
  200.             // Decrypt the cipher text using the private key.  
  201.             inputStream = new ObjectInputStream(new FileInputStream(  
  202.                     PRIVATE_KEY_FILE));  
  203.             final PrivateKey privateKey = (PrivateKey) inputStream.readObject();  
  204.             final String plainText = decrypt(cipherTextArray, privateKey);  
  205.   
  206.             // Printing the Original, Encrypted and Decrypted Text  
  207.             System.out.println("Original=" + originalText);  
  208.             System.out.println("Encrypted=" + cipherTextBase64);  
  209.             System.out.println("Decrypted=" + plainText);  
  210.   
  211.         } catch (Exception e) {  
  212.             e.printStackTrace();  
  213.         }  
  214.     }  
  215. }  



先 生成一对密钥,供以后加解密使用(不需要每次加解密都生成一个密钥),密钥长度为 256 位,也就是说生成密文长度都是 32 字节的,支持加密最大长度为 32 字节的明文,因为使用了 nopadding 所以对于同一密钥同一明文,本文总是生成一样的密文;然后使用生成的公钥对你提供的明文信息进行加密,生成 32 字节二进制明文,然后使用 Base64 将二进制密文转换为字符串保存;之后演示了如何把 Base64 字符串转换回二进制密文;最后把二进制密文转换成加密前的明文。以上程序输出如下:
Original=12345678901234567890123456789012
Encrypted=GTyX3nLO9vseMJ+RB/dNrZp9XEHCzFkHpgtaZKa8aCc=
Decrypted=12345678901234567890123456789012

参考资料

http://blog.csdn.net/defonds/article/details/42775183



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [java rsa 解密] 推荐:

JAVA实现RSA加密解密

- - CSDN博客推荐文章
提供加密,解密,生成密钥对等方法. RSA加密原理概述   :. RSA的安全性依赖于大数的分解,公钥和私钥都是两个大素数(大于100的十进制位)的函数. 据猜测,从一个密钥和密文推断出明文的难度等同于分解两个大素数的积   .  1.选择两个大素数 p,q ,计算 n=p*q;   .  2.随机选择加密密钥 e ,要求 e 和 (p-1)*(q-1)互质   .

Java 进行 RSA 加解密时不得不考虑到的那些事儿

- - zzm
加密的系统不要具备解密的功能,否则 RSA 可能不太合适. 加密的系统和解密的系统分开部署,加密的系统不应该同时具备解密的功能,这样即使黑客攻破了加密系统,他拿到的也只是一堆无法破解的密文数据. 否则的话,你就要考虑你的场景是否有必要用 RSA 了. 可以通过修改生成密钥的长度来调整密文长度. 生成密文的长度等于密钥长度.

一个基于RSA算法的Java数字签名例子

- - 行业应用 - ITeye博客
  网络数据安全包括数据的本身的安全性、数据的完整性(防止篡改)、数据来源的不可否认性等要素. 对数据采用加密算法加密可以保证数据本身的安全性,利用消息摘要可以保证数据的完整性,但是还有一点就是数据来源的不可否认性(也就是数据来自哪里接收者是清楚的,而且发送数据者不可抵赖).         有些方案曾经使用消息认证码(MAC)来保证数据来源于合法的发送着,但是利用消息认证码会带来一个问题,就是通讯双方必须事先约定两者之间的通讯用共享密码.

iOS、Android、java服务端 DES+RSA安全传输统一实现

- - 移动开发 - ITeye博客
工作中遇到了安全传输问题,需要解决iOS和Android客户端跟java服务端的安全传输问题,结合对HTTPS的了解,便使用DES+RSA方式模拟HTTPS. 在实现过程中,遇到了一些瓶颈,主要是保持平台兼容性的问题,Android和服务的还可以,统一使用java API,但要包含iOS就比较麻烦了,参考了网上很多资料,忙了三四天,终于搞通了.

iOS中使用RSA对数据进行加密解密

- - ITeye博客
RSA算法是一种非对称加密算法,常被用于加密数据传输.如果配合上数字摘要算法, 也可以用于文件签名.. 本文将讨论如何在iOS中使用RSA传输加密数据.. openssl-1.0.1j, openssl需要使用1.x版本, 推荐使用[homebrew](http://brew.sh/)安装.. RSA使用"秘匙对"对数据进行加密解密.在加密解密数据前,需要先生成公钥(public key)和私钥(private key)..

对文件压缩加密/解密解压缩的例子,DES/RSA [转]

- - 行业应用 - ITeye博客
RSA压缩加密/解压缩解密. * 对文件压缩加密/解密解压缩 对象类.   // 如果传入的是目录.    // 创建压缩的子目录.    // 把压缩文件加入rar中.   * 对directory目录下的文件压缩,保存为指定的文件zipFile.   * 解压缩文件zipFile保存在directory目录下.

RSA算法原理(二)

- - 阮一峰的网络日志
上一次,我介绍了一些 数论知识. 有了这些知识,我们就可以看懂 RSA算法. 这是目前地球上最重要的加密算法. 我们通过一个例子,来理解RSA算法. 假设 爱丽丝要与鲍勃进行加密通信,她该怎么生成公钥和私钥呢. 第一步,随机选择两个不相等的质数p和q. (实际应用中,这两个质数越大,就越难破解.

RSA算法原理(一)

- - 阮一峰的网络日志
如果你问我,哪一种 算法最重要. 我可能会回答 "公钥加密算法". 因为它是计算机通信安全的基石,保证了加密数据不会被破解. 你可以想象一下,信用卡交易被破解的后果. 进入正题之前,我先简单介绍一下,什么是"公钥加密算法". 1976年以前,所有的加密方法都是同一种模式:.   (1)甲方选择某一种加密规则,对信息进行加密;.

RSA的SecureID token数据被偷了?

- ripwu - 张志强的网络日志
博客 » 记事本 » 密码学 ». WSJ报道:RSA承认其数据被偷,4000万SecureID token需要被更新. 中国银行银行密钥用的就是RSA生产,就是下图这玩意儿,手里有这玩意儿的同学们要小心了(当然,如果你的账户里的钱没有6位数以上,也不用太担心,毕竟网银的安全性不全依赖于这个设备):.

RSA Security 被攻破的途徑

- MorrisC - Gea-Suan Lin&#39;s BLOG
今年三月的時候,RSA Security 被攻破,攻擊者順利取得 SecurID 的資料,這些資料很有可能降低 SecurID 的安全性. 也因此有了 Lockheed Martin 被攻擊的事情. 在官方的說明「Anatomy of an Attack」中,有提到「2011 Recruitment plan.xls」是使用 Excel 檔案,加上 Adobe Flash vulnerability (CVE-2011-0609) 攻入,而這是個 0-day attack (在當時).