如何實(shí)現(xiàn)接口之間參數(shù)加密傳輸 - RSA算法對(duì)接口參數(shù)簽名及驗(yàn)簽

在不同的服務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市也殖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖崎岂,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪湾,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡途样,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跋炕,“玉大人赖晶,你說我怎么就攤上這事》茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵胳嘲,是天一觀的道長(zhǎng)扣草。 經(jīng)常有香客問我,道長(zhǎng)辰妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任蛙婴,我火速辦了婚禮尔破,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘懒构。我一直安慰自己,他們只是感情好痴脾,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滚朵,像睡著了一般。 火紅的嫁衣襯著肌膚如雪辕近。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天归粉,我揣著相機(jī)與錄音漏峰,去河邊找鬼。 笑死浅乔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的靖苇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悼枢,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼脾拆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起名船,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤包帚,失蹤者是張志新(化名)和其女友劉穎渔期,沒想到半個(gè)月后渴邦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡信峻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年瓮床,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了产镐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踢步。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖获印,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情玻孟,我是刑警寧澤鳍征,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站艳丛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏质礼。R本人自食惡果不足惜织阳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望造挽。 院中可真熱鬧弄痹,春花似錦饭入、人聲如沸肛真。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽历极。三九已至窄瘟,卻和暖如春趟卸,著一層夾襖步出監(jiān)牢的瞬間氏义,已是汗流浹背图云。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琼稻,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓鸠补,卻偏偏與公主長(zhǎng)得像嘀掸,于是被迫代替她去往敵國(guó)和親紫岩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子睬塌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355