一、RSA简介
RSA加密是一种非对称加密。可以在不直接传递密钥的情况下,完成解密。这能够确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。是由一对密钥来进行加解密的过程,分别称为公钥和私钥。两者之间有数学相关,该加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。通常个人保存私钥,公钥是公开的(可能同时多人持有)。
二、加密和签名的区别
加密和签名都是为 了安全性考虑的,简单的说,加密是为了防止消息泄露,而签名是为了防止消息被篡改。
如上图所示,A系统需要发送信息到B系统,这个过程需要采用RSA非对称加密算法防止信息泄漏和 篡改。首先A、B首先都需要获得各自的一对秘钥(公钥和私钥),然后具体的操作过程如上图文字。
如果我们在实际应用中如果只使用加密和签名中的一种可能带来不同的问题。如果我们在消息传递的过程中只对数据加密不进行签名,这样虽然可保证被截获的消息 不会泄露,但是可以利用截获的公钥,将假信息进行加密,然后传递到B。如果只对信息签名,这样虽然可以防止消息被篡改,但是不能防止消息泄露,所以我们在实际项目中需要根据实际需求进行使用。
总结:公钥加密、私钥解密、私钥签名、公钥验签。
三、java代码工具类
3.1 RSA工具类RSAUtil
package com.jack.common.utils; import java.io.*; import java.security.*; import java.security.interfaces.RSAPrivateKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; /** * <p> * 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,<br/> * 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全 * </p> * */ public class RSAUtil { /** * 加密算法RSA */ public static final String KEY_ALGORITHM = "RSA"; private static final int MAX_ENCRYPT_BLOCK = 117; private static final int MAX_DECRYPT_BLOCK = 128; /** * method will close inputSteam * * @param pemFileInputStream * @return */ public static PublicKey loadPublicKey(InputStream pemFileInputStream) { return readPublicKey(readPEMFile(pemFileInputStream)); } /** * method will close inputSteam * * @param pkcs8PemFileInputStream * @return */ public static PrivateKey loadPrivateKey(InputStream pkcs8PemFileInputStream) { return readPrivateKey(readPEMFile(pkcs8PemFileInputStream)); } /** * * @param pemFile * @return */ public static PublicKey loadPublicKey(String pemFile) { return readPublicKey(readPEMFile(pemFile)); } /** * * @param pkcs8PemFile * @return */ public static PrivateKey loadPrivateKey(String pkcs8PemFile) { return readPrivateKey(readPEMFile(pkcs8PemFile)); } /** * read pem file, delete first and last line, sth. like:<br /> * <p> * -----BEGIN PUBLIC KEY----- -----END PUBLIC KEY----- * </p> * * @param filename * @return */ public static String readPEMFile(String filename) { try { return readPEMFile(new FileInputStream(filename)); } catch (FileNotFoundException e) { throw new RuntimeException(e); } } /** * method will close inputSteam * * @param stream * pem file inputstream * @return */ public static String readPEMFile(InputStream stream) { if (null != stream) { BufferedReader in = null; StringBuilder ret = new StringBuilder(); String line; try { in = new BufferedReader(new InputStreamReader(stream, "ASCII")); line = in.readLine(); while (null != line) { if (!(line.startsWith("-----BEGIN ") || line.startsWith("-----END "))) { ret.append(line); ret.append("\n"); } line = in.readLine(); } return ret.toString(); } catch (Exception ex) { throw new RuntimeException(ex); } finally { try { stream.close(); } catch (Exception ex) { ex.printStackTrace(); } if (null != in) { try { in.close(); } catch (Exception ex) { ex.printStackTrace(); } } } } return null; } /** * * @param pkcs8Base64String * <p> * delete the first and last line, sth. like below: -----BEGIN * PRIVATE KEY----- -----END PRIVATE KEY----- * </p> * @return */ public static PrivateKey readPrivateKey(String pkcs8Base64String) { byte[] keyByte = Base64Util.decode(pkcs8Base64String); try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyByte); RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(privateKeySpec); return privateKey; } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { throw new RuntimeException(ex); } } public static PublicKey readPublicKey(String pkcs8Base64String) { byte[] keyByte = Base64Util.decode(pkcs8Base64String); try { X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyByte); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); PublicKey publicKey = (PublicKey) keyFactory.generatePublic(x509KeySpec); return publicKey; } catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { throw new RuntimeException(ex); } } /** * <P> * 私钥解密 * </p> * * @param encryptedData * 已加密数据 * @param privateKey * @return */ public static byte[] decryptByPrivateKey(byte[] encryptedData, PrivateKey privateKey) { try { KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(encryptedData); } catch (Exception ex) { throw new RuntimeException(ex); } } /** * 公钥加密 * * @param content * 待加密内容 * @param publicKey * 公钥 * @param charset * content的字符集,如UTF-8, GBK, GB2312 * @return 密文内容 base64 ASCII */ public static String encryptByPublicKey(String content, PublicKey publicKey, String charset) { try { Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, publicKey); byte[] data = (charset == null || charset.isEmpty()) ? content.getBytes() : content.getBytes(charset); 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 = Base64Util.encode(out.toByteArray()); out.close(); return new String(encryptedData, "ASCII"); } catch (Exception e) { throw new RuntimeException(e); } } /** * 私钥加密 * * @param content * 待加密内容 * @param privateKey * 私钥 * @param charset * content的字符集,如UTF-8, GBK, GB2312 * @return 密文内容 base64 ASCII */ public static String encryptByPrivateKey(String content, PrivateKey privateKey, String charset) { try { Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] data = (charset == null || charset.isEmpty()) ? content.getBytes() : content.getBytes(charset); 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 = Base64Util.encode(out.toByteArray()); out.close(); return new String(encryptedData, "ASCII"); } catch (Exception e) { throw new RuntimeException(e); } } /** * 私钥解密 * * @param content * 待解密内容(base64, ASCII) * @param privateKey * 私钥 * @param charset * 加密前字符的字符集,如UTF-8, GBK, GB2312 * @return 明文内容 * @return */ public static String decryptByPrivateKey(String content, PrivateKey privateKey, String charset) { try { Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, privateKey); byte[] encryptedData = Base64Util.decode(content); 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 (charset == null || charset.isEmpty()) ? new String(decryptedData) : new String(decryptedData, charset); } catch (Exception e) { throw new RuntimeException(e); } } /** * 公钥解密 * * @param content * 待解密内容(base64, ASCII) * @param publicKey * 公钥 * @param charset * 加密前字符的字符集,如UTF-8, GBK, GB2312 * @return 明文内容 * @return */ public static String decryptByPublicKey(String content, PublicKey publicKey, String charset) { try { Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, publicKey); byte[] encryptedData = Base64Util.decode(content); 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 (charset == null || charset.isEmpty()) ? new String(decryptedData) : new String(decryptedData, charset); } catch (Exception e) { throw new RuntimeException(e); } } /** * * @param signType 如:SHA1withRSA * @param data * @param publicKey * @param sign base64字符串 * @return * @throws Exception */ public static boolean verifySign(String signType, byte[] data, PublicKey publicKey, String sign) throws Exception { Signature signature = Signature.getInstance(signType); signature.initVerify(publicKey); signature.update(data); return signature.verify(Base64Util.decode(sign)); } /** * * @param signType 如:SHA1withRSA * @param data * @param privateKey * @return base64字符串 * @throws Exception */ public static String sign(String signType, byte[] data, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance(signType); signature.initSign(privateKey); signature.update(data); return new String(Base64Util.encode(signature.sign())); } }
3.2 获得公钥和私钥的工具类ClientUtil
package com.jack.common.utils; import java.security.PrivateKey; import java.security.PublicKey; import java.util.HashMap; import java.util.Map; public class ClientUtil { public static final Map<String,String> keyMap = new HashMap<String,String>(); public static PrivateKey getPrivateKay(String fileTwo){ String privateKey = keyMap.get("privateKay"); if(privateKey == null || "".equals(privateKey)){ privateKey = RSAUtil.readPEMFile(fileTwo); keyMap.put("privateKay",privateKey); } PrivateKey privateKeyTwo = RSAUtil.readPrivateKey(privateKey); return privateKeyTwo; } public static PublicKey getPublicKey(String fileTwo,String orgCode){ String publicKey = keyMap.get(orgCode); if(publicKey == null || "".equals(publicKey)){ publicKey = RSAUtil.readPEMFile(fileTwo); keyMap.put(orgCode,publicKey); } PublicKey publicKeyTwo = RSAUtil.readPublicKey(publicKey); return publicKeyTwo; } }
3.3 添加签名方法
package com.jack.urgerobot.rsa.service; import com.jack.common.utils.ClientUtil; import com.jack.common.utils.RSAUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.security.PrivateKey; import java.security.PublicKey; @Service public class SignCommonService { private String signType = "SHA1WithRSA"; public String sign( String content) throws Exception{ PrivateKey privateKeyTwo = null; String fileTwo = this.getClass().getClassLoader().getResource("rsa/UserPri.key").getPath();//我们的私钥文件 privateKeyTwo = ClientUtil.getPrivateKay(fileTwo); String sign = RSAUtil.sign(signType, content.getBytes("utf-8"), privateKeyTwo); return sign; } }
注释:秘钥存放位置为项目的resources/rsa/下面
3.4 客户端测试发送数据 加密 签名
package com.jack.urgerobot.rsa.service; import com.aliyun.oss.common.utils.HttpUtil; import com.jack.common.utils.ClientUtil; import com.jack.common.utils.HttpUtils; import com.jack.common.utils.RSAUtil; import org.apache.ibatis.javassist.bytecode.stackmap.BasicBlock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.security.PublicKey; import java.util.HashMap; /** * @author zhenghao * @description * @date 2019/4/30 11:41 */ @Service public class testService { @Autowired private SignCommonService signCommonService; public void testScreit(){ try { String reqData = "{\n" + "\"caseCode\":\""+"12345"+"\",\n" + "\"billNo\":\""+"7687878"+"\",\n" + "}"; HashMap<String, String> pars = new HashMap<String, String>(); String fileTwo = this.getClass().getClassLoader().getResource("rsa/userPublic.key").getPath();//客户公钥 PublicKey publicKeyTwo = ClientUtil.getPublicKey(fileTwo, "mbt"); String entryContent = RSAUtil.encryptByPublicKey(reqData, publicKeyTwo, "UTF-8"); pars.put("serviceCode", "mbt"); pars.put("reqData", entryContent); pars.put("sign", signCommonService.sign(reqData)); HashMap<String,String> headerMap = new HashMap<>(); headerMap.put("Content-uid", "1"); headerMap.put("Content-pwkey", "1"); String r = HttpUtils.postWithHeaders("http://localhost:8090/sign/testSign", headerMap, pars); } catch (Exception e) { e.printStackTrace(); } } }
3.5 接收端 解密 验签
package com.jack.marketing.rsa.controller; import com.jack.common.utils.ServletUtils; import com.jack.marketing.rsa.service.RsaService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.HashMap; import java.util.Map; /** * @author zhenghao * @description * @date 2019/4/30 16:38 */ @Controller @RequestMapping("/sign") public class RsaController { @Autowired private RsaService rsaService; @RequestMapping("/testSign") public void addPartner(HttpServletRequest req, HttpServletResponse res,String orgCode, String sign, String reqData){ Map map = new HashMap<>(); map.put("orgCode", orgCode); map.put("sign", sign); map.put("reqData", reqData); String reqJson = rsaService.testSign(orgCode, sign, reqData); //获取传入的签名 ServletUtils.toJson(reqJson,req,res); } }
package com.jack.marketing.rsa.service; import com.jack.common.exception.ToUserException; import com.jack.common.utils.ClientUtil; import com.jack.common.utils.RSAUtil; import org.springframework.stereotype.Service; import java.security.PrivateKey; import java.security.PublicKey; import java.util.HashMap; /** * @author zhenghao * @description * @date 2019/4/30 15:44 */ @Service public class RsaService { private static String signType = "SHA1WithRSA"; private static PrivateKey privateKeyTwo = null; private static HashMap<String, PublicKey> publicKeyHashMap = new HashMap<String, PublicKey>(); public String testSign(String orgCode, String sign, String reqData){ String reqJson = null; System.out.println(orgCode + sign + reqData); boolean signOK = false; //自己私钥解密 if (privateKeyTwo == null) { String fileTwo = this.getClass().getClassLoader().getResource("rsa/userPri.key").getPath();//我们的私钥文件 privateKeyTwo = ClientUtil.getPrivateKay(fileTwo); } reqJson = RSAUtil.decryptByPrivateKey(reqData, privateKeyTwo, "utf-8"); PublicKey publicKeyTwo = publicKeyHashMap.get(orgCode); if (publicKeyTwo == null) { try { String pubfileTwo = this.getClass().getClassLoader().getResource("rsa/userPublic.key").getPath();//服务方的公钥文件 publicKeyTwo = ClientUtil.getPublicKey(pubfileTwo, orgCode); publicKeyHashMap.put(orgCode, publicKeyTwo); } catch (Exception e) { throw new ToUserException("上传公钥文件"); } } //对方公钥验签 try { signOK = RSAUtil.verifySign(signType, reqJson.getBytes("utf-8"), publicKeyTwo, sign); } catch (Exception e) { e.printStackTrace(); } if (signOK) { return reqJson; } else { throw new ToUserException("验证签名失败"); } } }
上面的代码已经通过测试,可以直接使用
四 小结
关于RSA的使用,之前在软考的时候了解过,后来在项目中也没有负责写过相关的模块,最近刚好项目中需要调用第三方提供的黑名单查询接口,看了下对方的文档需要使用RSA进行加密和签名,所以查了查相关的资料,将这块的东西写完了,在这总结下,在以后的项目中可以直接使用。