密碼學進階(一):淺談常見的七種加密算法及實現(經典加密密碼算法實現)
前言
數字簽名、信息加密 是前后端開發都經常需要使用到的技術,應用場景包括了用戶登入、交易、信息通訊、oauth 等等,不同的應用場景也會需要使用到不同的簽名加密算法,或者需要搭配不一樣的簽名加密算法來達到業務目標。這里簡單的給大家介紹幾種常見的簽名加密算法和一些典型場景下的應用。
正文
1.數字簽名
數字簽名,簡單來說就是通過提供可鑒別的數字信息驗證自身身份的一種方式。一套數字簽名通常定義兩種互補的運算,一個用于簽名,另一個用于驗證。分別由發送者持有能夠代表自己身份的私鑰 (私鑰不可泄露),由接受者持有與私鑰對應的公鑰,能夠在接受到來自發送者信息時用于驗證其身份。
注意:圖中加密過程有別于公鑰加密,更多介紹戳這里。簽名最根本的用途是要能夠唯一 證明發送方的身份,防止中間人攻擊、CSRF跨域身份偽造。基于這一點在諸如設備認證、用戶認證、第三方認證等認證體系中都會使用到簽名算法 (彼此的實現方式可能會有差異)。
2.加密和解密
2.1. 加密
數據加密的基本過程,就是對原來為明文的文件或數據按某種算法進行處理,使其成為不可讀的一段代碼,通常稱為 “密文”。通過這樣的途徑,來達到保護數據不被非法人竊取、閱讀的目的。
2.2. 解密
加密的逆過程為解密,即將該編碼信息轉化為其原來數據的過程。
3.對稱加密和非對稱加密
加密算法分對稱加密和非對稱加密,其中對稱加密算法的加密與解密的密鑰相同,非對稱加密算法的加密密鑰與解密的密鑰不同,此外,還有一類不需要密鑰的散列算法。
常見的對稱加密算法主要有DES、3DES、AES等,常見的非對稱算法主要有RSA、DSA 等,散列算法主要有SHA-1、MD5 等。
3.1. 對稱加密
對稱加密算法是應用較早的加密算法,又稱為共享密鑰加密算法。在對稱加密算法中,使用的密鑰只有一個,發送和接收雙方都使用這個密鑰對數據進行加密和解密。這就要求加密和解密方事先都必須知道加密的密鑰。
數據加密過程:在對稱加密算法中,數據發送方將明文 (原始數據) 和加密密鑰一起經過特殊加密處理,生成復雜的加密密文進行發送。
數據解密過程:數據接收方收到密文后,若想讀取原數據,則需要使用加密使用的密鑰及相同算法的逆算法對加密的密文進行解密,才能使其恢復成可讀明文。
3.2. 非對稱加密
非對稱加密算法,又稱為公開密鑰加密算法。它需要兩個密鑰,一個稱為公開密鑰 (public key),即公鑰,另一個稱為 私有密鑰 (private key),即私鑰。
因為 加密 和 解密 使用的是兩個不同的密鑰,所以這種算法稱為 非對稱加密算法。
如果使用 公鑰 對數據 進行加密,只有用對應的 私鑰 才能 進行解密。
如果使用 私鑰 對數據 進行加密,只有用對應的 公鑰 才能 進行解密。
例子:甲方生成 一對密鑰 并將其中的一把作為 公鑰 向其它人公開,得到該公鑰的 乙方 使用該密鑰對機密信息 進行加密 后再發送給甲方,甲方再使用自己保存的另一把 專用密鑰 (私鑰),對 加密 后的信息 進行解密。
4.常見的簽名加密算法
4.1. MD5算法
MD5用的是哈希函數,它的典型應用是對一段信息產生 信息摘要,以防止被篡改。嚴格來說,MD5 不是一種 加密算法 而是 摘要算法。無論是多長的輸入,MD5 都會輸出長度為 128bits 的一個串 (通常用 16 進制 表示為 32 個字符)。
public static final byte[] computeMD5(byte[] content) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); return md5.digest(content); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }
1
2
3
4
5
6
7
8
4.2. SHA1算法
SHA1 是和 MD5 一樣流行的 消息摘要算法,然而 SHA1 比 MD5 的 安全性更強。對于長度小于 2 ^ 64 位的消息,SHA1 會產生一個 160 位的 消息摘要。基于 MD5、SHA1 的信息摘要特性以及 不可逆 (一般而言),可以被應用在檢查 文件完整性 以及 數字簽名 等場景。
public static byte[] computeSHA1(byte[] content) { try { MessageDigest sha1 = MessageDigest.getInstance("SHA1"); return sha1.digest(content); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }
1
2
3
4
5
6
7
8
4.3. HMAC算法
HMAC 是密鑰相關的 哈希運算消息認證碼(Hash-based Message Authentication Code),HMAC 運算利用 哈希算法 (MD5、SHA1 等),以 一個密鑰 和 一個消息 為輸入,生成一個 消息摘要 作為 輸出。
HMAC 發送方 和 接收方 都有 key 進行計算,而沒有這把 key 的第三方,則是無法計算 出正確的散列值的,這樣就可以防止數據被篡改。
package net.pocrd.util; import net.pocrd.annotation.NotThreadSafe; import net.pocrd.define.ConstField; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Arrays; @NotThreadSafe public class HMacHelper { private static final Logger logger = LoggerFactory.getLogger(HMacHelper.class); private Mac mac; /** * MAC算法可選以下多種算法 * HmacMD5/HmacSHA1/HmacSHA256/HmacSHA384/HmacSHA512 */ private static final String KEY_MAC = "HmacMD5"; public HMacHelper(String key) { try { SecretKey secretKey = new SecretKeySpec(key.getBytes(ConstField.UTF8), KEY_MAC); mac = Mac.getInstance(secretKey.getAlgorithm()); mac.init(secretKey); } catch (Exception e) { logger.error("create hmac helper failed.", e); } } public byte[] sign(byte[] content) { return mac.doFinal(content); } public boolean verify(byte[] signature, byte[] content) { try { byte[] result = mac.doFinal(content); return Arrays.equals(signature, result); } catch (Exception e) { logger.error("verify sig failed.", e); } return false; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
測試結論:HMAC 算法實例在多線程環境下是不安全的。但是需要在多線程訪問時,進行同步的輔助類,使用 ThreadLocal 為每個線程緩存一個實例可以避免進行鎖操作。
4.4. AES/DES/3DES算法
AES、DES、3DES 都是 對稱 的 塊加密算法,加解密 的過程是 可逆的。常用的有 AES128、AES192、AES256 (默認安裝的 JDK 尚不支持 AES256,需要安裝對應的 jce 補丁進行升級 jce1.7,jce1.8)。
4.4.1. DES算法
DES 加密算法是一種分組密碼,以64位為分組對數據加密,它的密鑰長度是56位,加密解密用同一算法。
DES 加密算法是對 密鑰 進行保密,而 公開算法,包括加密和解密算法。這樣,只有掌握了和發送方相同密鑰的人才能解讀由 DES加密算法加密的密文數據。因此,破譯 DES 加密算法實際上就是搜索密鑰的編碼。對于56位長度的密鑰來說,如果用窮舉法來進行搜索的話,其運算次數為2 ^ 56次。
4.4.2. 3DES算法
是基于 DES 的 對稱算法,對一塊數據用三個不同的密鑰進行三次加密,強度更高。
4.4.3. AES算法
AES 加密算法是密碼學中的高級加密標準,該加密算法采用對稱分組密碼體制,密鑰長度的最少支持為128 位、192 位、256 位,分組長度 128 位,算法應易于各種硬件和軟件實現。這種加密算法是美國聯邦政府采用的區塊加密標準。
AES 本身就是為了取代 DES 的,AES 具有更好的 安全性、效率 和 靈活性。
import net.pocrd.annotation.NotThreadSafe; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; @NotThreadSafe public class AesHelper { private SecretKeySpec keySpec; private IvParameterSpec iv; public AesHelper(byte[] aesKey, byte[] iv) { if (aesKey == null || aesKey.length < 16 || (iv != null && iv.length < 16)) { throw new RuntimeException("錯誤的初始密鑰"); } if (iv == null) { iv = Md5Util.compute(aesKey); } keySpec = new SecretKeySpec(aesKey, "AES"); this.iv = new IvParameterSpec(iv); } public AesHelper(byte[] aesKey) { if (aesKey == null || aesKey.length < 16) { throw new RuntimeException("錯誤的初始密鑰"); } keySpec = new SecretKeySpec(aesKey, "AES"); this.iv = new IvParameterSpec(Md5Util.compute(aesKey)); } public byte[] encrypt(byte[] data) { byte[] result = null; Cipher cipher = null; try { cipher = Cipher.getInstance("AES/CFB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); result = cipher.doFinal(data); } catch (Exception e) { throw new RuntimeException(e); } return result; } public byte[] decrypt(byte[] secret) { byte[] result = null; Cipher cipher = null; try { cipher = Cipher.getInstance("AES/CFB/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); result = cipher.doFinal(secret); } catch (Exception e) { throw new RuntimeException(e); } return result; } public static byte[] randomKey(int size) { byte[] result = null; try { KeyGenerator gen = KeyGenerator.getInstance("AES"); gen.init(size, new SecureRandom()); result = gen.generateKey().getEncoded(); } catch (Exception e) { throw new RuntimeException(e); } return result; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
4.5. RSA算法
RSA 加密算法是目前最有影響力的公鑰加密算法,并且被普遍認為是目前最優秀的公鑰方案之一。RSA 是第一個能同時用于加密 和數字簽名的算法,它能夠抵抗到目前為止已知的所有密碼攻擊,已被 ISO 推薦為公鑰數據加密標準。
RSA 加密算法基于一個十分簡單的數論事實:將兩個大素數相乘十分容易,但想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。
import net.pocrd.annotation.NotThreadSafe; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.security.KeyFactory; import java.security.Security; import java.security.Signature; import java.security.interfaces.RSAPrivateCrtKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @NotThreadSafe public class RsaHelper { private static final Logger logger = LoggerFactory.getLogger(RsaHelper.class); private RSAPublicKey publicKey; private RSAPrivateCrtKey privateKey; static { Security.addProvider(new BouncyCastleProvider()); //使用bouncycastle作為加密算法實現 } public RsaHelper(String publicKey, String privateKey) { this(Base64Util.decode(publicKey), Base64Util.decode(privateKey)); } public RsaHelper(byte[] publicKey, byte[] privateKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); if (publicKey != null && publicKey.length > 0) { this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } if (privateKey != null && privateKey.length > 0) { this.privateKey = (RSAPrivateCrtKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey)); } } catch (Exception e) { throw new RuntimeException(e); } } public RsaHelper(String publicKey) { this(Base64Util.decode(publicKey)); } public RsaHelper(byte[] publicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("RSA"); if (publicKey != null && publicKey.length > 0) { this.publicKey = (RSAPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } } catch (Exception e) { throw new RuntimeException(e); } } public byte[] encrypt(byte[] content) { if (publicKey == null) { throw new RuntimeException("public key is null."); } if (content == null) { return null; } try { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); int size = publicKey.getModulus().bitLength() / 8 - 11; ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 11)); int left = 0; for (int i = 0; i < content.length; ) { left = content.length - i; if (left > size) { cipher.update(content, i, size); i += size; } else { cipher.update(content, i, left); i += left; } baos.write(cipher.doFinal()); } return baos.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } } public byte[] decrypt(byte[] secret) { if (privateKey == null) { throw new RuntimeException("private key is null."); } if (secret == null) { return null; } try { Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.DECRYPT_MODE, privateKey); int size = privateKey.getModulus().bitLength() / 8; ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size - 12) / (size - 11) * size); int left = 0; for (int i = 0; i < secret.length; ) { left = secret.length - i; if (left > size) { cipher.update(secret, i, size); i += size; } else { cipher.update(secret, i, left); i += left; } baos.write(cipher.doFinal()); } return baos.toByteArray(); } catch (Exception e) { logger.error("rsa decrypt failed.", e); } return null; } public byte[] sign(byte[] content) { if (privateKey == null) { throw new RuntimeException("private key is null."); } if (content == null) { return null; } try { Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initSign(privateKey); signature.update(content); return signature.sign(); } catch (Exception e) { throw new RuntimeException(e); } } public boolean verify(byte[] sign, byte[] content) { if (publicKey == null) { throw new RuntimeException("public key is null."); } if (sign == null || content == null) { return false; } try { Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initVerify(publicKey); signature.update(content); return signature.verify(sign); } catch (Exception e) { logger.error("rsa verify failed.", e); } return false; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
4.6. ECC算法
ECC 也是一種非對稱加密算法,主要優勢是在某些情況下,它比其他的方法使用更小的密鑰,比如 RSA 加密算法,提供相當的或更高等級的安全級別。不過一個缺點是加密和解密操作的實現比其他機制時間長 (相比 RSA 算法,該算法對 CPU 消耗嚴重)。
import net.pocrd.annotation.NotThreadSafe; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; import java.security.KeyFactory; import java.security.Security; import java.security.Signature; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @NotThreadSafe public class EccHelper { private static final Logger logger = LoggerFactory.getLogger(EccHelper.class); private static final int SIZE = 4096; private BCECPublicKey publicKey; private BCECPrivateKey privateKey; static { Security.addProvider(new BouncyCastleProvider()); } public EccHelper(String publicKey, String privateKey) { this(Base64Util.decode(publicKey), Base64Util.decode(privateKey)); } public EccHelper(byte[] publicKey, byte[] privateKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC"); if (publicKey != null && publicKey.length > 0) { this.publicKey = (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } if (privateKey != null && privateKey.length > 0) { this.privateKey = (BCECPrivateKey)keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKey)); } } catch (ClassCastException e) { throw new RuntimeException("", e); } catch (Exception e) { throw new RuntimeException(e); } } public EccHelper(String publicKey) { this(Base64Util.decode(publicKey)); } public EccHelper(byte[] publicKey) { try { KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC"); if (publicKey != null && publicKey.length > 0) { this.publicKey = (BCECPublicKey)keyFactory.generatePublic(new X509EncodedKeySpec(publicKey)); } } catch (Exception e) { throw new RuntimeException(e); } } public byte[] encrypt(byte[] content) { if (publicKey == null) { throw new RuntimeException("public key is null."); } try { Cipher cipher = Cipher.getInstance("ECIES", "BC"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); int size = SIZE; ByteArrayOutputStream baos = new ByteArrayOutputStream((content.length + size - 1) / size * (size + 45)); int left = 0; for (int i = 0; i < content.length; ) { left = content.length - i; if (left > size) { cipher.update(content, i, size); i += size; } else { cipher.update(content, i, left); i += left; } baos.write(cipher.doFinal()); } return baos.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } } public byte[] decrypt(byte[] secret) { if (privateKey == null) { throw new RuntimeException("private key is null."); } try { Cipher cipher = Cipher.getInstance("ECIES", "BC"); cipher.init(Cipher.DECRYPT_MODE, privateKey); int size = SIZE + 45; ByteArrayOutputStream baos = new ByteArrayOutputStream((secret.length + size + 44) / (size + 45) * size); int left = 0; for (int i = 0; i < secret.length; ) { left = secret.length - i; if (left > size) { cipher.update(secret, i, size); i += size; } else { cipher.update(secret, i, left); i += left; } baos.write(cipher.doFinal()); } return baos.toByteArray(); } catch (Exception e) { logger.error("ecc decrypt failed.", e); } return null; } public byte[] sign(byte[] content) { if (privateKey == null) { throw new RuntimeException("private key is null."); } try { Signature signature = Signature.getInstance("SHA1withECDSA", "BC"); signature.initSign(privateKey); signature.update(content); return signature.sign(); } catch (Exception e) { throw new RuntimeException(e); } } public boolean verify(byte[] sign, byte[] content) { if (publicKey == null) { throw new RuntimeException("public key is null."); } try { Signature signature = Signature.getInstance("SHA1withECDSA", "BC"); signature.initVerify(publicKey); signature.update(content); return signature.verify(sign); } catch (Exception e) { logger.error("ecc verify failed.", e); } return false; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
5.各種加密算法對比
5.1. 散列算法比較
5.2. 對稱加密算法比較
5.3. 非對稱加密算法比較
5.4. 對稱算法與非對稱加密算法
5.4.1. 對稱算法
密鑰管理:比較難,不適合互聯網,一般用于內部系統
安全性:中
加密速度:快好 幾個數量級 (軟件加解密速度至少快 100 倍,每秒可以加解密數 M 比特 數據),適合大數據量的加解密處理
5.4.2. 非對稱算法
密鑰管理:密鑰容易管理
安全性:高
加密速度:比較慢,適合小數據量加解密或數據簽名
小結
本文介紹了 數字簽名,加密和解密,對稱加密和非對稱加密,然后詳細介紹了 MD5,SHA-1,HMAC,DES/AES,RSA 和 ECC 這幾種加密算法和代碼示例。
數據加密服務
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。