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

 

背景介紹

一、SHA

安全散列算法SHA (Secure Hash Algorithm)是美國國家標準和技術局發佈的國家標準FIPS PUB 180-1,通常稱爲SHA-1。其對長度不超過264二進制位的消息產生160位的消息摘要輸出,按512比特塊處理其輸入。java

SHA是一種數據加密算法,該算法通過加密專家多年來的發展和改進已日益完善,如今已成爲公認的最安全的散列算法之一,並被普遍使用。git

該算法的思想是接收一段明文,而後以一種不可逆的方式將它轉換成一段(一般更小)密文,也能夠簡單的理解爲取一串輸入碼(稱爲預映射或信息),並把它們轉化爲長度較短、位數固定的輸出序列即散列值(也稱爲信息摘要或信息認證代碼)的過程。散列函數值能夠說時對明文的一種「指紋」或是「摘要」因此對散列值的數字簽名就能夠視爲對此明文的數字簽名。算法

二、消息摘要

定義:

消息摘要(Message Digest)又稱爲數字摘要(Digital Digest)。它是一個惟一對應一個消息或文本的固定長度的值,它由一個單向Hash加密函數對消息進行做用而產生。若是消息在途中改變了,則接收者經過對收到消息的新產生的摘要與原摘要比較,就可知道消息是否被改變了。所以消息摘要保證了消息的完整性。數組

消息摘要採用單向Hash函數將需加密的明文"摘要"成一串128bit的密文,這一串密文亦稱爲數字指紋(Finger Print),它有固定的長度,且不一樣的明文摘要成密文,其結果老是不一樣的,而一樣的明文其摘要一定一致。這樣這串摘要即可成爲驗證實文是不是"真身"的"指紋"了。安全

類型:

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

三、公鑰和私鑰:

定義:

公鑰和私鑰就是俗稱的不對稱加密方式,是從之前的對稱加密(使用用戶名與密碼)方式的提升。ide

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

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

  • 公鑰: 就是給你們用的,你能夠經過電子郵件發佈,能夠經過網站讓別人下載,公鑰實際上是用來加密/驗章用的。
  • 私鑰: 就是本身的,必須很是當心保存,最好加上密碼,私鑰是用來解密/簽章,首先就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.

四、數字簽名

電子商務中數據傳輸的幾個安全性需求

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

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

數字簽名的含義

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

數字簽名的實現方法

實現數字簽名有不少方法,目前數字簽名採用較多的是公鑰加密技術,如基於RSA Date Security 公司的PKCS( Public Key Cryptography Standards )、Digital Signature Algorithm、x.50九、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;
    }
    
}

 

參考文章:

相關文章
相關標籤/搜索