在不同的服務(wù)器或系統(tǒng)之間通過API接口進(jìn)行交互時(shí)智末,兩個(gè)系統(tǒng)系統(tǒng)之間必須進(jìn)行身份的驗(yàn)證,以滿足安全上的防抵賴和防篡改。
通常情況下為了達(dá)到以上所描述的目的里逆,我們首先向到使用非對(duì)稱加密算法對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行簽名以驗(yàn)證發(fā)送方的身份,而RSA
加密算法是目前比較通用的非對(duì)稱加密算法用爪,經(jīng)常被用有數(shù)字簽名及數(shù)據(jù)加密原押,且很多編程語言的標(biāo)準(zhǔn)庫中都自帶有RSA
算法的庫,所以實(shí)現(xiàn)起來也是相對(duì)簡(jiǎn)單的项钮。
本文將使用Java標(biāo)準(zhǔn)庫來實(shí)現(xiàn) RAS密鑰對(duì) 的生成及數(shù)字簽名和驗(yàn)簽班眯,密鑰對(duì)中的私鑰由請(qǐng)求方系統(tǒng)妥善保管,不能泄露烁巫;而公鑰則交由系統(tǒng)的響應(yīng)方用于驗(yàn)證簽名署隘。
RAS使用私鑰對(duì)數(shù)據(jù)簽名,使用公鑰進(jìn)行驗(yàn)簽亚隙,生成RSA密鑰對(duì)的代碼如下:
package com.pyy.demo.util;
import lombok.extern.slf4j.Slf4j;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
/**
* ========================
* 生成RSA公/私鑰對(duì)
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/10/29 9:19
* Version: v1.0
* ========================
*/
@Slf4j
public class GeneratorRSAKey {
/**
* 初始化密鑰磁餐,生成公鑰私鑰對(duì)
*
* @return Object[]
* @throws NoSuchAlgorithmException
*/
private Object[] initSecretkey() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(512);// 可以理解為:加密后的密文長(zhǎng)度,實(shí)際原文要小些 越大 加密解密越慢
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
log.info("初始化密鑰,生成公鑰私鑰對(duì)完畢");
String publicKey = Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded());
String privateKey = Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded());
log.debug("---------------------publicKey----------------------");
log.debug(publicKey);
log.debug("---------------------privateKey----------------------");
log.debug(privateKey);
Object[] keyPairArr = new Object[2];
keyPairArr[0] = publicKey;
keyPairArr[1] = privateKey;
return keyPairArr;
}
public static void main(String[] args) throws NoSuchAlgorithmException {
GeneratorRSAKey generatorRSAKey = new GeneratorRSAKey();
generatorRSAKey.initSecretkey();
}
}
運(yùn)行如上代碼诊霹,控制臺(tái)將輸出一對(duì)RSA密鑰對(duì)羞延,復(fù)制該密鑰對(duì)并保存,后面我們將會(huì)用到:
10:39:06.885 [main] INFO com.pyy.demo.util.GeneratorRSAKey - 初始化密鑰伴箩,生成公鑰私鑰對(duì)完畢
10:39:06.887 [main] DEBUG com.pyy.demo.util.GeneratorRSAKey - ---------------------publicKey----------------------
10:39:06.887 [main] DEBUG com.pyy.demo.util.GeneratorRSAKey - MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAInOxrvSXfk8Q5v3ZCw+7gzMqab4Eh2V8GA08qyHcjU0uMgeZb+qfeipkT1XBIhku8Uzp5cHIceajpZYXsSKra8CAwEAAQ==
10:39:06.887 [main] DEBUG com.pyy.demo.util.GeneratorRSAKey - ---------------------privateKey----------------------
10:39:06.888 [main] DEBUG com.pyy.demo.util.GeneratorRSAKey - MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAic7Gu9Jd+TxDm/dkLD7uDMyppvgSHZXwYDTyrIdyNTS4yB5lv6p96KmRPVcEiGS7xTOnlwchx5qOllhexIqtrwIDAQABAkAjGeUq8CF5m20JLBF656iQ4AySd/t9R7TLfJEXewSPInUGGOCZ5anP+tzvFrfqvpRIWlbJNp8EWOda+UHqq8HpAiEA8QLkZuEEvR+Gf0PdkHM2DXSehgKUtndRYIIVOzzeASUCIQCSYMtKkg58SUmkGC6Vic2iwK1vrS1jrUh+lBeCnOONQwIgRgx5Jg2wuuc2yDaJZzqVM0P57ylA3+e+Fza3xQfj3qECIApNn+GW2EgtTG6teRHzijLrhwm2UdyTROgL+n+qFWZLAiBNRhs0yM7Lxxz36PJvnd5piheiKJ4vdNsm8GC2r6h1EA==
然后我們需要一個(gè)可以生成簽名字符串及驗(yàn)證簽名的工具類鄙漏,這樣可以方便接口的開發(fā),代碼如下:
package com.pyy.demo.util;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* ========================
* RSA簽名工具類
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/10/29 9:33
* Version: v1.0
* ========================
*/
public class JdkSignatureUtil {
private final static String RSA = "RSA";
private final static String MD5_WITH_RSA = "MD5withRSA";
/**
* 執(zhí)行簽名
*
* @param rsaPrivateKey 私鑰
* @param src 參數(shù)內(nèi)容
* @return 簽名后的內(nèi)容怔蚌,base64后的字符串
* @throws InvalidKeyException InvalidKeyException
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws SignatureException SignatureException
*/
public static String executeSignature(String rsaPrivateKey, String src) throws InvalidKeyException,
NoSuchAlgorithmException, InvalidKeySpecException, SignatureException {
// base64解碼私鑰
byte[] decodePrivateKey = Base64.getDecoder().decode(rsaPrivateKey.replace("\r\n", ""));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decodePrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Signature signature = Signature.getInstance(MD5_WITH_RSA); //用md5生成內(nèi)容摘要巩步,再用RSA的私鑰加密,進(jìn)而生成數(shù)字簽名
signature.initSign(privateKey);
signature.update(src.getBytes());
// 生成簽名
byte[] result = signature.sign();
// base64編碼簽名為字符串
return Base64.getEncoder().encodeToString(result);
}
/**
* 驗(yàn)證簽名
*
* @param rsaPublicKey 公鑰
* @param sign 簽名
* @param src 參數(shù)內(nèi)容
* @return 驗(yàn)證結(jié)果
* @throws NoSuchAlgorithmException NoSuchAlgorithmException
* @throws InvalidKeySpecException InvalidKeySpecException
* @throws InvalidKeyException InvalidKeyException
* @throws SignatureException SignatureException
*/
public static boolean verifySignature(String rsaPublicKey, String sign, String src) throws NoSuchAlgorithmException,
InvalidKeySpecException, InvalidKeyException, SignatureException {
// base64解碼公鑰
byte[] decodePublicKey = Base64.getDecoder().decode(rsaPublicKey.replace("\r\n", ""));
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(decodePublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Signature signature = Signature.getInstance(MD5_WITH_RSA);
signature.initVerify(publicKey);
signature.update(src.getBytes());
// base64解碼簽名為字節(jié)數(shù)組
byte[] decodeSign = Base64.getDecoder().decode(sign);
// 驗(yàn)證簽名
return signature.verify(decodeSign);
}
}
接著我們來基于SpringBoot編寫一個(gè)簡(jiǎn)單的demo椅野,看看如何實(shí)際的使用RSA算法對(duì)接口參數(shù)進(jìn)行簽名及驗(yàn)簽籍胯。發(fā)送方代碼如下:
package com.pyy.demo.controller;
import com.pyy.demo.util.JdkSignatureUtil;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
/**
* ========================
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/10/29 10:27
* Version: v1.0
* ========================
*/
public class ClientController {
/**
* 私鑰
*/
private final static String PRIVATE_KEY = "MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAic7Gu9Jd+TxDm/dkLD7uDMyppvgSHZXwYDTyrIdyNTS4yB5lv6p96KmRPVcEiGS7xTOnlwchx5qOllhexIqtrwIDAQABAkAjGeUq8CF5m20JLBF656iQ4AySd/t9R7TLfJEXewSPInUGGOCZ5anP+tzvFrfqvpRIWlbJNp8EWOda+UHqq8HpAiEA8QLkZuEEvR+Gf0PdkHM2DXSehgKUtndRYIIVOzzeASUCIQCSYMtKkg58SUmkGC6Vic2iwK1vrS1jrUh+lBeCnOONQwIgRgx5Jg2wuuc2yDaJZzqVM0P57ylA3+e+Fza3xQfj3qECIApNn+GW2EgtTG6teRHzijLrhwm2UdyTROgL+n+qFWZLAiBNRhs0yM7Lxxz36PJvnd5piheiKJ4vdNsm8GC2r6h1EA==";
public static String sender() throws InvalidKeySpecException, NoSuchAlgorithmException,
InvalidKeyException, SignatureException, UnsupportedEncodingException {
// 請(qǐng)求所需的參數(shù)
Map<String, Object> requestParam = new HashMap<>(16);
requestParam.put("username", "×××");
requestParam.put("sex", "男");
requestParam.put("city", "北京");
requestParam.put("status", 1);
// 將需要簽名的參數(shù)內(nèi)容按參數(shù)名的字典順序進(jìn)行排序竟闪,并拼接為字符串
StringBuilder sb = new StringBuilder();
requestParam.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(entry ->
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&")
);
String paramStr = sb.toString().substring(0, sb.length() - 1);
// 使用私鑰生成簽名字符串
String sign = JdkSignatureUtil.executeSignature(PRIVATE_KEY, paramStr);
// 對(duì)簽名字符串進(jìn)行url編碼
String urlEncodeSign = URLEncoder.encode(sign, StandardCharsets.UTF_8.name());
// 請(qǐng)求參數(shù)中需帶上簽名字符串
requestParam.put("sign", urlEncodeSign);
// 發(fā)送請(qǐng)求
return postJson("http://localhost:8080/server", requestParam);
}
/**
* 發(fā)送數(shù)據(jù)類型為json的post請(qǐng)求
*
* @param url
* @param param
* @param <T>
* @return
*/
public static <T> String postJson(String url, T param) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
HttpEntity<T> httpEntity = new HttpEntity<>(param, headers);
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);
return responseEntity.getBody();
}
public static void main(String[] args) {
try {
System.out.println(sender());
} catch (Exception e) {
e.printStackTrace();
}
}
}
接收方代碼如下:
package com.pyy.demo.controller;
import com.pyy.demo.util.JdkSignatureUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.Comparator;
import java.util.Map;
/**
* ========================
* Created with IntelliJ IDEA.
* User:pyy
* Date:2019/10/29 10:33
* Version: v1.0
* ========================
*/
@RestController
public class ServerController {
/**
* 公鑰
*/
private final static String PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAInOxrvSXfk8Q5v3ZCw+7gzMqab4Eh2V8GA08qyHcjU0uMgeZb+qfeipkT1XBIhku8Uzp5cHIceajpZYXsSKra8CAwEAAQ==";
@PostMapping(value = "/server")
public String server(@RequestBody Map<String, Object> param) throws InvalidKeySpecException,
NoSuchAlgorithmException, InvalidKeyException, SignatureException, UnsupportedEncodingException {
// 從參數(shù)中取出簽名字符串并刪除芒炼,因?yàn)閟ign不參與字符串拼接
String sign = (String) param.remove("sign");
// 對(duì)簽名字符串進(jìn)行url解碼
String decodeSign = URLDecoder.decode(sign, StandardCharsets.UTF_8.name());
// 將簽名的參數(shù)內(nèi)容按參數(shù)名的字典順序進(jìn)行排序,并拼接為字符串
StringBuilder sb = new StringBuilder();
param.entrySet().stream().sorted(Comparator.comparing(Map.Entry::getKey)).forEach(entry ->
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&")
);
String paramStr = sb.toString().substring(0, sb.length() - 1);
// 使用公鑰進(jìn)行驗(yàn)簽
boolean result = JdkSignatureUtil.verifySignature(PUBLIC_KEY, decodeSign, paramStr);
if (result) {
return "簽名驗(yàn)證成功";
}
return "簽名驗(yàn)證失敗鲸湃,非法請(qǐng)求";
}
}
編寫完以上代碼后子寓,啟動(dòng)SpringBoot項(xiàng)目暗挑,然后運(yùn)行發(fā)送方的代碼斜友,控制臺(tái)輸出結(jié)果如下:
10:41:50.013 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP POST http://localhost:8080/server
10:41:50.020 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
10:41:50.033 [main] DEBUG org.springframework.web.client.RestTemplate - Writing [{city=北京, sex=男, sign=W0X%2FIsX7RzEagGUwODi3PxBnFTNNmxnjlqYX4j7liS%2BDqmcLkXXgYAQQ3bXc8D7lmvaV30Jh1J9IshAHegP3zg%3D%3D, username=×××, status=1}] as "application/json;charset=UTF-8"
10:41:50.168 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK
10:41:50.169 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [java.lang.String] as "text/plain;charset=UTF-8"
這里只是給出一個(gè)簡(jiǎn)單實(shí)例,實(shí)際項(xiàng)目開發(fā)中需要根據(jù)特定的業(yè)務(wù)需求烹看,進(jìn)行修改洛史。
源碼:https://github.com/pyygithub/RSA.git
參考:https://blog.51cto.com/zero01/2331063