1. 加密的系统不要具备解密的功能,否则 RSA 可能不太合适
公钥加密,私钥解密。加密的系统和解密的系统分开部署,加密的系统不应该同时具备解密的功能,这样即使黑客攻破了加密系统,他拿到的也只是一堆无法破解的密文数据。否则的话,你就要考虑你的场景是否有必要用 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 明文长度最长为密钥长度。
大部分人跌入这个误区而不自知,包括一些写了多年 Java 的老鸟。比如这篇博客《How To Convert Byte[] Array To String In Java》中的代码
输出:
Text : This is an example
Text [Byte Format] : [B@187aeca
Text [Byte Format] : [B@187aeca
Text Decryted : This is an example
以及这篇博客《RSA Encryption Example》中的代码
输出:
[B@4c3a8ea3
这些输出其实都是字节数组在内存的位置的一个标识,而不是作者所认为的字节数组转换成的字符串内容。如果我们对密钥以 byte[].toString() 进行持久化存储或者和其他一些字符串打 json 传输,那么密钥的解密者得到的将只是一串毫无意义的字符,当他解码的时候很可能会遇到 "javax.crypto.BadPaddingException" 异常。
Java 默认的 RSA 实现是 "RSA/None/PKCS1Padding"(比如 Cipher cipher = Cipher.getInstance("RSA");句,这个 Cipher 生成的密文总是不一致的),Bouncy Castle 的默认 RSA 实现是 "RSA/None/NoPadding"。
为什么 Java 默认的 RSA 实现每次生成的密文都不一致呢,即使每次使用同一个明文、同一个公钥?这是因为 RSA 的 PKCS #1 padding 方案在加密前对明文信息进行了随机数填充。
你可以使用以下办法让同一个明文、同一个公钥每次生成同一个密文,但是你必须意识到你这么做付出的代价是什么。比如,你可能使用 RSA 来加密传输,但是由于你的同一明文每次生成的同一密文,攻击者能够据此识别到同一个信息都是何时被发送。
先生成一对密钥,供以后加解密使用(不需要每次加解密都生成一个密钥),密钥长度为 256 位,也就是说生成密文长度都是 32 字节的,支持加密最大长度为 32 字节的明文,因为使用了 nopadding 所以对于同一密钥同一明文,本文总是生成一样的密文;然后使用生成的公钥对你提供的明文信息进行加密,生成 32 字节二进制明文,然后使用 Base64 将二进制密文转换为字符串保存;之后演示了如何把 Base64 字符串转换回二进制密文;最后把二进制密文转换成加密前的明文。以上程序输出如下:
Original=12345678901234567890123456789012
Encrypted=GTyX3nLO9vseMJ+RB/dNrZp9XEHCzFkHpgtaZKa8aCc=
Decrypted=12345678901234567890123456789012
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》中的代码
- public class TestByte
- {
- public static void main(String[] argv) {
- String example = "This is an example";
- byte[] bytes = example.getBytes();
- System.out.println("Text : " + example);
- System.out.println("Text [Byte Format] : " + bytes);
- System.out.println("Text [Byte Format] : " + bytes.toString());
- String s = new String(bytes);
- System.out.println("Text Decryted : " + s);
- }
- }
输出:
Text : This is an example
Text [Byte Format] : [B@187aeca
Text [Byte Format] : [B@187aeca
Text Decryted : This is an example
以及这篇博客《RSA Encryption Example》中的代码
- final byte[] cipherText = encrypt(originalText, publicKey);
- 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):- // use String to hold cipher binary data
- Base64 base64 = new Base64();
- String cipherTextBase64 = base64.encodeToString(cipherText);
- // get cipher binary data back from String
- 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 来加密传输,但是由于你的同一明文每次生成的同一密文,攻击者能够据此识别到同一个信息都是何时被发送。
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- 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 个字节。如果你还嫌大,可以通过调整算法提供者来减小密文长度:- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC");
- 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》存在一些认识上的误区,但笔者仍然认为它是一篇很不错的入门级文章。结合本文所列内容,笔者将其代码做了一些调整以供参考:- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.security.KeyPair;
- import java.security.KeyPairGenerator;
- import java.security.NoSuchAlgorithmException;
- import java.security.PrivateKey;
- import java.security.PublicKey;
- import java.security.Security;
- import javax.crypto.Cipher;
- import org.apache.commons.codec.binary.Base64;
- /**
- * @author JavaDigest
- *
- */
- public class EncryptionUtil {
- /**
- * String to hold name of the encryption algorithm.
- */
- public static final String ALGORITHM = "RSA";
- /**
- * String to hold name of the encryption padding.
- */
- public static final String PADDING = "RSA/NONE/NoPadding";
- /**
- * String to hold name of the security provider.
- */
- public static final String PROVIDER = "BC";
- /**
- * String to hold the name of the private key file.
- */
- public static final String PRIVATE_KEY_FILE = "e:/defonds/work/20150116/private.key";
- /**
- * String to hold name of the public key file.
- */
- public static final String PUBLIC_KEY_FILE = "e:/defonds/work/20150116/public.key";
- /**
- * Generate key which contains a pair of private and public key using 1024
- * bytes. Store the set of keys in Prvate.key and Public.key files.
- *
- * @throws NoSuchAlgorithmException
- * @throws IOException
- * @throws FileNotFoundException
- */
- public static void generateKey() {
- try {
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(
- ALGORITHM, PROVIDER);
- keyGen.initialize(256);
- final KeyPair key = keyGen.generateKeyPair();
- File privateKeyFile = new File(PRIVATE_KEY_FILE);
- File publicKeyFile = new File(PUBLIC_KEY_FILE);
- // Create files to store public and private key
- if (privateKeyFile.getParentFile() != null) {
- privateKeyFile.getParentFile().mkdirs();
- }
- privateKeyFile.createNewFile();
- if (publicKeyFile.getParentFile() != null) {
- publicKeyFile.getParentFile().mkdirs();
- }
- publicKeyFile.createNewFile();
- // Saving the Public key in a file
- ObjectOutputStream publicKeyOS = new ObjectOutputStream(
- new FileOutputStream(publicKeyFile));
- publicKeyOS.writeObject(key.getPublic());
- publicKeyOS.close();
- // Saving the Private key in a file
- ObjectOutputStream privateKeyOS = new ObjectOutputStream(
- new FileOutputStream(privateKeyFile));
- privateKeyOS.writeObject(key.getPrivate());
- privateKeyOS.close();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- /**
- * The method checks if the pair of public and private key has been
- * generated.
- *
- * @return flag indicating if the pair of keys were generated.
- */
- public static boolean areKeysPresent() {
- File privateKey = new File(PRIVATE_KEY_FILE);
- File publicKey = new File(PUBLIC_KEY_FILE);
- if (privateKey.exists() && publicKey.exists()) {
- return true;
- }
- return false;
- }
- /**
- * Encrypt the plain text using public key.
- *
- * @param text
- * : original plain text
- * @param key
- * :The public key
- * @return Encrypted text
- * @throws java.lang.Exception
- */
- public static byte[] encrypt(String text, PublicKey key) {
- byte[] cipherText = null;
- try {
- // get an RSA cipher object and print the provider
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);
- // encrypt the plain text using the public key
- cipher.init(Cipher.ENCRYPT_MODE, key);
- cipherText = cipher.doFinal(text.getBytes());
- } catch (Exception e) {
- e.printStackTrace();
- }
- return cipherText;
- }
- /**
- * Decrypt text using private key.
- *
- * @param text
- * :encrypted text
- * @param key
- * :The private key
- * @return plain text
- * @throws java.lang.Exception
- */
- public static String decrypt(byte[] text, PrivateKey key) {
- byte[] dectyptedText = null;
- try {
- // get an RSA cipher object and print the provider
- Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
- final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);
- // decrypt the text using the private key
- cipher.init(Cipher.DECRYPT_MODE, key);
- dectyptedText = cipher.doFinal(text);
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- return new String(dectyptedText);
- }
- /**
- * Test the EncryptionUtil
- */
- public static void main(String[] args) {
- try {
- // Check if the pair of keys are present else generate those.
- if (!areKeysPresent()) {
- // Method generates a pair of keys using the RSA algorithm and
- // stores it
- // in their respective files
- generateKey();
- }
- final String originalText = "12345678901234567890123456789012";
- ObjectInputStream inputStream = null;
- // Encrypt the string using the public key
- inputStream = new ObjectInputStream(new FileInputStream(
- PUBLIC_KEY_FILE));
- final PublicKey publicKey = (PublicKey) inputStream.readObject();
- final byte[] cipherText = encrypt(originalText, publicKey);
- // use String to hold cipher binary data
- Base64 base64 = new Base64();
- String cipherTextBase64 = base64.encodeToString(cipherText);
- // get cipher binary data back from String
- byte[] cipherTextArray = base64.decode(cipherTextBase64);
- // Decrypt the cipher text using the private key.
- inputStream = new ObjectInputStream(new FileInputStream(
- PRIVATE_KEY_FILE));
- final PrivateKey privateKey = (PrivateKey) inputStream.readObject();
- final String plainText = decrypt(cipherTextArray, privateKey);
- // Printing the Original, Encrypted and Decrypted Text
- System.out.println("Original=" + originalText);
- System.out.println("Encrypted=" + cipherTextBase64);
- System.out.println("Decrypted=" + plainText);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
先生成一对密钥,供以后加解密使用(不需要每次加解密都生成一个密钥),密钥长度为 256 位,也就是说生成密文长度都是 32 字节的,支持加密最大长度为 32 字节的明文,因为使用了 nopadding 所以对于同一密钥同一明文,本文总是生成一样的密文;然后使用生成的公钥对你提供的明文信息进行加密,生成 32 字节二进制明文,然后使用 Base64 将二进制密文转换为字符串保存;之后演示了如何把 Base64 字符串转换回二进制密文;最后把二进制密文转换成加密前的明文。以上程序输出如下:
Original=12345678901234567890123456789012
Encrypted=GTyX3nLO9vseMJ+RB/dNrZp9XEHCzFkHpgtaZKa8aCc=
Decrypted=12345678901234567890123456789012
参考资料
- http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions
- http://stackoverflow.com/questions/1536054/how-to-convert-byte-array-to-string-and-vice-versa
- http://www.experts-exchange.com/Security/Encryption/Q_26980724.html
- http://stackoverflow.com/questions/17497426/why-does-rsa-produce-different-results-with-same-key-and-message
相关推荐
使用RSA非对称加密完成Java后端RSA加密和分段加解密,最近研究了RSA非对称加密,关于什么是RSA,网上各种文章一搜一大把,由于RSA的特性,一个1024位的密钥只能加密117位字节数据,当数据量超过117位字节的时候,程序...
Java实现的RSA加密解密算法示例 本文主要介绍了Java实现的RSA加密解密算法,结合实例形式分析了Java RSA加密解密算法的相关实现技巧。 知识点1:RSA加密解密算法简介 RSA加密解密算法是一种非对称加密算法,由Ron...
RSA加密解密是一种广泛应用于网络安全领域的非对称加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman三位科学家在1977年提出,因此得名RSA。这种算法基于大整数因子分解的困难性,使得只有持有正确密钥的人才能...
由于Java的RSA加解密一般都是用RSA/ECB/PKCS1PADDING,导致Python一般的RSA加密库的加解密结果与Java的不兼容,Python下目前能与之兼容的RSA的库目前发现的只有一个,就是m2crypto。 这个库目前的问题是在windows...
- **性能**:RSA加密解密速度较慢,不适合对大量数据进行直接操作,通常用于加密会话密钥等小量数据,然后用会话密钥加密大量数据(如使用AES)。 - **编码**:在处理字符串时,确保正确处理字符编码,避免数据丢失...
本项目"PHP_JAVA_RSA互通加解密"实现了PHP和Java之间使用RSA算法进行数据的加解密操作,确保了跨平台、跨语言的数据安全通信。 首先,RSA(Rivest-Shamir-Adleman)加密算法基于数论中的大数因子分解问题,它的核心...
在给定的“java_RSA2048加密解密.zip”压缩包中,包含了前后端实现RSA2048加密解密的示例代码,使得开发者可以轻松地将这种安全机制集成到自己的项目中。 首先,我们需要理解RSA算法的基本原理。RSA(Rivest–...
Java OpenSSL生成的RSA公私钥进行数据加解密详细介绍 项目: JAVA生成的RSA的密文,通过C++来解密。 RSA这里就不多介绍了大家自己去看。 JAVA也是通过包来实现加密和解密的,那么我的C++是通过OPENSSL的库来实现的...
Java实现RSA加解密工具类Demo是一个典型的非对称加密技术的应用示例,RSA是一种广泛使用的公开密钥加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman在1977年提出。它基于大数因子分解的困难性,提供了安全的...
本案例聚焦于"C#与Java平台RSA加密解密签名验签互通"的问题,这涉及到两个主要的技术点:RSA加密算法和跨平台兼容性。下面将详细阐述这两个知识点。 首先,RSA是一种非对称加密算法,由Ron Rivest、Adi Shamir和...
Java RSA 加密解密是一种基于非对称加密算法的安全技术,广泛应用于数据传输、数字签名等领域。RSA(Rivest-Shamir-Adleman)由三位数学家命名,其核心原理是大整数因子分解的困难性。在Java中,我们可以使用Java ...
最后,涉及到ConsoleApplication1可能是一个C#的控制台应用程序示例,这个项目可能包含了实现RSA加密和解密的代码,用于演示如何在C#和Java之间进行安全的数据交换。通过阅读和理解这个示例代码,开发者可以更好地...
java go RSA互相加解密 go rsa加密后可以用 java解密, java rsa加密后 可以用解密, 要把 txt文件中的秘钥和私钥 都复制粘贴到java 文件和go 文件 ,公钥和私钥统一才可以
在Java中实现RSA加解密,我们需要使用`java.security`包中的相关类。首先,我们需要生成一对公钥和私钥。这通常通过`KeyPairGenerator`类完成,指定算法为"RSA",然后通过`generateKeyPair()`方法生成包含公钥...
在Java中实现RSA加解密,主要涉及到Java的`java.security`和`javax.crypto`这两个包。首先,我们需要生成一对公钥和私钥,这通常通过`KeyPairGenerator`类来完成。以下是一段创建RSA密钥对的示例代码: ```java ...
RSA算法涉及到两个密钥:公钥和私钥,其中公钥用于加密,私钥用于解密。这种特性使得它适用于在不安全的环境中进行通信,例如在网络上传输敏感信息。 以下是一个简单的RSA加密解密工具类的实现: ```java import ...
在Java中,`java.security`和`javax.crypto`包提供了必要的接口和类来实现RSA加解密。例如,`KeyPairGenerator`用于生成公钥和私钥对,`Cipher`用于进行加解密操作。 在描述中提到的"解决长度限制",这是因为在RSA...
Java 中的 RSA 加密解密主要涉及到三个部分:密钥生成、加密和解密。 密钥生成是指生成公钥和私钥的过程。在 Java 中,可以使用 KeyPairGenerator 生成密钥对。密钥对包括公钥和私钥,公钥用于加密,私钥用于解密。...
java rsa 加解密 包含界面,随机生成密钥
Java RSA加解密是一种基于非对称加密算法的典型应用,广泛用于网络安全、数据保护等领域。RSA算法由Ron Rivest、Adi Shamir和Leonard Adleman在1977年提出,因其三位发明者的名字命名。它利用了大整数因子分解的困难...