为什么80%的码农都做不了架构师?>>>   

前面讲了理论和操作,现在看下最关键的代码实现,不废话,直接代码说明。本文最要介绍通过JCE及默认实现开发的一个RSA的工具类,主要包括两部分:加解密,签名验签和公私钥的各做加载方法。

公私钥的加载

作为一个工具类,会在各做场景和情况使用,所有要考虑比较全的公私加载方法,主要包括以下几种常见情况:

  • 从秘钥文件加载(*.pem格式)
  • 从二进制数据加载
  • 从base64数据加载
  • 从hex字符串数据加载
  • 从证书文件加载
  • 从keystore文件加载(jks和pfx)

公钥的加载

  • 秘钥数据加载 公钥秘钥数据的形态,常见的一般是二进制byte数组,base64,秘钥文件,hex字符串数据几种方式。其本质都是byte数组加载。代码如下:
/*** 加载公钥(byte数组)** @param publicKeyBytes*            公钥bytes* @param keyAlgorithm*            算法,如:RSA* @param providerName*            可为空* @return 公钥对象*/public static PublicKey loadPublicKey(byte[] publicKeyBytes, String keyAlgorithm, String providerName) {String algorithm = Strings.isBlankDefault(keyAlgorithm, DEFAULT_KEY_ALGO);try {// KEY_ALGORITHM 指定的加密算法KeyFactory keyFactory = null;if (Strings.isBlank(providerName)) {keyFactory = KeyFactory.getInstance(algorithm);} else {keyFactory = KeyFactory.getInstance(algorithm, providerName);}X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);return keyFactory.generatePublic(keySpec);} catch (Exception e) {throw new RuntimeException("load publicKey fail. " + e.getMessage());}}/*** 加载公钥(base64)** @param publicKeyBase64*            秘钥为BASE64编码* @param keyAlgorithm*            如:RSA* @param providerName*            可以为空,如:BC* @return*/public static PublicKey loadPublicKey(String publicKeyBase64, String keyAlgorithm, String providerName) {byte[] publicKeyBytes = Encodes.decodeBase64(publicKeyBase64);return loadPublicKey(publicKeyBytes, keyAlgorithm, providerName);}/*** 加载公钥(文件pem)** @param publicKeyFile*            公钥秘钥文件,内容为BASE64编码* @param keyAlgorithm*            如:RSA* @param providerName*            可以为空,如:BC* @return*/public static PublicKey loadPublicKey(File publicKeyFile, String keyAlgorithm, String providerName) {byte[] publicKeyBytes = null;try {publicKeyBytes = Encodes.decodeBase64(FileUtils.readFileToString(publicKeyFile));} catch (Exception e) {throw new RuntimeException("加载公钥文件内容失败:" + e.getMessage());}return loadPublicKey(publicKeyBytes, keyAlgorithm, providerName);}    ```  * 通过证书或keystore加载```java/*** 证书文件加载* * @param springResourceUri* @return*/public static PublicKey loadPublicKeyFromCert(String springResourceUri) {InputStream in = null;try {Resource resource = new DefaultResourceLoader().getResource(springResourceUri);in = resource.getInputStream();CertificateFactory cf = CertificateFactory.getInstance("X.509");X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);return x509.getPublicKey();} catch (Exception e) {throw new RuntimeException("加载公钥文件内容失败:" + e.getMessage());} finally {IOUtils.closeQuietly(in);}}/*** 证书pem格式加载* * @param pemCert* @return*/public static PublicKey loadPublicKeyFromCertWithPem(String pemCert) {InputStream in = null;try {CertificateFactory cf = CertificateFactory.getInstance("X.509");in = new ByteArrayInputStream(Encodes.decodeBase64(pemCert));X509Certificate x509 = (X509Certificate) cf.generateCertificate(in);return x509.getPublicKey();} catch (Exception e) {throw new RuntimeException("加载公钥文件内容失败:" + e.getMessage());} finally {IOUtils.closeQuietly(in);}}/*** 从keystore加载公钥* * @param keystoreUri* @param keystoreType* @param keystorePassword* @return*/public static PublicKey loadPublicKeyFromKeyStore(String keystoreUri, String keystoreType,String keystorePassword) {InputStream in = null;try {KeyStore keyStore = KeyStore.getInstance(keystoreType);Resource resource = new DefaultResourceLoader().getResource(keystoreUri);in = resource.getInputStream();keyStore.load(in, keystorePassword.toCharArray());Enumeration<String> enumas = keyStore.aliases();String keyAlias = null;if (enumas.hasMoreElements()) {keyAlias = enumas.nextElement();}return keyStore.getCertificate(keyAlias).getPublicKey();} catch (Exception e) {throw new RuntimeException("通过keystore加载证书公钥失败:" + e.getMessage());} finally {IOUtils.closeQuietly(in);}}

私钥的加载

私钥的加载默认同公钥,这里不在贴代码,从公钥稍微调整下就OK

加密和解密

这里以RSA算法作为介绍,在JAVA实现中,需要注意几点:

  • 不管明文长度是多少,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 的异常)
  • 如果采用其他Provider,每段长度可能不同,如BC(Provider --> Security.addProvider(new BouncyCastleProvider());) 提供的加密算法能够支持到的 RSA 明文长度最长为密钥长度。

特别说明:一般来说Cipher的创建时初始化会比较慢(在我imac下,150ms左右),在性能要求的场景,有同学会担心性能问题,所以会采用ThreadLocal做内存缓存,我开始也这样做了,但是实际测试后发现,其他JCE的默认Provider已做了,而且做得很彻底(不是在ThreadLocal中缓存Cipher实例,而是缓存了Cipher实例初始化的较为耗时的操作和资源,kao~,NB),所以,针对非对称加密场景,不用考虑Cipher 的线程缓存~

公钥加密

/*** 公钥加密** @param plainBytes*            明文bytes* @param publicKey*            公钥* @return 密文bytes*/public static byte[] encryptByPublicKey(byte[] plainBytes, PublicKey publicKey) {ByteArrayOutputStream out = null;try {// Cipher在非对称加密场景,没有必要自己做thread cache,JCE实现的provider默认做了~Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());cipher.init(Cipher.ENCRYPT_MODE, publicKey);int inputLen = plainBytes.length;out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 对数据分段解密int keyLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8;int plainLength = keyLength - 11;while (inputLen - offSet > 0) {if (inputLen - offSet > plainLength) {cache = cipher.doFinal(plainBytes, offSet, plainLength);} else {cache = cipher.doFinal(plainBytes, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * plainLength;}byte[] encryptedData = out.toByteArray();return encryptedData;} catch (Exception e) {throw new RuntimeException("publicKey encrypt fail:" + e.getMessage());} finally {IOUtils.closeQuietly(out);}}public static String encryptByPublicKeyBase64(String plainText, PublicKey publicKey, String charset) {return Encodes.encodeBase64(encryptByPublicKey(getBytes(plainText, charset), publicKey));}public static String encryptByPublicKeyBase64(String plainText, PublicKey publicKey) {return encryptByPublicKeyBase64(plainText, publicKey, DEFAULT_CHARSET);}public static String encryptByPublicKeyHex(String plainText, PublicKey publicKey, String charset) {return Encodes.encodeHex(encryptByPublicKey(getBytes(plainText, charset), publicKey));}public static String encryptByPublicKeyHex(String plainText, PublicKey publicKey) {return encryptByPublicKeyHex(plainText, publicKey, DEFAULT_CHARSET);}

私钥解密

/*** 私钥解密** @param encryptedBytes*            密文数据bytes* @param privateKey*            私钥* @return 明文数据bytes*/public static byte[] decryptByPrivateKey(byte[] encryptedBytes, PrivateKey privateKey) {ByteArrayOutputStream out = null;try {Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());cipher.init(Cipher.DECRYPT_MODE, privateKey);int inputLen = encryptedBytes.length;out = new ByteArrayOutputStream();int offSet = 0;byte[] cache;int i = 0;// 对数据分段解密int keyLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;while (inputLen - offSet > 0) {if (inputLen - offSet > keyLength) {cache = cipher.doFinal(encryptedBytes, offSet, keyLength);} else {cache = cipher.doFinal(encryptedBytes, offSet, inputLen - offSet);}out.write(cache, 0, cache.length);i++;offSet = i * keyLength;}byte[] decryptedData = out.toByteArray();return decryptedData;} catch (Exception e) {throw new RuntimeException("privateKey decrypt fail:" + e.getMessage());} finally {IOUtils.closeQuietly(out);}}public static String decryptByPrivateKeyBase64(String encryptedBase64, PrivateKey privateKey, String charset) {byte[] plainBytes = decryptByPrivateKey(Encodes.decodeBase64(encryptedBase64), privateKey);try {return new String(plainBytes, Strings.isBlankDefault(charset, DEFAULT_CHARSET));} catch (UnsupportedEncodingException e) {throw new RuntimeException("不支持的字符集:" + e.getMessage());}}public static String decryptByPrivateKeyHex(String plainHex, PrivateKey privateKey, String charset) {byte[] plainBytes = decryptByPrivateKey(Encodes.decodeHex(plainHex), privateKey);try {return new String(plainBytes, Strings.isBlankDefault(charset, DEFAULT_CHARSET));} catch (UnsupportedEncodingException e) {throw new RuntimeException("不支持的字符集:" + e.getMessage());}}

签名和验签

签名和验签基本无需考虑性能问题,我在imac上实际测试一般Signature的初始化在2ms左右,除非要求极致性能,否则也不用做线程缓存。

私钥签名

/*** 私钥签名** @param dataBytes*            明文数据bytes* @param privateKey*            私钥 参考 loadPrivateKey方法* @param signatureAlgorithm*            签名算法 如:SHA1withRSA,MD5withRSA* @return 签名的二进制结果*/public static byte[] sign(@NotNull byte[] dataBytes, @NotNull PrivateKey privateKey,@Null String signatureAlgorithm) {String sa = Strings.isBlankDefault(signatureAlgorithm, SIGN_ALGO_SHA1);try {Signature signature = Signature.getInstance(sa);signature.initSign(privateKey);signature.update(dataBytes);return signature.sign();} catch (Exception e) {throw new RuntimeException("sign with " + sa + " fail:" + e.getMessage());}}public static String signBase64(@NotNull String data, @NotNull PrivateKey privateKey,@Null String signatureAlgorithm, @Null String dataCharset) {byte[] result = sign(getBytes(data, dataCharset), privateKey, signatureAlgorithm);return Encodes.encodeBase64(result);}public static String signBase64(@NotNull String data, @NotNull PrivateKey privateKey) {return signBase64(data, privateKey, null, null);}public static String signHex(@NotNull String data, @NotNull PrivateKey privateKey, @Null String signatureAlgorithm,@Null String dataCharset) {byte[] result = sign(getBytes(data, dataCharset), privateKey, signatureAlgorithm);return Encodes.encodeHex(result);}public static String signHex(@NotNull String data, @NotNull PrivateKey privateKey) {return signHex(data, privateKey, null, null);}

公钥验签

/*** 公钥验签** @param data*            明文数据bytes* @param signData*            签名数据bytes* @param publicKey*            公钥 参考 loadPublicKey(...)方法* @param signatureAlgorithm*            签名算法* @return*/public static boolean verify(byte[] data, byte[] signData, PublicKey publicKey, String signatureAlgorithm) {String sa = Strings.isBlankDefault(signatureAlgorithm, SIGN_ALGO_SHA1);try {Signature signature = Signature.getInstance(sa);signature.initVerify(publicKey);signature.update(data);return signature.verify(signData);} catch (Exception e) {throw new RuntimeException("verify with " + sa + " fail." + e.getMessage());}}public static boolean verifyBase64(String text, String signBase64, PublicKey publicKey, String signatureAlgorithm,String charset) {byte[] data = getBytes(text, charset);byte[] signData = Encodes.decodeBase64(signBase64);return verify(data, signData, publicKey, signatureAlgorithm);}public static boolean verifyBase64(String text, String signBase64, PublicKey publicKey) {return verifyBase64(text, signBase64, publicKey, null, null);}public static boolean verifyHex(String text, String signHex, PublicKey publicKey, String signatureAlgorithm,String charset) {byte[] data = getBytes(text, charset);byte[] signData = Encodes.decodeHex(signHex);return verify(data, signData, publicKey, signatureAlgorithm);}public static boolean verifyHex(String text, String signHex, PublicKey publicKey) {return verifyHex(text, signHex, publicKey, null, null);}

如果你实在要ThreadLocal

如果你选择的Provider不是JCE默认实现,并且这个实现还真没有做缓存,或者你在对称加密场景想做缓存(AES,ECS等场景Cipher初始化还是非常慢的~)。下面一个参考实现:


/*** RSA Cipher 线程缓存* * @author zhangpu*/
public class RSACipherCache {/*** 每个线程可以缓存最多100个不同秘钥对Cipher*/private static final int THREAD_CACHE_SIZE = 10;private static final ThreadLocal<LoadingCache<Key, Cipher>> threadLocal = new ThreadLocal<LoadingCache<Key, Cipher>>() {@Overrideprotected LoadingCache<Key, Cipher> initialValue() {/*** 设置一个按容量控制的内存缓存*/return CacheBuilder.newBuilder().recordStats().maximumSize(THREAD_CACHE_SIZE).build(new CacheLoader<Key, Cipher>() {@Overridepublic Cipher load(final Key key) throws Exception {Cipher cipher = null;try {cipher = Cipher.getInstance(key.getAlgorithm());if (PrivateKey.class.isAssignableFrom(key.getClass())) {// 私钥,在加解密场景用于解密cipher.init(Cipher.DECRYPT_MODE, key);} else {cipher.init(Cipher.ENCRYPT_MODE, key);}} catch (Exception e) {throw new RuntimeException(e);}return cipher;}});}};public static Cipher newCipher(final Key key) {if (key == null) {throw new RuntimeException("Key must be not null.");}return threadLocal.get().getUnchecked(key);}public static CacheStats getStats() {return threadLocal.get().stats();}private RSACipherCache() {}}

OK,以上基本够用~

转载于:https://my.oschina.net/acooly/blog/769977

Java非对称加密开发(三)-代码及说明相关推荐

  1. Java非对称加密KeyPairGenerator类

    Java加密的常用的加密算法类型有三种 1单向加密: 也就是不可逆的加密,例如MD5,SHA,HMAC 2对称加密: 也就是加密方和解密方利用同一个秘钥对数据进行加密和解密,例如DES,PBE等等 3 ...

  2. 【Jasypt】Java 轻量级加密工具实现代码数据库账号密码加密

    前言 对很多人来说,项目中习惯会把数据库的账号密码直接用明文写在配置文件中,其实这样并不是特别好,虽然是方便查看,但是也不太安全.所以这篇文章主要是一款轻量级加密工具的使用介绍. 参考资料 Jasyp ...

  3. JSON 接口如何实现 RSA 非对称加密与签名

    代码地址如下: http://www.demodashi.com/demo/14000.html 一.概述 1. 数字签名的作用:保证数据完整性,机密性和发送方角色的不可抵赖性,加密与签字结合时,两套 ...

  4. C#实现带盐值加密,适配JAVA Shiro加密

    C#实现带盐值加密,适配JAVA Shiro加密 前言 核心代码 Java代码 C#代码 注意 前言 工业领域上位机软件与管理系统通常使用不同编程语言实现,比如我们的上位机软件通常使用C#,而MES. ...

  5. 5分钟了解对称加密和非对称加密

    对称加密 对称加密是指加密和解密都是使用同一个密钥来进行的加密方式.这种加密方式的优点是加密和解密速度快,适合加密大量数据.常见的对称加密算法有 DES.AES 等. 很典型的一个场景就是当我们保存数 ...

  6. java上位机开发(开篇)

    [ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 目前对于java语言,很多人都认为太简单.其实这是不对的,因为某种意义上来说所,java语言就像 ...

  7. Java加密与解密笔记(三) 非对称加密

    非对称的特点是加密和解密时使用的是不同的钥匙.密钥分为公钥和私钥,用公钥加密的数据只能用私钥进行解密,反之亦然. 另外,密钥还可以用于数字签名.数字签名跟上文说的消息摘要是一个道理,通过一定方法对数据 ...

  8. Java代码实现非对称加密RSA算法示例

    非对称加密:有两把密钥:使用公钥加密,必须使用私钥解密:或者使用私钥加密,必须使用公钥解密 加解密核心类:Cipher 下面代码是使用RSA算法加解密的一个示例,实现过程包括:生成密钥对,把公钥和私钥 ...

  9. 非对称加密——RSA算法JAVA代码实践

    文章目录 说明 RSA加解密 测试代码 打印输出 说明 1:下面代码参考自<JAVA加密解密的艺术>,有部分修改,详见原理见原书 2:下面代码是RSA在JAVA中API级别的代码实现,具体 ...

  10. (二)Java网络编程之爆肝HTTP、HTTPS、TLS协议及对称与非对称加密原理!

    引言 在上篇文章中,已经讲明了当下计算机网络的基础知识,其中对网络体系结构.分层模型.TCP/IP协议簇.....等多方面内容进行了阐述,而在本章会分析到网络知识中另外两个大名鼎鼎的协议:HTTP/H ...

最新文章

  1. 清除扇区和低格哪个好_C++入门篇(四十五),结点删除与链表的清除
  2. 关于SQL视图的创建和使用方法
  3. Hook安卓项目内的字符串获取,用服务器的key value优先代替本地的key value
  4. SAP批次级别的意义及启用操作
  5. CNN+LSTM+CTC
  6. ]remove-duplicates-from-sorted-list-ii (删除)
  7. mysql数据库命令_新手入门MYSQL数据库命令大全
  8. 轻轻松松统计代码行数
  9. cad画流程图的插件_如何用cad画交互流程图
  10. python 持续集成 教程_dotnet 部署 github 的 Action 进行持续集成|简明python教程|python入门|python教程...
  11. 这段iframe代码可以盖住dropdownlist
  12. johnson算法(johnson算法最优顺序怎么算)
  13. ISO15693协议RFID读卡器模块HX829的韦根66(WG66)通信协议说明
  14. freeswitch呼叫系统
  15. vue图片裁剪:使用vue-cropper做图片裁剪
  16. ele-calendar 日历插件使用
  17. 马哥教育开学感想随笔
  18. 2018国赛数学建模B题一道工序代码
  19. 手机配件市场上的“隐形巨头”:80后长沙夫妻创办,IPO首日市值逼近600亿
  20. 华为云计算工程师证好考吗?

热门文章

  1. Android studio 报错 Unknown host 'jcenter.bintray.com'
  2. python urllib2详解及实例
  3. 基础集合论 第一章 6 并集
  4. pc微信登录扫码显示无法连接服务器,WeAuth微信小程序实现PC网站扫码授权登录...
  5. 【转载】身份证号码验证算法
  6. apache服务上配置https安全与域名请求
  7. Linux源码包和脚本安装包的安装方法
  8. 本地idea运行spark,A master URL must be set in your configuration
  9. 1052. Linked List Sorting (25)再
  10. Ajax方法实现登录页面