1. 程式人生 > >深入理解【數字簽名】原理與技術

深入理解【數字簽名】原理與技術

在騰訊工作已經第三週了,由於是支付業務,不免會涉及到加解密、數字簽名之類的安全手段,花了一天時間,學習了下數字簽名,整理髮出。

1.數字簽名概述

在討論數字簽名之前,我們先來說說簽名。簽名,即自己寫自己的名字,尤其為表示同意、認可、承擔責任或義務。在實際生活中,一些方式(如字跡,指紋等)一直被用作簽名者身份的證明。這是因為:簽名是可信的;不可偽造的;不可重用的;不可抵賴的。簽名即代表著同意,併產生法律效力,在法律上賦予了檔案以真實性。

在日漸來臨的數字化生活中,由於資訊在儲存,傳輸和處理等過程往往是在開放的通訊網路上進行的,所以資訊更容易受到來自外界或內部的竊聽、擷取、修改、偽造和重放等多種手段的攻擊。這時候,就需要數字簽名來保證訊息的來源、訊息的真實,並確保訊息傳送者不可以抵賴自己傳送的訊息,其與現實生活中籤名的作用大致相同。

2.數字簽名性質

數字簽名作為一種密碼技術,具有以下功能和性質:

1.防冒充

其他人不能偽造對訊息的簽名,因為私有金鑰只有簽名者自己知道,所以其他人不能偽造出正確的簽名結果。要求私鑰的持有人儲存好自己的私鑰。

2.防篡改

對於數字簽名,簽名和原有檔案己經形成一個混合的整體資料,不能篡改,從而保證了資料的完整性。

3.防重放

在數字簽名中,如果採用了對簽名報文新增流水號、時戳等技術,可以防止重放攻擊.

4.防抵賴

數字簽名可以鑑別身份,不可能冒充偽造。簽名者無法對自己作過的簽名抵賴。要防止接收者的抵賴,在數字簽名體制中,要求接收者返回一個自己簽名的表示收到的報文,給對方或者是第三方,或者引入第三方仲裁機制。這樣,雙方均不可抵賴。

5.機密性

有了機密性的保證,擷取攻擊就不會成功了。對要簽名的訊息進行適合的加密操作來保證機密性,這些涉及到加密或籤密理論。

3.對稱加密和非對稱加密

在學習數字簽名的工作原理之前,需要了解對稱加密和非對稱加密的原理。

對稱加密

所謂對稱加密,是採用對稱密碼編碼技術的加密措施,它的特點是檔案加密和解密都是使用相同的金鑰。

這種方法在密碼學中叫做對稱加密演算法,對稱加密演算法使用起來簡單快捷,金鑰較短,且破譯困難,除了資料加密標準(DES),另一個對稱金鑰加密系統是國際資料加密演算法(IDEA),它比DES的加密性好,而且對計算機功能要求也沒有那麼高。

非對稱加密

與對稱加密演算法不同,非對稱加密演算法需要兩個金鑰:公開金鑰(publickey)和私有金鑰(privatekey)。

公開金鑰與私有金鑰是一對,如果用公開金鑰對資料進行加密,只有用對應的私有金鑰才能解密;如果用私有金鑰對資料進行加密,那麼只有用對應的公開金鑰才能解密。

因為加密和解密使用的是兩個不同的金鑰,所以這種演算法叫作非對稱加密演算法。

4.資訊摘要

一般在進行數字簽名時,需要先對檔案使用HASH演算法計算其資訊摘要,然後對該摘要值進行數字簽名。

資訊摘要:對資料進行處理,得到一段固定長度的結果,其特點:

1、輸出長度固定。即輸出長度和輸入長度無關。

2、不可逆。即由輸出資料理論上不能推匯出輸入資料

4、對輸入資料敏感。當輸入資料變化極小時,輸出資料也會發生明顯的變化

5、防碰撞。即不同的資料資料得到相同輸出資料的可能性極低。

由於資訊摘要具有上述特點,一般保證資料的完整性,對一個大檔案進行摘要運算,得到其摘要值。通過網路或者其他渠道傳輸後,通過驗證其摘要值,確定大檔案本身有沒有發生變化。

5.數字簽名原理

數字簽名實現的具體原理:

1.將報文按雙方約定的HASH演算法計算得到一個固定位數的報文摘要。在數學上保證,只要改動報文中任何一位,重新計算出的報文摘要值就會與原先的值不相符。這樣就保證了報文的不可更改性。

2.將該報文摘要值用傳送者的私人金鑰加密即稱數字簽名,然後連同原報文和數字證書(包含公鑰)一起傳送給接收者。

3.接收方收到數字簽名後,用同樣的HASH演算法對報文計算摘要值,然後將數字簽名用傳送者的公鑰進行解密,並與報文摘要值相比較,如相等則說明報文確實來自所稱的傳送者。

上述過程可以借用下圖完美詮釋:

為了防止公鑰在傳輸過程中被調包,需要證書中心(簡稱CA)為公鑰做認證。證書中心用自己的私鑰,對公鑰和一些相關資訊一起加密,生成"數字證書"(Digital Certificate),客戶端用CA的公鑰解開數字證書,從而保證公鑰的真實性。

5.數字簽名演算法—RSA

RSA是目前計算機密碼學中最經典演算法,也是目前為止使用最廣泛的數字簽名演算法,RSA數字簽名演算法的金鑰實現與RSA的加密演算法是一樣的,演算法的名稱都叫RSA。金鑰的產生和轉換都是一樣的,包括在售的所有SSL數字證書、程式碼簽名證書、文件簽名以及郵件簽名大多都採用RSA演算法進行加密。

RSA是目前最有影響力的公鑰加密演算法,它能夠抵抗到目前為止已知的絕大多數密碼攻擊,已被ISO推薦為公鑰資料加密標準。

RSA數字簽名演算法主要包括MD和SHA兩種演算法,例如我們熟知的MD5和SHA-256即是這兩種演算法中的一類,具體如下表格分佈:

示例程式碼:

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class HelloRSA {

    private final static String SIGNATURE_ALGORITHM = "RSA";
    private final static String KEY_ALGORITHM = "SHA256withRSA"; // MD5withRSA
    private final static String src = "Hello World";

    public static void main(String[] args) {
        jdkRSA();
    }

    public static void jdkRSA() {
        try {
            //1.初始化金鑰
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM);
            keyPairGenerator.initialize(512); // 位(64的整數倍)
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic(); //公鑰
            RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)keyPair.getPrivate(); //私鑰

            //2.執行簽名
            //PKCS8EncodedKeySpec類表示私鑰的ASN.1編碼。
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            //簽名
            Signature signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(src.getBytes());
            byte[] result = signature.sign();
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" sign : " + bytesToHexString(result));

            //3.驗證簽名
            //X509EncodedKeySpec類表示根據ASN.1型別SubjectPublicKeyInfo編碼的公鑰的ASN.1編碼。
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
            keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //驗籤
            signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(src.getBytes());
            boolean bool = signature.verify(result);
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" verify : " + bool);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * byte[] 轉 16進位制
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

}

6.數字簽名演算法—DSA

DSA全稱Digital Signature Algorithm,DSA只是一種演算法,和RSA不同之處在於它不能用作加密和解密,也不能進行金鑰交換,只用於簽名,所以它比RSA要快很多,其安全性與RSA相比差不多。DSA的一個重要特點是兩個素數公開,這樣,當使用別人的p和q時,即使不知道私鑰,你也能確認它們是否是隨機產生的,還是作了手腳。RSA演算法卻做不到。

具體演算法種類如下圖:

示例程式碼:

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class HelloDSA {

    private final static String SIGNATURE_ALGORITHM = "DSA";
    private final static String KEY_ALGORITHM = "SHA1withDSA";
    private final static String src = "Hello World";

    public static void main(String[] args) {
        jdkDSA();
    }

    public static void jdkDSA() {
        try {
            //1.初始化金鑰
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM);
            keyPairGenerator.initialize(512); // 位(64的整數倍)
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            DSAPublicKey dsaPublicKey = (DSAPublicKey)keyPair.getPublic(); //公鑰
            DSAPrivateKey dsaPrivateKey = (DSAPrivateKey)keyPair.getPrivate(); //私鑰

            //2.執行簽名
            //PKCS8EncodedKeySpec類表示私鑰的ASN.1編碼。
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(dsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            //簽名
            Signature signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(src.getBytes());
            byte[] result = signature.sign();
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" sign : " + bytesToHexString(result));

            //3.驗證簽名
            //X509EncodedKeySpec類表示根據ASN.1型別SubjectPublicKeyInfo編碼的公鑰的ASN.1編碼。
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(dsaPublicKey.getEncoded());
            keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //驗籤
            signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(src.getBytes());
            boolean bool = signature.verify(result);
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" verify : " + bool);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * byte[] 轉 16進位制
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

}

7.數字簽名演算法—ECDSA

ECDSA(橢圓曲線數字簽名演算法:Elliptic Curve Digital Signatrue Algorithm)是用於數字簽名,是ECC與DSA的結合,整個簽名過程與DSA類似,所不一樣的是簽名中採取的演算法為ECC,最後簽名出來的值也是分為r,s。而ECC(全稱Elliptic Curves Cryptography)是一種橢圓曲線密碼編碼學。

ECDH每次用一個固定的DH key,導致不能向前保密(forward secrecy),所以一般都是用ECDHE(ephemeral)或其他版本的ECDH演算法。ECDH則是基於ECC的DH( Diffie-Hellman)金鑰交換演算法。

ECC與RSA 相比,有以下的優點:

  • a. 相同金鑰長度下,安全效能更高,如160位ECC已經與1024位RSA、DSA有相同的安全強度。
  • b. 計算量小,處理速度快,在私鑰的處理速度上(解密和簽名),ECC遠 比RSA、DSA快得多。
  • c. 儲存空間佔用小 ECC的金鑰尺寸和系統引數與RSA、DSA相比要小得多, 所以佔用的儲存空間小得多。
  • d. 頻寬要求低使得ECC具有廣泛得應用前景。

具體演算法種類如下圖:

示例程式碼:

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class HelloECDSA {

    private final static String SIGNATURE_ALGORITHM = "EC";
    private final static String KEY_ALGORITHM = "SHA1withECDSA";
    private final static String src = "Hello World";

    public static void main(String[] args) {
        jdkEC();
    }

    public static void jdkEC() {
        try {
            //1.初始化金鑰
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(SIGNATURE_ALGORITHM);
            keyPairGenerator.initialize(256); // 位
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            ECPublicKey rsaPublicKey = (ECPublicKey)keyPair.getPublic(); //公鑰
            ECPrivateKey rsaPrivateKey = (ECPrivateKey)keyPair.getPrivate(); //私鑰

            //2.執行簽名
            //PKCS8EncodedKeySpec類表示私鑰的ASN.1編碼。
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
            KeyFactory keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            //簽名
            Signature signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(src.getBytes());
            byte[] result = signature.sign();
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" sign : " + bytesToHexString(result));

            //3.驗證簽名
            //X509EncodedKeySpec類表示根據ASN.1型別SubjectPublicKeyInfo編碼的公鑰的ASN.1編碼。
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
            keyFactory = KeyFactory.getInstance(SIGNATURE_ALGORITHM);
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            //驗籤
            signature = Signature.getInstance(KEY_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(src.getBytes());
            boolean bool = signature.verify(result);
            System.out.println("jdk "+SIGNATURE_ALGORITHM+" verify : " + bool);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * byte[] 轉 16進位制
     */
    private static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

}

參考文章:

openssl 摘要和簽名驗證指令dgst使用詳解

Java實現數字簽名

數字簽名演算法介紹和區