如何設計一個安全對外的接口?加簽驗簽了解一下

咱們在求職面試中,常常會被問到,如何設計一個安全對外的接口呢? 其實能夠回答這一點,加簽和驗籤,這將讓你的接口更加有安全。接下來,本文將和你們一塊兒來學習加簽和驗籤。從理論到實戰,加油哦~java

  • 密碼學相關概念
  • 加簽驗籤概念
  • 爲何須要加簽、驗籤
  • 加密算法簡介
  • 加簽驗籤相關API
  • 加簽驗籤代碼實現

本文已經收錄到我的github,文章有用的話,能夠給個star呀:git

https://github.com/whx123/JavaHomegithub

密碼學相關概念

明文、密文、密鑰、加密、解密

  • 明文:指沒有通過加密的信息/數據。
  • 密文:明文被加密算法加密以後,會變成密文,以確保數據安全。
  • 密鑰:是一種參數,它是在明文轉換爲密文或將密文轉換爲明文的算法中輸入的參數。密鑰分爲對稱密鑰與非對稱密鑰。
  • 加密:將明文變成密文的過程。
  • 解密:將密文還原爲明文的過程。

對稱加密、非對稱加密

  • 對稱加密:加密和解密使用相同密鑰的加密算法。 面試

  • 非對稱加密:非對稱加密算法須要兩個密鑰(公開密鑰和私有密鑰)。公鑰與私鑰是成對存在的,若是用公鑰對數據進行加密,只有對應的私鑰才能解密。算法

 

 

 

什麼是公鑰私鑰?

  • 公鑰與私鑰是成對存在的密鑰,若是用公鑰對數據進行加密,只有用對應的私鑰才能解密。
  • 其實,公鑰就是公開的祕鑰,私鑰就是要你私自保存好的祕鑰。
  • 非對稱加密算法須要有一對公私鑰~

假設你有一個文件,你用字母a加密,只有字母b才能解密;或者你用b加密,只有a才能解密,那麼a和b就是一對公私鑰。若是密鑰a公開,密鑰b你就要私自保存好啦,這時候密鑰a就是公鑰,密鑰b就是私鑰。相反,若是b公開,a就要保存好,這時候呢,祕鑰b就是公鑰,祕鑰a就是私鑰。安全

加簽驗籤概念

  • 「加簽」:用Hash函數把原始報文生成報文摘要,而後用私鑰對這個摘要進行加密,就獲得這個報文對應的數字簽名。一般來講呢,請求方會把「數字簽名和報文原文」一併發送給接收方。併發

  • 「驗籤」:接收方拿到原始報文和數字簽名後,用「同一個Hash函數」從報文中生成摘要A。另外,用對方提供的公鑰對數字簽名進行解密,獲得摘要B,對比A和B是否相同,就能夠得知報文有沒有被篡改過。app

 

 

爲何須要加簽驗籤

上小節中,加簽和驗籤咱們已經知道概念啦,那麼,爲何須要加簽和驗籤呢?有些朋友可能以爲,咱們不是用「公鑰加密,私鑰解密」就行了嘛?編輯器

接下來呢,舉個demo吧。函數

假設如今有A公司,要接入C公司的轉帳系統。在一開始呢,C公司把本身的公鑰寄給A公司,本身收藏好私鑰。A公司這邊的商戶,發起轉帳時,A公司先用C公司的公鑰,對請求報文加密,加密報文到達C公司的轉帳系統時,C公司就用本身的私鑰把報文揭開。假設在加密的報文在傳輸過程當中,被中間人Actor獲取了,他也鬱悶,由於他沒有私鑰,看着天鵝肉,又吃不了。原本想修改報文,給本身帳號轉一個億的,哈哈。這個實現方式看起來是完美無缺,穩得一匹的。

 

 

可是呢,若是一開始,C公司把公鑰發給公司A的時候,就被中間人Actor獲取到呢,醬紫就出問題了。

中間人Actor截取了C的公鑰,他把本身的公鑰發給了A公司,A誤覺得這就是C公司的公鑰。A在發起轉帳時,用Actor的公鑰,對請求報文加密,加密報文到在傳輸過程,Actor又截取了,這時候,他用本身的私鑰解密,而後修改了報文(給本身轉一個億),再用C的公鑰加密,發給C公司,C公司收到報文後,繼續用本身的私鑰解密。最後是否是A公司的轉帳帳戶損失了一個億呢~

 

 

C公司是怎麼區分報文是否是來自A呢,仍是被中間人修改過呢?爲了代表身份和報文真實性,這就須要「加簽驗籤」啦!

A公司把本身的公鑰也發送給C公司,私鑰本身保留着。在發起轉帳時,先用本身的私鑰對請求報文加簽,因而獲得本身的數字簽名。再把數字簽名和請求報文一塊兒發送給C公司。C公司收到報文後,拿A的公鑰進行驗籤,若是原始報文和數字簽名的摘要內容不一致,那就是報文被篡改啦~

有些朋友可能有疑問,假設A在發本身的公鑰給C公司的時候,也被中間人Actor截取了呢。嗯嗯,咱們來模擬一波Actor又截取了公鑰,看看Actor能幹出什麼事情來~哈哈

假設Actor截取到A的公鑰後,隨後也截取了到A發往C的報文。他截取到報文後,第一件想作的事確定是修改報文內容。可是若是單單修改原始報文是不能夠的,由於發過去C公司確定驗籤不過啦。可是呢,數字簽名彷佛解不開,由於消息摘要算法(hash算法)沒法逆向解開的,只起驗證的做用呢....

因此呢,公鑰與私鑰是用來加密與加密的,「加簽與驗籤是用來證實身份」,以避免被篡改的。

常見加密相關算法簡介

  • 消息摘要算法
  • 對稱加密算法
  • 非對稱加密算法
  • 國密算法

消息摘要算法:

  • 相同的明文數據通過相同的消息摘要算法會獲得相同的密文結果值。
  • 數據通過消息摘要算法處理,獲得的摘要結果值,是沒法還原爲處理前的數據的。
  • 數據摘要算法也被稱爲哈希(Hash)算法或散列算法。
  • 消息摘要算法通常用於簽名驗籤。

消息摘要算法主要分三類:MD(Message Digest,消息摘要算法)、SHA(Secure Hash Algorithm,安全散列算法)和MAC(Message Authentication Code,消息認證碼算法)。

MD家族算法

MD(Message Digest,消息摘要算法)家族,包括MD2,MD4,MD5。

  • MD2,MD4,MD5 計算的結果都是是一個128位(即16字節)的散列值,用於確保信息傳輸完整一致。
  • MD2的算法較慢但相對安全,MD4速度很快,但安全性降低,MD5則比MD4更安全、速度更快。
  • MD5被普遍應用於數據完整性校驗、數據(消息)摘要、數據加密等。
  • MD5,能夠被破解,對於須要高度安全性的數據,專家通常建議改用其餘算法,如SHA-2。2004年,證明MD5算法沒法防止碰撞攻擊,所以不適用於安全性認證,如SSL公開密鑰認證或是數字簽名等用途。

舉個例子,看看如何獲取字符串的MD5值吧:

public class MD5Test {

    public static void main(String[] args) throws UnsupportedEncodingException {
        String s = "123";
        byte[] result = getMD5Bytes(s.getBytes());
        StringBuilder stringBuilder = new StringBuilder();
        for (byte temp : result) {
            if (temp >= 0 && temp < 16) {
                stringBuilder.append("0");
            }
            stringBuilder.append(Integer.toHexString(temp & 0xff));
        }
        System.out.println(s + ",MD5加密後:" + stringBuilder.toString());
    }

    private static byte[] getMD5Bytes(byte[] content) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            return md5.digest(content);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException(e);
        }
    }
}

運行結果:

123,MD5加密後:202cb962ac59075b964b07152d234b70

ShA家族算法

SHA(Secure Hash Algorithm,安全散列算法),包括SHA-0、SHA-一、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384等)、SHA-3。它是在MD算法基礎上實現的,與MD算法區別在於「摘要長度」,SHA 算法的摘要「長度更長,安全性更高」

  • SHA-0發佈以後很快就被NSA撤回,由於含有會下降密碼安全性的錯誤,它是SHA-1的前身。
  • SHA-1在許多安全協議中廣爲使用,包括TLS、GnuPG、SSH、S/MIME和IPsec,是MD5的後繼者。
  • SHA-2包括SHA-22四、SHA-25六、SHA-38四、SHA-5十二、SHA-512/22四、SHA-512/256。它的算法跟SHA-1基本上類似,目前尚未出現明顯弱點。
  • SHA-3是2015年正式發佈,因爲對「MD5出現成功的破解」,以及對SHA-0和SHA-1出現理論上破解的方法,SHA-3應運而生。它與以前算法不一樣的是,它是可替換的加密散列算法。

SHA-一、SHA-2(SHA-256,SHA-512,SHA-224,SHA-384)等算法是比較經常使用的,咱們來看看跟MD5的對比吧

算法類型 摘要長度(bits) 最大輸入消息長度(bits) 碰撞攻擊(bits) 性能示例(MiB/s)
MD5 128 無限 ≤18(發現碰撞) 335
SHA-1 160 2^64 − 1 <63(發現碰撞) 192
SHA-224 224 2^64 − 1 112 139
SHA-256 256 2^64 − 1 128 139
SHA-384 384 2^128 − 1 192 154
SHA-512 512 2^128 − 1 256 154

MAC算法家族

MAC算法 MAC(Message Authentication Code,消息認證碼算法),是帶密鑰的Hash函數。輸入密鑰和消息,輸出一個消息摘要。它集合了MD和SHA兩大系列消息摘要算法。

  • MD 系列算法: HmacMD二、HmacMD4 和 HmacMD5 ;
  • SHA 系列算法:HmacSHA一、HmacSHA22四、HmacSHA25六、HmacSHA384 和 HmacSHA512 。

對稱加密算法

加密和解密使用「相同密鑰」的加密算法就是對稱加密算法。常見的對稱加密算法有AES、3DES、DES、RC五、RC6等。

DES

數據加密標準(英語:Data Encryption Standard,縮寫爲 DES)是一種對稱密鑰加密塊密碼算法。DES算法的入口參數有三個:Key、Data、Mode。

  • Key: 7個字節共56位,是DES算法的工做密鑰;
  • Data: 8個字節64位,是要被加密或被解密的數據;
  • Mode: 加密或解密。

3DES

三重數據加密算法(英語:Triple Data Encryption Algorithm,又稱3DES(Triple DES),是一種對稱密鑰加密塊密碼,至關因而對每一個數據塊應用三次數據加密標準(DES)算法。

AES

AES,高級加密標準(英語:Advanced Encryption Standard),在密碼學中又稱Rijndael加密法,是美國聯邦政府採用的一種區塊加密標準。

  • 採用對稱分組密碼體制,密鑰長度爲 128 位、 192 位、256 位,分組長度128位
  • 相對於DES ,AES具備更好的 安全性、效率 和 靈活性。

非對稱加密算法

非對稱加密算法須要兩個密鑰:公鑰和私鑰。公鑰與私鑰是成對存在的,若是用公鑰對數據進行加密,只有用對應的私鑰才能解密。主要的非對稱加密算法有:RSA、Elgamal、DSA、D-H、ECC。

RSA算法

  • RSA加密算法是一種非對稱加密算法,普遍應用於加密和數字簽名
  • RSA算法原理:兩個大素數的乘積進行因式分解卻極其困難,所以能夠將乘積公開做爲加密密鑰。
  • RSA是被研究得最普遍的公鑰算法,從提出到如今,經歷了各類攻擊的考驗,廣泛認爲是目前最優秀的公鑰方案之一。

DSA

  • DSA(Digital Signature Algorithm,數字簽名算法),也是一種非對稱加密算法。
  • DSA和RSA區別在,DSA僅用於數字簽名,不能用於數據加密解密。其安全性和RSA至關,但其性能要比RSA好。

ECC 算法

  • ECC(Elliptic Curves Cryptography,橢圓曲線密碼編碼學),基於橢圓曲線加密。
  • Ecc主要優點是,在某些狀況下,它比其餘的方法使用更小的密鑰,好比RSA加密算法,提供至關的或更高等級的安全級別。
  • 它的一個缺點是,加密和解密操做的實現比其餘機制時間長 (相比RSA算法,該算法對CPU 消耗嚴重)。

國密算法

國密即國家密碼局認定的國產密碼算法。爲了保障商用密碼的安全性,國家商用密碼管理辦公室制定了一系列密碼標準,即SM1,SM2,SM3,SM4等國密算法。

SM1

  • SM1,爲對稱加密算法,加密強度爲128位,基於硬件實現。
  • SM1的加密強度和性能,與AES至關。

SM2

  • SM2主要包括三部分:簽名算法、密鑰交換算法、加密算法
  • SM2用於替換RSA加密算法,基於ECC,效率較低。

SM3

  • SM3,即國產消息摘要算法。
  • 適用於商用密碼應用中的數字簽名和驗證,消息認證碼的生成與驗證以及隨機數的生成。

SM4

  • SM4是一個分組算法,用於無線局域網產品。
  • 該算法的分組長度爲128比特,密鑰長度爲128比特。
  • 加密算法與密鑰擴展算法都採用32輪非線性迭代結構。
  • 解密算法與加密算法的結構相同,只是輪密鑰的使用順序相反,解密輪密鑰是加密輪密鑰的逆序。
  • 它的功能相似國際算法的DES。

加簽驗籤相關Java的API

這個小節先介紹一下加簽驗籤須要用到的API吧~

加簽相關API

- java.security.Signature.getInstance(String algorithm); //根據對應算法,初始化簽名對象
- KeyFactory.getInstance(String algorithm);// 根據對應算法,生成KeyFactory對象
- KeyFactory.generatePrivate(KeySpec keySpec); //生成私鑰
- java.security.Signature.initSign(PrivateKey privateKey) //由私鑰,初始化加簽對象
- java.security.Signature.update(byte[] data)  //把原始報文更新到加簽對象
- java.security.Signature.sign();//加簽

「Signature.getInstance(String algorithm);」

  • 根據對應算法,初始化簽名對象
  • algorithm參數能夠取SHA256WithRSA或者MD5WithRSA等參數,SHA256WithRSA表示生成摘要用的是SHA256算法,簽名加簽用的是RSA算法

「KeyFactory.getInstance(String algorithm);」

  • 根據對應算法,生成KeyFactory對象,好比你的公私鑰用的是RSA算法,那麼就傳入RSA

「KeyFactory.generatePrivate(KeySpec keySpec)」

  • 生成私鑰,加簽用的是私鑰哈,因此須要經過KeyFactory先構造一個私鑰對象。

「Signature.initSign(PrivateKey privateKey)」

  • 加簽用的是私鑰,因此傳入私鑰,初始化加簽對象

「Signature.update(byte[] data)」

  • 把原始報文更新到加簽對象

「java.security.Signature.sign();」

  • 進行加簽操做

驗籤相關API

- java.security.Signature.getInstance(String algorithm); //根據對應算法,初始化簽名對象
- KeyFactory.getInstance(String algorithm);// 根據對應算法,生成KeyFactory對象
- KeyFactory.generatePublic(KeySpec keySpec); //生成公鑰
- java.security.Signature.initVerify(publicKey); //由公鑰,初始化驗籤對象
- java.security.Signature.update(byte[] data)  //把原始報文更新到驗籤對象
- java.security.Signature.verify(byte[] signature);//驗籤

「Signature.getInstance(String algorithm)」

  • 根據對應算法,初始化簽名對象,注意驗籤和加簽是須要用相同的algorithm算法參數哦~

「KeyFactory.getInstance(String algorithm);」

  • 根據對應算法,生成KeyFactory對象

「KeyFactory.generatePublic(KeySpec keySpec);」

  • 生成公鑰,驗籤用的是公鑰,經過KeyFactory先構造一個公鑰對象

「Signature.initVerify(publicKey);」

  • 公鑰驗籤,因此傳入公鑰對象參數,初始化驗籤對象

「Signature.update(byte[] data)」

  • 把原始報文更新到加簽對象

「Signature.verify(byte[] signature);」

  • 進行驗籤操做

加簽驗籤代碼實現

前幾個小節討論完概念,是時候上代碼實戰了,我這邊用的是SHA-256做爲摘要算法,RSA做爲簽名驗籤算法,以下:

package pattern;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 * 加簽驗籤demo
 *  @Author 撿田螺的小男孩
 */
public class SignatureTest {
    //公鑰字符串
    private static final String PUBLIC_KEY_STR = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDaJzVjC5K6kbS2YE2fiDs6H8pB\n" +
            "JFDGEYqqJJC9I3E0Ebr5FsofdImV5eWdBSeADwcR9ppNbpORdZmcX6SipogKx9PX\n" +
            "5aAO4GPesroVeOs91xrLEGt/arteW8iSD+ZaGDUVV3+wcEdci/eCvFlc5PUuZJou\n" +
            "M2XZaDK4Fg2IRTfDXQIDAQAB";
    //私鑰字符串
    private static final String PRIVATE_KEY_STR = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANonNWMLkrqRtLZg\n" +
            "TZ+IOzofykEkUMYRiqokkL0jcTQRuvkWyh90iZXl5Z0FJ4APBxH2mk1uk5F1mZxf\n" +
            "pKKmiArH09floA7gY96yuhV46z3XGssQa39qu15byJIP5loYNRVXf7BwR1yL94K8\n" +
            "WVzk9S5kmi4zZdloMrgWDYhFN8NdAgMBAAECgYA9bz1Bn0i68b2KfqRdgOfs/nbe\n" +
            "0XNN1DLQp2t7WDfRCg01iI1zPkZgyFVZWtI85f5/uIrLs5ArLosL1oNuqqc0nNne\n" +
            "CvJK+ZxvA98Hx3ZqYTzDnleR054YhofL5awbhSciYVic204DOG1rhSsYWMqtX7J7\n" +
            "3geoWL7TYdMfYXcCAQJBAPMMKsz6ZJh98EeQ1tDG5gpAGWFQkYNrxZDelP/LjeO0\n" +
            "TP3XkQnIpcaZoCs7V/rRGRGMWwQ2BUdc/01in89ZZ5ECQQDlx2oBc1CtOAm2UAhN\n" +
            "1xWrPkZWENQ53wTrwXO4qbTGDfBKon0AehLlGCSqxQ71aufLkNO7ZlX0IHTAlnk1\n" +
            "TvENAkAGSEQ69CXxgx/Y2beTwfBkR2/gghKg0QJUUkyLqBlMz3ZGAXJwTE1sqr/n\n" +
            "HiuSAiGhwH0ByNuuEotO1sPGukrhAkAMK26a2w+nzPL+u+hkrwKPykGRZ1zGH+Cz\n" +
            "19AYNKzFXJGgclCqiMydY5T1knBDYUEbj/UW1Mmyn1FvrciHoUG1AkAEMEIuDauz\n" +
            "JabEAU08YmZw6OoDGsukRWaPfjOEiVhH88p00veM1R37nwhoDMGyEGXVeVzNPvk7\n" +
            "cELg28MSRzCK";


    public static void main(String[] args) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidKeySpecException {
        //原始報文
        String plain = "歡迎你們關注個人公衆號,撿田螺的小男孩";
        //加簽
        byte[] signatureByte = sign(plain);
        System.out.println("原始報文是:" + plain);
        System.out.println("加簽結果:");
        System.out.println(new BASE64Encoder().encode(signatureByte));
        //驗籤
        boolean verifyResult = verify(plain, signatureByte);
        System.out.println("驗簽結果:" + verifyResult);
    }

    /**
     * 加簽方法
     * @param plain
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws UnsupportedEncodingException
     * @throws SignatureException
     */
    private static byte[] sign(String plain) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, SignatureException {
        //根據對應算法,獲取簽名對象實例
        Signature signature = Signature.getInstance("SHA256WithRSA");
        //獲取私鑰,加簽用的是私鑰,私鑰通常是在配置文件裏面讀的,這裏爲了演示方便,根據私鑰字符串生成私鑰對象
        PrivateKey privateKey = getPriveteKey(PRIVATE_KEY_STR);
        //初始化簽名對象
        signature.initSign(privateKey);
        //把原始報文更新到對象
        signature.update(plain.getBytes("UTF-8"));
        //加簽
        return signature.sign();
    }

    /**
     * 驗籤方法
     * @param plain
     * @param signatureByte
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws IOException
     * @throws SignatureException
     * @throws InvalidKeySpecException
     */
    private static boolean verify(String plain, byte[] signatureByte) throws NoSuchAlgorithmException, InvalidKeyException, IOException, SignatureException, InvalidKeySpecException {
        //獲取公鑰
        PublicKey publicKey = getPublicKey(PUBLIC_KEY_STR);
        //根據對應算法,獲取簽名對象實例
        Signature signature = Signature.getInstance("SHA256WithRSA");
        //初始化簽名對象
        signature.initVerify(publicKey);
        //把原始報文更新到簽名對象
        signature.update(plain.getBytes("UTF-8"));
        //進行驗籤
        return signature.verify(signatureByte);
    }

    private static PublicKey getPublicKey(String publicKeyStr) throws InvalidKeySpecException, IOException {
        PublicKey publicKey = null;
        try {
            java.security.spec.X509EncodedKeySpec bobPubKeySpec = new java.security.spec.X509EncodedKeySpec(
                    new BASE64Decoder().decodeBuffer(publicKeyStr));
            // RSA對稱加密算法
            java.security.KeyFactory keyFactory;
            keyFactory = java.security.KeyFactory.getInstance("RSA");
            // 生成公鑰對象
            publicKey = keyFactory.generatePublic(bobPubKeySpec);
           } catch (NoSuchAlgorithmException e) {
             e.printStackTrace();
            }
        return publicKey;
      }

    private static PrivateKey getPriveteKey(String privateKeyStr) {
        PrivateKey privateKey = null;
        PKCS8EncodedKeySpec priPKCS8;
        try {
            priPKCS8 = new PKCS8EncodedKeySpec(new BASE64Decoder().decodeBuffer(privateKeyStr));
            KeyFactory keyf = KeyFactory.getInstance("RSA");
            privateKey = keyf.generatePrivate(priPKCS8);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            e.printStackTrace();
        }
        return privateKey;
    }
}
 

「運行結果:」

原始報文是:歡迎你們關注個人公衆號,撿田螺的小男孩
加簽結果:
Oz15/aybGe42eGHbc+iMoSYHSCc8tfRskTVjjGSTPD4HjadL0CC5JUWNUW0WxHjUb4MvxWo2oeWE
Qw0+m61d+JgBMto/TWcVDcgwL/AbObsbWdQ6E/fVRqG13clkE8MyKsjt9Z7tcbwpycYTv0rUR4co
rndAVfBdtv5KeV+OXqM=
驗簽結果:true

Reference

[1]維基百科:https://zh.wikipedia.org/wiki/Wikipedia:%E9%A6%96%E9%A1%B5

[2]百度百科:https://baike.baidu.com/

[3]經常使用消息摘要算法簡介:https://cloud.tencent.com/developer/article/1584742

[4]淺談常見的七種加密算法及實現:https://juejin.im/post/5b48b0d7e51d4519962ea383#heading-12

[5]【易錯概念】國密算法SM1(SCB2)、SM二、SM三、SM四、SM七、SM九、ZUC:https://www.jianshu.com/p/8c3657a1769f

相關文章
相關標籤/搜索