Java 非对称加密RSA理解和运用

Posted 一个八月想偷懒的开发坑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 非对称加密RSA理解和运用相关的知识,希望对你有一定的参考价值。

听说戴耳机,效果更佳~

Java 非对称加密RSA理解和运用


前言

       工作里接入支付宝微信支付接口和对接鉴权通道使用到了 RSA 加密方式,现有时间记录一下使用体验哈哈哈!


对称加密和非对称加密

        对称加密:对称加密指的就是加密和解密使用同一个秘钥,所以叫做对称加密。对称加密只有一个秘钥,作为私钥。 常见的对称加密算法:DES,AES,3DES 等。

        非对称加密:非对称加密指的是:加密和解密使用不同的秘钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。私钥加密的信息,只有公钥才能解密。 常见的非对称加密算法:RSA,ECC 等。

        区别:对称加密算法相比非对称加密算法来说,加解密的效率要高得多。但是缺陷在于对于秘钥的管理上,以及在非安全信道中通讯时,密钥交换的安全性不能保障。所以在实际的网络环境中,会将两者混合使用。


RSA

        RSA 算法是第一个能同时用于加密和数字签名的算法,也易于理解和操作。RSA 是被研究得最广泛的公钥算法,从提出到现今的三十多年里,经历了各种攻击的考验,逐渐为人们接受,截止 2017 年被普遍认为是最优秀的公钥方案之一。


RSA 安全性

        RSA的安全性依赖于大数分解,但是否等同于大数分解一直未能得到理论上的证明,因为没有证明破解 RSA 就一定需要作大数分解。假设存在一种无须分解大数的算法,那它肯定可以修改成为大数分解算法。目前,RSA 的一些变种算法已被证明等价于大数分解。不管怎样,分解 n 是最显然的攻击方法。现在,人们已能分解 140 多个十进制位的大素数。因此,模数 n 必须选大一些,因具体适用情况而定。   


RSA 速度

        由于进行的都是大数计算,使得 RSA 最快的情况也比 DES 慢上 100 倍,无论是软件还是硬件实现。速度一直是RSA 的缺陷。一般来说只用于少量数据加密。RSA 的速度比对应同样安全级别的对称密码算法要慢 1000 倍左右。比起 DES 和其它对称算法来说,RSA 要慢得多。实际上 Bob 一般使用一种对称算法来加密他的信息,然后用 RSA来加密他的比较短的对称密码,然后将用 RSA 加密的对称密码和用对称算法加密的消息送给 Alice。这样一来对随机数的要求就更高了,尤其对产生对称密码的要求非常高,因为否则的话可以越过 RSA 来直接攻击对称密码。


RSA 缺点

        产生密钥很麻烦,受到素数产生技术的限制,因而难以做到一次一密。

        分组长度太大,为保证安全性,n 至少也要 600 bits 以上,使运算代价很高,尤其是速度较慢,较对称密码算法慢几个数量级; 且随着大数分解技术的发展,这个长度还在增加,不利于数据格式的标准化。 目前,SET(Secure Electronic Transaction)协议中要求 CA 采用 2048bit 的密钥,其他实体使用 1024bit 的密钥。


RSA 密钥分配

        和其它加密过程一样,对 RSA 来说分配公钥的过程是非常重要的。分配公钥的过程必须能够抵挡一个从中取代的攻击。假设 Eve 交给 Bob 一个公钥,并使 Bob 相信这是 Alice 的公钥,并且她可以截下 Alice 和 Bob 之间的信息传递,那么她可以将她自己的公钥传给 Bob,Bob 以为这是 Alice 的公钥。Eve 可以将所有 Bob 传递给 Alice 的消息截下来,将这个消息用她自己的密钥解密,读这个消息,然后将这个消息再用 Alice 的公钥加密后传给Alice。理论上 Alice 和 Bob 都不会发现 Eve 在偷听他们的消息。今天人们一般用数字认证来防止这样的攻击。


数字签名

        只有信息的发送者才能产生的别人无法伪造的一段数字串,这段数字串同时也是对信息的发送者发送信息真实性的一个有效证明。数字签名是非对称密钥加密技术与数字摘要技术的应用。简单地说,所谓数字签名就是附加在数据单元上的一些数据,或是对数据单元所作的密码变换。这种数据或变换允许数据单元的接收者用以确认数据单元的来源和数据单元的完整性并保护数据,防止被人(例如接收者)进行伪造。


数字签名的主要功能如下:

        保证信息传输的完整性、发送者的身份认证、防止交易中的抵赖发生。

        数字签名技术是将摘要信息用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的摘要信息,然后用对收到的原文产生一个摘要信息,与解密的摘要信息对比。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改,否则说明信息被修改过,因此数字签名能够验证信息的完整性。

        数字签名是个加密的过程,数字签名验证是个解密的过程。

        数字签名算法依靠公钥加密技术来实现的。在公钥加密技术里,每一个使用者有一对密钥:一把公钥和一把私钥。公钥可以自由发布,但私钥则秘密保存;还有一个要求就是要让通过公钥推算出私钥的做法不可能实现。


普通的数字签名算法包括三种算法:

  • 密码生成算法

  • 标记算法

  • 验证算法。


        通过 RSA 加密解密算法,我们可以实现数字签名的功能。我们可以用私钥对信息生成数字签名,再用公钥来校验数字签名,当然也可以反过来公钥签名,私钥校验。


信息传输

  • Alice 准备好要传送的数字信息(明文)。

  • Alice 对数字信息进行哈希(hash)运算,得到一个信息摘要。

  • Alice 用自己的私钥(SK)对信息摘要进行加密得到Alice 的数字签名,并将其附在数字信息上。

  • Alice 用Bob 的公钥(PK)对刚才随机产生的加密密钥进行加密,将加密后的密文传送给Bob

  • Bob 收到Alice 传送过来的密文,用自己的私钥(SK)对密文解密。

  • Bob 用Alice 的公钥(PK)对 Alice 的数字签名进行解密,得到信息摘要。

  • Bob 用相同的 hash 算法对收到的明文再进行一次 hash 运算,得到一个新的信息摘要。

  • Bob 将收到的信息摘要和新产生的信息摘要进行比较,如果一致,说明收到的信息没有被修改过。


采用数字签名,能完成这些功能:

  • 确认信息是由签名者发送的;

  • 确认信息自签名后到收到为止,未被修改过;

  • 签名者无法否认信息是由自己发送的。      

 

       但是上述过程存在问题,例如有用户 C 窃取 A 的电脑信息,把 B 的公钥修改成了自己的公钥,A 用此公钥加密信息发给 B,C 在中间截获这个数据就可以获取这些私密信息。怎么应对这种情况呢?签名证书就派上用场了。

        证书中心用自己的私钥,对每一个用户的、的公钥和一些相关信息一起加密,生成 "数字证书"(Digital Certificate)。发送者在发送信息之前如果想核对接受者的公钥,只需用 CA 的公钥对 B 的签名证书进行解密确认即可。


RSA 实战

流程图:

Java 非对称加密RSA理解和运用

Java RSA 工具类:

import sun.misc.BASE64Decoder;import sun.misc.BASE64Encoder;import org.apache.commons.codec.binary.Base64;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.NoSuchPaddingException;import java.io.*;import java.security.*;import java.security.interfaces.RSAPrivateKey;import java.security.interfaces.RSAPublicKey;import java.security.spec.InvalidKeySpecException;import java.security.spec.PKCS8EncodedKeySpec;import java.security.spec.X509EncodedKeySpec;
public class RSAPKCS8Util {
private static final String KEY_ALGORITHMS = "RSA";
/** * 签名算法 */private static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";//SHA1WithRSA 或者 MD5withRSA
/** * RSA最大加密明文大小 */private static final int MAX_ENCRYPT_BLOCK = 117;
/** * RSA最大解密密文大小 */private static final int MAX_DECRYPT_BLOCK = 128;
/** * 字节数据转字符串专用集合 */private static final char[] HEX_CHAR = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

/** * 初始化公钥和秘钥 * @param keySize 64的整倍数,范围:512~2048,建议是1024或2048 * @throws Exception */ private void initKey(Integer keySize) throws Exception { RSAPublicKey rsaPublicKey; RSAPrivateKey rsaPrivateKey; // 1、初始化密钥 KeyPairGenerator keyPairGenerator; try { keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHMS); keyPairGenerator.initialize(keySize); KeyPair keyPair = keyPairGenerator.generateKeyPair(); rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); System.out.println("Public Key : " + Base64.encodeBase64String(rsaPublicKey.getEncoded())); System.out.println("Private Key : " + Base64.encodeBase64String(rsaPrivateKey.getEncoded())); } catch (NoSuchAlgorithmException e) { throw new Exception("无此解密算法"); } }

/** * 从文件中输入流中加载公钥 * @param in 公钥输入流 * @throws Exception 加载公钥时产生的异常 */ public static RSAPublicKey loadPublicKey(InputStream in) throws Exception { try { BufferedReader br = new BufferedReader(new InputStreamReader(in)); String readLine = null; StringBuilder sb = new StringBuilder(); while ((readLine = br.readLine()) != null) { if (readLine.charAt(0) == '-') { continue; } else { sb.append(readLine); sb.append('\r'); } } return loadPublicKey(sb.toString()); } catch (IOException e) { throw new Exception("公钥数据流读取错误"); } catch (NullPointerException e) { throw new Exception("公钥输入流为空"); } }
/** * 从字符串中加载公钥 * @param publicKeyStr 公钥数据字符串 * @throws Exception 加载公钥时产生的异常 */ public static RSAPublicKey loadPublicKey(String publicKeyStr) throws Exception { try { BASE64Decoder base64Decoder = new BASE64Decoder(); byte[] buffer = base64Decoder.decodeBuffer(publicKeyStr); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHMS); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer); return (RSAPublicKey) keyFactory.generatePublic(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("无此算法"); } catch (InvalidKeySpecException e) { throw new Exception("公钥非法"); } catch (IOException e) { throw new Exception("公钥数据内容读取错误"); } catch (NullPointerException e) { throw new Exception("公钥数据为空"); } }
/** * 从文件中加载私钥 * @param in 私钥文件输入流 * @return 是否成功 * @throws Exception */ public static RSAPrivateKey loadPrivateKey(InputStream in) throws Exception { try { BufferedReader br = new BufferedReader(new InputStreamReader(in)); String readLine = null; StringBuilder sb = new StringBuilder(); while ((readLine = br.readLine()) != null) { if (readLine.charAt(0) == '-') { continue; } else { sb.append(readLine); sb.append('\r'); } } return loadPrivateKey(sb.toString()); } catch (IOException e) { throw new Exception("私钥数据读取错误"); } catch (NullPointerException e) { throw new Exception("私钥输入流为空"); } }
/** * 从字符串中加载私钥 * @param privateKeyStr 私钥数据字符串 * @throws Exception 加载私钥时产生的异常 */ public static RSAPrivateKey loadPrivateKey(String privateKeyStr) throws Exception { try { BASE64Decoder base64Decoder = new BASE64Decoder(); byte[] buffer = base64Decoder.decodeBuffer(privateKeyStr); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHMS); return (RSAPrivateKey) keyFactory.generatePrivate(keySpec); } catch (NoSuchAlgorithmException e) { throw new Exception("无此算法"); } catch (InvalidKeySpecException e) { throw new Exception("私钥非法"); } catch (IOException e) { throw new Exception("私钥数据内容读取错误"); } catch (NullPointerException e) { throw new Exception("私钥数据为空"); } }
/** * RSA签名 * @param content 待签名数据 * @param privateKey 商户私钥 * @param encode 字符集编码 * @return 签名值 */ public static String sign(String content, RSAPrivateKey privateKey, String encode) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey); signature.update(content.getBytes(encode));
byte[] signed = signature.sign();
return (new BASE64Encoder()).encodeBuffer(signed);
}
public static String sign(String content, RSAPrivateKey privateKey) throws Exception {
// 用私钥对信息生成数字签名 Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM); signature.initSign(privateKey); signature.update(content.getBytes("UTF-8")); byte[] signed = signature.sign(); return (new BASE64Encoder()).encodeBuffer(signed);
}
/** * RSA验签名检查 * @param content 待签名数据 * @param sign 签名值 * @param publicKey 分配给开发商公钥 * @param encode 字符集编码 * @return 布尔值 */ public static boolean doCheck(String content, String sign, RSAPublicKey publicKey, String encode) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey); signature.update(content.getBytes(encode));
return signature.verify(new BASE64Decoder().decodeBuffer(sign));
}
public static boolean doCheck(String content, String sign, RSAPublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey); signature.update(content.getBytes());
return signature.verify(new BASE64Decoder().decodeBuffer(sign));
  }
/** * 加密过程 * @param publicKey 公钥 * @param data 明文数据 * @return * @throws Exception 加密过程中的异常信息 */ public static String encrypt(RSAPublicKey publicKey, byte[] data) throws Exception { if (publicKey == null) { throw new Exception("加密公钥为空, 请设置"); } Cipher cipher = null; try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHMS); cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //cipher = Cipher.getInstance("RSA", new BouncyCastleProvider()); cipher.init(Cipher.ENCRYPT_MODE, publicKey);// 编码前设定编码方式及密钥 // byte[] output = cipher.doFinal(plainTextData);      //      return output;
int inputLen = data.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 对数据分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(data, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_ENCRYPT_BLOCK; } byte[] encryptedData = out.toByteArray(); out.close(); return new BASE64Encoder().encodeBuffer(encryptedData); } catch (NoSuchAlgorithmException e) { throw new Exception("无此加密算法"); } catch (NoSuchPaddingException e) { e.printStackTrace(); return null; } catch (InvalidKeyException e) { throw new Exception("加密公钥非法,请检查"); } catch (IllegalBlockSizeException e) { throw new Exception("明文长度非法"); } catch (BadPaddingException e) { throw new Exception("明文数据已损坏"); } }
/** * 解密过程 * @param privateKey 私钥 * @param encryptedData 密文数据 * @return 明文 * @throws Exception 解密过程中的异常信息 */ public static byte[] decrypt(RSAPrivateKey privateKey, byte[] encryptedData) throws Exception { if (privateKey == null) { throw new Exception("解密私钥为空, 请设置"); } Cipher cipher = null; try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHMS); cipher = Cipher.getInstance(keyFactory.getAlgorithm()); //cipher = Cipher.getInstance("RSA", new BouncyCastleProvider()); cipher.init(Cipher.DECRYPT_MODE, privateKey); // byte[] output = cipher.doFinal(cipherData); // return output; int inputLen = encryptedData.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(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[] decryptedData = out.toByteArray(); out.close(); return decryptedData; } catch (NoSuchAlgorithmException e) { throw new Exception("无此解密算法"); } catch (NoSuchPaddingException e) { e.printStackTrace(); return null; } catch (InvalidKeyException e) { throw new Exception("解密私钥非法,请检查"); } catch (IllegalBlockSizeException e) { throw new Exception("密文长度非法"); } catch (BadPaddingException e) { throw new Exception("密文数据已损坏"); } }
/** * 字节数据转十六进制字符串 * @param data 输入数据 * @return 十六进制内容 */ public static String byteArrayToString(byte[] data) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < data.length; i++) { //取出字节的高四位 作为索引得到相应的十六进制标识符 注意无符号右移 stringBuilder.append(HEX_CHAR[(data[i] & 0xf0) >>> 4]); //取出字节的低四位 作为索引得到相应的十六进制标识符 stringBuilder.append(HEX_CHAR[(data[i] & 0x0f)]); if (i < data.length - 1) { stringBuilder.append(' '); } } return stringBuilder.toString(); }
public static String byteToHex(byte[] source) { StringBuffer hexString = new StringBuffer(); for (int i = 0; i < source.length; i++) { String shaHex = Integer.toHexString(source[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); }
public static byte[] hexToBytes(String hexString) { if (hexString == null || hexString.equals("")) { return null; } hexString = hexString.toUpperCase(); int length = hexString.length() / 2; char[] hexChars = hexString.toCharArray(); byte[] d = new byte[length]; for (int i = 0; i < length; i++) { int pos = i * 2; d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
} return d; }
private static byte charToByte(char c) { return (byte) "0123456789ABCDEF".indexOf(c); }
}


实战 Demo:

Java 非对称加密RSA理解和运用

Java 非对称加密RSA理解和运用



(完)






以上是关于Java 非对称加密RSA理解和运用的主要内容,如果未能解决你的问题,请参考以下文章

再谈加密-RSA非对称加密的理解和使用

非对称加密算法-RSA算法

再谈加密-RSA非对称加密的理解和使用

java-信息安全-基于非对称加密,对称加密等理解HTTPS

Java使用非对称数据加密RSA加密解密

java加密算法入门-非对称加密详解