深入理解【數字簽名】原理與技術
在騰訊工作已經第三週了,由於是支付業務,不免會涉及到加解密、數字簽名之類的安全手段,花了一天時間,學習了下數字簽名,整理髮出。
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實現數字簽名
數字簽名演算法介紹和區