背景介紹
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),它有固定的長度,且不同的明文摘要成密文,其結果總是不同的,而同樣的明文其摘要必定一致。這樣這串摘要便可成為驗證明文是否是"真身"的"指紋"了。
類型:
摘要:GOST3411
,Keccak
,MD2
,MD4
,MD5
,RIPEMD128
,RIPEMD160
,RIPEMD256
,RIPEMD320
,SHA-1
,SHA-224
,SHA-256
,SHA-384
,SHA-512
,SHA3
,Tiger
和Whirlpool
。
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
andXSalsa20
. - 對稱密鑰模式:
CBC
,CFB
,CTS
,GOFB
,OFB
,OpenPGPCFB
和SIC(或CTR)
。 - 對稱密鑰填充:
ISO10126d2
,ISO7816d4
,PKCS-5/7
,TBC
,X.923
, andZero Byte
. - 非對稱密鑰算法:
ElGamal
,DSA
,ECDSA
,NaccacheStern
andRSA (with blinding)
. - 非對稱密鑰填充/編碼:
ISO9796d1
,OAEP
, andPKCS-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; } }