1. 程式人生 > >利用SHA-1演算法和RSA祕鑰進行簽名驗籤(帶註釋)

利用SHA-1演算法和RSA祕鑰進行簽名驗籤(帶註釋)

 

背景介紹

1、SHA

安全雜湊演算法SHA (Secure Hash Algorithm)是美國國家標準和技術局釋出的國家標準FIPS PUB 180-1,一般稱為SHA-1。其對長度不超過264二進位制位的訊息產生160位的訊息摘要輸出,按512位元塊處理其輸入。

SHA是一種資料加密演算法,該演算法經過加密專家多年來的發展和改進已日益完善,現在已成為公認的最安全的雜湊演算法之一,並被廣泛使用。

該演算法的思想是接收一段明文,然後以一種不可逆的方式將它轉換成一段(通常更小)密文,也可以簡單的理解為取一串輸入碼(稱為預對映或資訊),並把它們轉化為長度較短、位數固定的輸出序列即雜湊值(也稱為資訊摘要或資訊認證程式碼)的過程。雜湊函式值可以說時對明文的一種“指紋”或是“摘要”所以對雜湊值的數字簽名就可以視為對此明文的數字簽名。

2、訊息摘要

定義:

訊息摘要(Message Digest)又稱為數字摘要(Digital Digest)。它是一個唯一對應一個訊息或文字的固定長度的值,它由一個單向Hash加密函式對訊息進行作用而產生。如果訊息在途中改變了,則接收者通過對收到訊息的新產生的摘要與原摘要比較,就可知道訊息是否被改變了。因此訊息摘要保證了訊息的完整性。

訊息摘要採用單向Hash函式將需加密的明文"摘要"成一串128bit的密文,這一串密文亦稱為數字指紋(Finger Print),它有固定的長度,且不同的明文摘要成密文,其結果總是不同的,而同樣的明文其摘要必定一致。這樣這串摘要便可成為驗證明文是否是"真身"的"指紋"了。

型別:

摘要:GOST3411KeccakMD2MD4MD5RIPEMD128RIPEMD160RIPEMD256RIPEMD320SHA-1SHA-224SHA-256SHA-384SHA-512SHA3TigerWhirlpool

3、公鑰和私鑰:

定義:

公鑰和私鑰就是俗稱的不對稱加密方式,是從以前的對稱加密(使用使用者名稱與密碼)方式的提高。

下面用電子郵件的方式說明一下原理:
使用公鑰與私鑰的目的就是實現安全的電子郵件,必須實現如下目的:

  • 1、我傳送給你的內容必須加密,在郵件的傳輸過程中不能被別人看到。
  • 2、必須保證是我傳送的郵件,不是別人冒充我的。
    要達到這樣的目標必須傳送郵件的兩人都有公鑰和私鑰。 

  • 公鑰: 就是給大家用的,你可以通過電子郵件釋出,可以通過網站讓別人下載,公鑰其實是用來加密/驗章用的。
  • 私鑰: 就是自己的,必須非常小心儲存,最好加上密碼,私鑰是用來解密/簽章,首先就Key的所有權來說,私鑰只有個人擁有。
  • 公鑰與私鑰的作用是: 用公鑰加密的內容只能用私鑰解密,用私鑰加密的內容只能用公鑰解密。 
  • 舉例:
      比如說,我要給你傳送一個加密的郵件。首先,我必須擁有你的公鑰,你也必須擁有我的公鑰。
      首先,我用你的公鑰給這個郵件加密,這樣就保證這個郵件不被別人看到,而且保證這個郵件在傳送過程中沒有被修改。你收到郵件後,用你的私鑰就可以解密,就能看到內容。
      其次我用我的私鑰給這個郵件加密,傳送到你手裡後,你可以用我的公鑰解密。因為私鑰只有我手裡有,這樣就保證了這個郵件是我傳送的。
      當A->B資料時,A會使用B的公鑰加密,這樣才能確保只有B能解開,否則普羅大眾都能解開加密的訊息,就是去了資料的保密性。驗證方面則是使用籤驗章的機制,A傳資料給大家時,會以自己的私鑰做簽章,如此所有收到訊息的人都可以用A的公鑰進行驗章,便可確認訊息是由A發出來的了。

型別:

  • 對稱金鑰演算法: AES, Blowfish, Camellia, CAST5, CAST6,ChaCha, DES, DESede, GOST28147, HC-128, HC-256, IDEA, ISAAC, Noekeon, RC2, RC4, RC5-32, RC5-64, RC6, Rijndael, Salsa20, SEED, Serpent, Skipjack, TEA/XTEA, Threefish, Tnepres, Twofish, VMPC and XSalsa20.
  • 對稱金鑰模式:CBCCFBCTSGOFBOFBOpenPGPCFBSIC(或CTR)
  • 對稱金鑰填充: ISO10126d2, ISO7816d4, PKCS-5/7, TBC, X.923, and Zero Byte.
  • 非對稱金鑰演算法: ElGamal, DSA, ECDSA, NaccacheStern and RSA (with blinding).
  • 非對稱金鑰填充/編碼:ISO9796d1, OAEP, and PKCS-1.

4、數字簽名

電子商務中資料傳輸的幾個安全性需求

  • 1、資料的保密性:用於防止非法使用者進入系統及合法使用者對系統資源的非法使用;通過對一些敏感的資料檔案進行加密來保護系統之間的資料交換,防止除接收方之外的第三方截獲資料及即使獲取檔案也無法得到其內容。如在電子交易中,避免遭到黑客的襲擊使信用卡資訊丟失的問題。
  • 2、資料的完整性:防止非法使用者對進行交換的資料進行無意或惡意的修改、插入,防止交換的資料丟失等。
  • 3、資料的不可否認性:對資料和資訊的來源進行驗證,以確保資料由合法的使用者發出;防止資料傳送方在發出資料後又加以否認;同時防止接收方在收到資料後又否認曾收到過此資料及篡改資料。

注: 上述需求對應於防火牆、加密、數字簽名、身份認證等技術,但其關鍵在於數字簽名技術。

數字簽名的含義

數字簽名是通過一個單向函式對要傳送的報文進行處理得到的用以認證報文來源並核實報文是否發生變化的一個字母數字串。

數字簽名的實現方法

實現數字簽名有很多方法,目前數字簽名採用較多的是公鑰加密技術,如基於RSA Date Security 公司的PKCS( Public Key Cryptography Standards )、Digital Signature Algorithm、x.509、PGP(Pretty Good Privacy)。

1994年美國標準與技術協會公佈了數字簽名標準(DSS)而使公鑰加密技術廣泛應用。公鑰加密系統採用的是非對稱加密演算法。

由SignerUtilities支援的簽名演算法

MD2withRSA, MD4withRSA,MD5withRSA, RIPEMD128withRSA, RIPEMD160withECDSA, RIPEMD160withRSA, RIPEMD256withRSA, SHA-1withRSA, SHA-224withRSA, SHA-256withRSAandMGF1, SHA-384withRSAandMGF1, SHA-512withRSAandMGF1, SHA-1withDSA, and SHA-1withECDSA

使用範例:(帶註釋)

SHA-1:

對於長度小於2^64位的訊息,SHA1會產生一個160位(40個字元)的訊息摘要。當接收到訊息的時候,這個訊息摘要可以用來驗證資料的完整性。在傳輸的過程中,資料很可能會發生變化,那麼這時候就會產生不同的訊息摘要。

SHA-1有如下特性:

  • 不可以從訊息摘要中復原資訊;
  • 兩個不同的訊息不會產生同樣的訊息摘要,(但會有1x10 ^ 48分之一的機率出現相同的訊息摘要,一般使用時忽略)。

利用SHA-1演算法和RSA祕鑰進行簽名驗籤:

程式碼如下:

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.util.Base64;

/**
 * @author: mmzsit
 * @date:   2018年10月24日
 * @Description:
 * 部落格地址:https://blog.mmzsblog.cn
 * @version V1.0
 */
public class EncryptUtil {

    public static void main(String[] args) {
        ObjectInputStream inputStream = null;

        //引數字串
        String userName="測試test0->1";
        String orderId="測試ID123456";
        String price="666";
        //構建用於簽名和傳輸的字串
        StringBuffer bufferStr =new StringBuffer();
        bufferStr.append("userName=").append(userName)
                .append("&orderId=").append(orderId)
                .append("&price=").append(price);
        //將構建的字串轉化為String型別
        String localStr =bufferStr.toString();

        //簽名演算法加密
        try {
            //隨機生成祕鑰對
            // 檢查是否存在這對金鑰,否則生成這些金鑰
            if (!areKeysPresent()) {
                // 使用RSA演算法生成一對金鑰,並存儲在指定路徑的指定檔案中
                generateKey();
            }

            //服務端數字簽名開始
            //第一步:用SHA-1算出原文的摘要
            byte[] shaDigest = shaEncrypt(localStr);
            System.out.println("原文字內容:\n"+localStr);
            String shaStr = new String(shaDigest,"UTF-8");
            System.out.println("原文字內容SHA-1演算法後:\n"+shaStr);

            //第二步:使用私鑰對原文進行加密
            //讀取檔案中的私鑰
            inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
            final PrivateKey privateKey = (PrivateKey) inputStream.readObject();
            //使用私鑰加密
            byte[] rsaBytes = rsaEncrypt(shaDigest,privateKey);

            //第三步:對密文進行BASE64編碼
            byte[] base64Str = Base64.getEncoder().encode(rsaBytes);
            String base64enCode=new String(base64Str,"UTF-8");
            System.out.println("加密後的內容:\n"+base64enCode);

            //簽名加密完成資料傳輸到客戶端

            //客戶端驗證簽名開始
            //獲取原文
            String receiveStr=localStr;
            //第一步:使用Base64解碼密文
            byte[] bese64Decoded =Base64.getDecoder().decode(base64enCode.getBytes("UTF-8"));

            //第二步:使用公鑰對祕聞進行解碼
            //讀取檔案中的公鑰
            inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
            final PublicKey publicKey = (PublicKey) inputStream.readObject();
            //使用公鑰加密
            byte[] rsaDecode = rsaDecrypt(bese64Decoded,publicKey);
            //公鑰解密後的結果
            String base64denCode=new String(rsaDecode,"utf-8");
            System.out.println("公鑰解密後的結果:\n"+base64denCode);

            //第三步:驗籤
            //讀取解密後的摘要
            String sha1=Base64.getEncoder().encodeToString(rsaDecode);
            //使用Sha-1對原文計算摘要
            MessageDigest md =MessageDigest.getInstance("SHA-1");
            String sha2=Base64.getEncoder().encodeToString(md.digest(receiveStr.getBytes("utf-8")));
            //用Sha-1對原文計算摘要結果和解密後的明文比對
            if(sha1.equals(sha2)) {
                System.out.println("驗籤成功");
            } else {
                System.out.println("驗籤失敗");
            }

            System.out.println("字串sha1:\n"+sha1);
            System.out.println("字串sha2:\n"+sha2);

        }catch (Exception e) {
            e.printStackTrace();
        }


    }

    /**
     * 用於儲存加密演算法名稱的字串
     */
    public static final String ALGORITHM = "RSA";

    /**
     *
     * 用於儲存加密填充名稱的字串
     * 如果不填寫,那麼RSA/NONE/NoPadding就是Bouncy Castle 的預設 RSA 實現
     * 備用:
     */
    public static final String PADDING = "RSA/ECB/PKCS1Padding";

    /**
     * 用於儲存安全提供程式名稱的字串
     */
//    public static final String PROVIDER = "BC";

    /**
     * 用於儲存私鑰檔名稱的字串
     */
    public static final String PRIVATE_KEY_FILE = "d:/Temp/private.key";

    /**
     * 用於儲存公鑰檔名稱的字串
     */
    public static final String PUBLIC_KEY_FILE = "d:/Temp/public.key";

    /**
     * 假設最高安全性(即4096位RSA金鑰或更大)是非常安全
     * 使用1024位元組生成包含一對私鑰和公鑰的金鑰。
     * 將該組金鑰儲存在Prvate.key和Public.key檔案中。
     *
     * @throws NoSuchAlgorithmException
     * @throws IOException
     * @throws FileNotFoundException
     */
    public static void generateKey() {
        try {

            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM);
            //final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(ALGORITHM, PROVIDER);
            //金鑰位數
            keyGen.initialize(1024);
            //金鑰對
            final KeyPair key = keyGen.generateKeyPair();

            File privateKeyFile = new File(PRIVATE_KEY_FILE);
            File publicKeyFile = new File(PUBLIC_KEY_FILE);

            // 建立資料夾儲存私鑰
            if (privateKeyFile.getParentFile() != null) {
                privateKeyFile.getParentFile().mkdirs();
            }
            privateKeyFile.createNewFile();
            // 建立資料夾儲存公鑰
            if (publicKeyFile.getParentFile() != null) {
                publicKeyFile.getParentFile().mkdirs();
            }
            publicKeyFile.createNewFile();

            // 建立資料夾儲存公鑰
            ObjectOutputStream publicKeyOS = new ObjectOutputStream(
                    new FileOutputStream(publicKeyFile));
            publicKeyOS.writeObject(key.getPublic());
            publicKeyOS.close();

            // 建立資料夾儲存私鑰
            ObjectOutputStream privateKeyOS = new ObjectOutputStream(
                    new FileOutputStream(privateKeyFile));
            privateKeyOS.writeObject(key.getPrivate());
            privateKeyOS.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 檢查是否已生成一對公鑰和私鑰
     *
     * @return boolean 返回是否生成祕鑰對的標識
     */
    public static boolean areKeysPresent() {

        File privateKey = new File(PRIVATE_KEY_FILE);
        File publicKey = new File(PUBLIC_KEY_FILE);

        if (privateKey.exists() && publicKey.exists()) {
            return true;
        }
        return false;
    }

    /**
     * 使用公鑰解密資料
     *
     * @param text 待解密文字
     * @param key 公鑰
     * @return 解密文字
     * @throws java.lang.Exception
     */
    public static byte[] rsaDecrypt(byte[] text, PublicKey key) {
        byte[] cipherText = null;
        try {
            // 獲取RSA密碼物件並列印提供程式
//            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            final Cipher cipher = Cipher.getInstance(PADDING);
            //final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);

            // 使用公鑰,ENCRYPT_MODE表示為解密模式
            cipher.init(Cipher.DECRYPT_MODE, key);
            cipherText = cipher.doFinal(text);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cipherText;
    }

    /**
     * 使用私鑰加密資料
     *
     * @param text 待加密文字
     * @param key 私鑰
     * @return 加密後的資料
     * @throws java.lang.Exception
     */
    public static byte[] rsaEncrypt(byte[] text, PrivateKey key) {
        byte[] dectyptedText = null;
        try {
//            //獲取RSA密碼物件並列印提供程式
//            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            final Cipher cipher = Cipher.getInstance(PADDING);
            //final Cipher cipher = Cipher.getInstance(PADDING, PROVIDER);

            // 使用私鑰加密文字
            cipher.init(Cipher.ENCRYPT_MODE, key);
            dectyptedText = cipher.doFinal(text);

        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return dectyptedText;
    }

    /**
     * 使用sha-1對摘要進行加密
     * @param text 簽名的原始文字
     */
    public static byte[] shaEncrypt(String text) {
        //建立訊息摘要演算法的類
        MessageDigest md = null;
        //由於接收加密後的摘要的位元組陣列
        byte[] shaDigest = null;
        try {
            //使用getInstance("演算法")來獲得訊息摘要
            md = MessageDigest.getInstance("SHA-1");
            //將摘要轉化為UTF-8格式的位元組陣列
            byte[] plainText = text.getBytes("UTF-8");
            //使用指定的 byte 陣列更新摘要
            md.update(plainText);
            //得出SHA-1演算法加密後的結果
            shaDigest=md.digest();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return shaDigest;
    }
    
}

 

參考文章: