- 區塊鏈底層設計Java實戰
- 牛冬編著
- 6524字
- 2019-07-25 11:59:22
3.1 加密與解密
3.1.1 加密與解密簡介
加密與解密技術是對信息進行編碼和解碼的技術。編碼的過程即加密的過程,加密模塊把可讀信息(即明文)處理成代碼形式(即密文)。解碼的過程即解密的過程,解密模塊把代碼形式(即密文)轉換回可讀信息(即明文)。在加密和解密的過程中,密鑰是非常關鍵的角色。
目前,加密技術主要分為對稱加密、不對稱加密和不可逆加密三類算法。
對稱加密算法是應用較早的加密算法,技術十分成熟。在對稱加密算法中,加密和解密的密鑰相同。對稱密鑰技術有兩種基本類型:分組密碼和序列密碼。
對稱加密算法的特點是算法公開、計算量小、加密速度快、加密效率高。不足之處是,交易雙方使用相同的密鑰,安全性得不到保證。目前,廣泛使用的對稱加密算法有DES、3DES、IDEA和AES等,其中,美國國家標準局倡導的AES即將作為新標準取代DES。
不對稱加密算法使用兩把完全不同但又完全匹配的一對鑰匙,稱之為公鑰和私鑰,公鑰和私鑰成對配合使用。目前,廣泛應用的不對稱加密算法有RSA算法和美國國家標準局提出的DSA。我們常見的數字簽名(Digital Signature)技術就是以不對稱加密算法為基礎的。雖然不對稱加密算法的安全性得到了提高,但相較于對稱加密算法,加密速度慢、效率低。
不可逆加密算法的使用和上述兩類算法略有不同,加密過程中不需要使用密鑰,輸入明文后由系統直接經過加密算法處理成密文,加密后的數據是無法被解密的,只有重新輸入明文,并再次經過同樣不可逆的加密算法處理,得到相同的加密密文并被系統重新識別后,才能真正解密。目前,應用較多的不可逆加密算法有RSA公司發明的MD5算法和由美國國家標準局建議的不可逆加密標準SHS(Secure Hash Standard,安全Hash標準)等。
3.1.2 Java實現
我們既可以基于Java原生實現加密和解密,又可以基于第三方的工具包實現。
下面我們首先介紹基于Java原生API的實現方法,接著介紹第三方工具包hutool和Tink的加密解密API使用方法。
1.Cipher類
Java中的Cipher類主要提供加密和解密的功能,該類位于javax.crypto包下,聲明為public class Cipher extends Object,它構成了Java Cryptographic Extension (JCE)框架的核心。
使用Cipher類時,需構建Cipher對象,再調用Cipher的getInstance方法來實現。需要注意的是,這里可以由用戶自定義傳參。在Cipher的概念中稱之為“轉換”,“轉換”用于描述生成對象使用的算法,其格式如下:
“算法/模式/填充”或“算法”
例如:Cipher c=Cipher.getInstance("DES/CBC/PKCS5Padding");
Cipher類中常用的常量字段主要有:
· public static final int ENCRYPT_MODE:用于將Cipher初始化為加密模式的常量。
· public static final int DECRYPT_MODE:用于將Cipher初始化為解密模式的常量。
· public static final int WRAP_MODE:用于將Cipher初始化為密鑰包裝模式的常量。
· public static final int UNWRAP_MODE:用于將Cipher初始化為密鑰解包模式的常量。
· public static final int PUBLIC_KEY:用于表示要解包的密鑰為“公鑰”的常量。
· public static final int PRIVATE_KEY:用于表示要解包的密鑰為“私鑰”的常量。
· public static final int SECRET_KEY:用于表示要解包的密鑰為“秘密密鑰”的常量。
Cipher類中提供了構造方法,供用戶自定義使用,構造方法如下所示:
protected Cipher(CipherSpi cipherSpi, Provider provider, String transformation)
Cipher類中的核心方法總結如下:
1)public static final Cipher getInstance(String transformation)
該方法返回實現指定轉換的Cipher對象。transformation為轉換的名稱,例如,DES/CBC/PKCS5Padding。
2)public static final Cipher getInstance(String transformation, String provider)
該方法用于返回實現指定轉換的Cipher對象。
3)public static final Cipher getInstance(String transformation, Provider provider)
該方法同樣用于返回實現指定轉換的Cipher對象。
4)public final Provider getProvider()
該方法用于返回Cipher對象的提供者。
5)public final String getAlgorithm()
該方法用于返回此Cipher對象的算法名稱。
6)public final int getBlockSize()
該方法用于返回塊的大小(以字節為單位)。
7)public final int getOutputSize(int inputLen)
該方法根據給定的輸入長度inputLen(以字節為單位),返回保存下一個update或doFinal操作結果所需的輸出緩沖區長度(以字節為單位)。
8)public final byte[] getIV()
該方法用于返回新緩沖區中的初始化向量 (IV)。
9)public final AlgorithmParameters getParameters()
該方法返回此Cipher使用的參數。返回的參數可能與初始化此Cipher所使用的參數相同。如果此Cipher需要算法參數,但卻未使用任何參數進行初始化,則返回的參數將由默認值和底層Cipher實現所使用的隨機參數值組成。
10)public final ExemptionMechanism getExemptionMechanism()
該方法返回此Cipher使用的豁免(exemption)機制對象。
11)public final void init(int opmode, Key key)
該方法用密鑰初始化此Cipher。
12)public final void init(int opmode, Key key, SecureRandom random)
該方法用密鑰和隨機源初始化此Cipher。
13)public final void init(int opmode, Key key, AlgorithmParameterSpec params)
該方法用密鑰和一組算法參數初始化此Cipher。
14)public final void init(int opmode, Key key, AlgorithmParameterSpec params, SecureRandom random)
該方法用一個密鑰、一組算法參數和一個隨機源初始化此Cipher。
15)public final void init(int opmode, Key key, AlgorithmParameters params)
該方法用密鑰和一組算法參數初始化此Cipher。
16)public final void init(int opmode, Key key, AlgorithmParameters params, SecureRandom random)
該方法用一個密鑰、一組算法參數和一個隨機源初始化此Cipher。
17)public final void init(int opmode, Certificate certificate)
該方法用取自給定證書的公鑰初始化此Cipher。
18)public final void init(int opmode, Certificate certificate, SecureRandom random)
該方法用取自給定證書的公鑰和隨機源初始化此Cipher。
19)public final byte[] update(byte[] input)
20)public final byte[] update(byte[] input, int inputOffset, int inputLen)
21)public final int update(byte[] input, int inputOffset, int inputLen, byte[] output)
22)public final int update(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
23)public final int update(ByteBuffer input, ByteBuffer output)
上述幾種方法用于大量數據需要進行加密或解密的場景,此時需將大量數據分批次進行加密或解密,對每一個批次數據的加密或解密都需要調用update方法。
24)public final byte[] doFinal() throws IllegalBlockSizeException, BadPaddingException
該方法用于大量數據需要進行加密或解密的場景,此時需將大量數據分批次進行加密或解密,doFinal()方法用于完成多批次加密或解密的收尾工作。比如,待加密或解密的大量數據可以分為9份,每批次4份,則第三批次就剩1份,這1份數據由doFinal()方法執行對應的加密或解密操作。
25)public final int doFinal(byte[] output, int outputOffset)
26)public final byte[] doFinal(byte[] input)
27)public final byte[] doFinal(byte[] input, int inputOffset, int inputLen)
28)public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output)
29)public final int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output, int outputOffset)
30)public final int doFinal(ByteBuffer input, ByteBuffer output)
上述幾種方法用于大量數據和少量數據進行加密或解密的場景。少量數據時,按單一部分操作加密或解密數據;大量數據需要進行加密或解密的場景中,需將大量數據分批次進行加密或解密,doFinal()方法用于完成多批次加密或解密的收尾工作。比如,待加密或解密的大量數據可以分為9份,每批次4份,則第三批次就剩1份,這1份數據由doFinal()方法執行對應的加密或解密操作。
31)public final byte[] wrap(Keykey) throws IllegalBlockSizeException, InvalidKeyException
該方法用于包裝密鑰。
32)public final Key unwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, int wrappedKeyType)throws InvalidKeyException, NoSuchAlgorithmException
該方法用于解包一個以前包裝的密鑰。
參數列表釋義如下:
· wrappedKey:待解包的密鑰。
· wrappedKeyAlgorithm:與此包裝密鑰關聯的算法。
· wrappedKeyType:已包裝密鑰的類型。此類型必須為SECRET_KEY、PRIVATE_KEY或PUBLIC_KEY之一。
2.基于Cipher實現加密和解密
在熟悉了Cipher的主要API后,我們基于Cipher編寫加密和解密代碼如下:
package com.niudong.demo.util; import java.io.IOException; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.DESKeySpec; import org.testng.util.Strings; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; /** * 基于Cipher實現的加密和解密工具類 * * @author牛冬 * */ public class DeEnCoderCipherUtil { // 加密、解密模式 private final static String CIPHER_MODE = "DES"; // DES密鑰 public static String DEFAULT_DES_KEY = "區塊鏈是分布式數據存儲、點對點傳輸、共識機制、加密算法等計算機技術的新型應用模式。"; /** * function加密通用方法 * * @param originalContent:明文 * @param key加密密鑰 * @return密文 */ public static String encrypt(String originalContent, String key) { // 明文或加密密鑰為空時 if(Strings.isNullOrEmpty(originalContent) || Strings.isNullOrEmpty (key)){ return null; } // 明文或加密密鑰不為空時 try { byte[] byteContent = encrypt(originalContent.getBytes(), key.getBytes()); return new BASE64Encoder().encode(byteContent); } catch (Exception e) { e.printStackTrace(); } return null; } /** * function解密通用方法 * * @param ciphertext密文 * @param key DES解密密鑰(同加密密鑰) * @return明文 */ public static String decrypt(String ciphertext, String key) { // 密文或加密密鑰為空時 if(Strings.isNullOrEmpty(ciphertext)||Strings.isNullOrEmpty(key)){ return null; } // 密文或加密密鑰不為空時 try { BASE64Decoder decoder = new BASE64Decoder(); byte[] bufCiphertext = decoder.decodeBuffer(ciphertext); byte[] contentByte = decrypt(bufCiphertext, key.getBytes()); return new String(contentByte); } catch (Exception e) { e.printStackTrace(); } return null; } /** * function字節加密方法 * * @param originalContent:明文 * @param key加密密鑰的byte數組 * @return密文的byte數組 */ private static byte[] encrypt(byte[] originalContent, byte[] key) throws Exception { // 步驟1:生成可信任的隨機數源 SecureRandom secureRandom = new SecureRandom(); // 步驟2:基于密鑰數據創建DESKeySpec對象 DESKeySpec desKeySpec = new DESKeySpec(key); // 步驟3:創建密鑰工廠,將DESKeySpec轉換成SecretKey對象來保存對稱密鑰 SecretKeyFactory keyFactory=SecretKeyFactory.getInstance (CIPHER_MODE); SecretKey securekey = keyFactory.generateSecret(desKeySpec); // 步驟4:Cipher對象實際完成加密操作,指定其支持指定的加密和解密算法 Cipher cipher = Cipher.getInstance(CIPHER_MODE); // 步驟5:用密鑰初始化Cipher對象,ENCRYPT_MODE表示加密模式 cipher.init(Cipher.ENCRYPT_MODE, securekey, secureRandom); // 返回密文 return cipher.doFinal(originalContent); } /** * function字節解密方法 * * @param ciphertextByte:字節密文 * @param key解密密鑰(同加密秘鑰)byte數組 * @return明文byte數組 */ private static byte[] decrypt(byte[] ciphertextByte, byte[] key) throws Exception { // 步驟1:生成可信任的隨機數源 SecureRandom secureRandom = new SecureRandom(); // 步驟2:從原始密鑰數據創建DESKeySpec對象 DESKeySpec desKeySpec = new DESKeySpec(key); // 步驟3:創建密鑰工廠,將DESKeySpec轉換成SecretKey對象來保存對稱密鑰 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance (CIPHER_MODE); SecretKey securekey = keyFactory.generateSecret(desKeySpec); // 步驟4:Cipher對象實際完成解密操作,指定其支持響應的加密和解密算法 Cipher cipher = Cipher.getInstance(CIPHER_MODE); // 步驟5:用密鑰初始化Cipher對象,DECRYPT_MODE表示解密模式 cipher.init(Cipher.DECRYPT_MODE, securekey, secureRandom); // 返回明文 return cipher.doFinal(ciphertextByte); } }
我們基于TestNG和Mockito(使用說明詳見附錄A和附錄B)編寫DeEnCoderCipherUtil對應的單元測試代碼DeEnCoderCipherUtilTest如下:
package com.niudong.demo.util; import org.testng.annotations.Test; import org.testng.Assert; /** * DeEnCoderCipherUtil的單元測試類 * * @author牛冬 * */ public class DeEnCoderCipherUtilTest { private static String ciphertextGlobal; @Test public void testEncrypt() { // case1:originalContent = null; key = null; String originalContent = null; String key = null; Assert.assertEquals(DeEnCoderCipherUtil.encrypt(originalContent, key), null); // case2:originalContent ! = null; key = null; originalContent = "2019屆校園招聘開啟啦! "; key = null; Assert.assertEquals(DeEnCoderCipherUtil.encrypt(originalContent, key), null); // case3:originalContent = null; key ! = null; originalContent = null; key = " 2019屆校園招聘開啟啦!內推簡歷扔過來呀!"; Assert.assertEquals(DeEnCoderCipherUtil.encrypt(originalContent, key), null); // case3:originalContent = null; key ! = null; originalContent = " 2019屆校園招聘開啟啦! "; key = " 2019屆校園招聘開啟啦!內推簡歷扔過來呀!"; ciphertextGlobal = DeEnCoderCipherUtil.encrypt(originalContent, key); Assert.assertEquals(ciphertextGlobal, " Jd/2DCl5MX6g8EKfqR/kGzy9OUSBxsfoQKMlpR3FCaE= "); } @Test(dependsOnMethods = {"testEncrypt"}) public void testDecrypt() { // case1:String ciphertext = null, String key =null String ciphertext = null, key = null; Assert.assertEquals(DeEnCoderCipherUtil.decrypt(ciphertext, key), null); // case2:String ciphertext ! = null, String key =null ciphertext = ciphertextGlobal; Assert.assertEquals(DeEnCoderCipherUtil.decrypt(ciphertext, key),null); // case3:String ciphertext = null, String key ! =null ciphertext = null; key = " 2019屆校園招聘開啟啦!內推簡歷扔過來呀!"; Assert.assertEquals(DeEnCoderCipherUtil.decrypt(ciphertext, key), null); // case4:String ciphertext ! = null, String key ! =null ciphertext = ciphertextGlobal; key = " 2019屆校園招聘開啟啦!內推簡歷扔過來呀!"; Assert.assertEquals(DeEnCoderCipherUtil.decrypt(ciphertext, key), "2019屆校園招聘開啟啦! "); } }
3.Hutool簡介
在Java世界中,AES、DES的加密解密需要使用Cipher對象構建加密解密系統,有沒有更好的封裝工具類來簡化開發呢?當然有!Hutool就是一個好助手。
Hutool是一個實用的Java工具包,有pom.jar、javadoc.jar和sources.jar等文件,可對文件、流、加密解密、轉碼、正則、線程、XML等JDK方法進行封裝,組成各種Util工具類。適用于Web開發,與其他框架無耦合,高度可替換。
在加密解密這部分,Hutool針對JDK支持的所有對稱加密算法都做了封裝,封裝為SymmetricCrypto類,AES和DES是此類的簡化表示。通過實例化這個類,傳入相應的算法枚舉即可使用相同方法加密解密字符串或對象。
當前Hutool支持的對稱加密算法枚舉有:
· AES
· ARCFOUR
· Blowfish
· DES
· DESede
· RC2
· PBEWithMD5AndDES
· PBEWithSHA1AndDESede
· PBEWithSHA1AndRC2_40
這些枚舉全部在SymmetricAlgorithm中被列舉。
對于不對稱加密,最常用的就是RSA和DSA,在Hutool中使用AsymmetricCrypto對象來負責加密解密。
4.基于Hutool實現加密解密
使用Hutool之前,在工程中需引入Hutool的依賴配置,配置代碼如下所示:
<dependency> <groupId>com.xiaoleilu</groupId> <artifactId>hutool-all</artifactId> <version>3.0.9</version> </dependency>
基于Hutool工具類的加密解密類DeEnCoderHutoolUtil的代碼如下:
package com.niudong.demo.util; import java.security.PrivateKey; import java.security.PublicKey; import org.testng.util.Strings; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import cn.hutool.crypto.symmetric.AES; import cn.hutool.crypto.symmetric.DES; /** * 基于Hutool工具類的加密解密類 * * @author牛冬 * */ public class DeEnCoderHutoolUtil { // 構建RSA對象 private static RSA rsa = new RSA(); // 獲得私鑰 private static PrivateKey privateKey = rsa.getPrivateKey(); // 獲得公鑰 private static PublicKey publicKey = rsa.getPublicKey(); /** * function RSA加密通用方法:對稱加密解密 * * @param originalContent:明文 * @return密文 */ public static String rsaEncrypt(String originalContent) { // 明文或加密密鑰為空時 if (Strings.isNullOrEmpty(originalContent)) { return null; } // 公鑰加密,之后私鑰解密 return rsa.encryptBase64(originalContent, KeyType.PublicKey); } /** * function RSA解密通用方法:對稱加密解密 * * @param ciphertext密文 * @param key RSA解密密鑰(同加密密鑰) * @return明文 */ public static String rsaDecrypt(String ciphertext) { // 密文或加密密鑰為空時 if (Strings.isNullOrEmpty(ciphertext)) { return null; } return rsa.decryptStr(ciphertext, KeyType.PrivateKey); } /** * function DES加密通用方法:對稱加密解密 * * @param originalContent:明文 * @param key加密密鑰 * @return密文 */ public static String desEncrypt(String originalContent, String key) { // 明文或加密密鑰為空時 if (Strings.isNullOrEmpty(originalContent) || Strings.isNullOrEmpty(key)) { return null; } // 還可以隨機生成密鑰 // byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES. // getValue()).getEncoded(); // 構建 DES des = SecureUtil.des(key.getBytes()); // 加密 return des.encryptHex(originalContent); } /** * function DES解密通用方法:對稱加密解密 * * @param ciphertext密文 * @param key DES解密密鑰(同加密秘鑰) * @return明文 */ public static String desDecrypt(String ciphertext, String key) { // 密文或加密密鑰為空時 if(Strings.isNullOrEmpty(ciphertext)||Strings.isNullOrEmpty(key)){ return null; } // 還可以隨機生成密鑰 // byte[] key = SecureUtil.generateKey(SymmetricAlgorithm.DES. // getValue()).getEncoded(); // 構建 DES des = SecureUtil.des(key.getBytes()); // 解密 return des.decryptStr(ciphertext); } }
同樣為了方便測試,我們基于TestNG和Mockito框架編寫單元測試代碼如下:
package com.niudong.demo.util; import org.testng.Assert; import org.testng.annotations.Test; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.symmetric.SymmetricAlgorithm; /** * 基于Hutool工具的加密解密的單元測試類 * * @author 牛冬 * */ public class DeEnCoderHutoolUtilTest { @Test public void testDesEncrypt() { // case1:String originalContent=null, String key=null String originalContent = null, key = null; Assert.assertEquals(DeEnCoderHutoolUtil.desEncrypt (originalContent, key), null); // case2:String originalContent! =null, String key=null originalContent = "2019屆校園招聘開啟啦! "; Assert.assertEquals(DeEnCoderHutoolUtil.desEncrypt (originalContent, key), null); // case2:String originalContent=null, String key! =null originalContent = null; key = " 2019屆校園招聘開啟啦!內推簡歷扔過來呀!"; Assert.assertEquals(DeEnCoderHutoolUtil.desEncrypt (originalContent, key), null); // case4:String originalContent! =null, String key! =null originalContent = "2019屆校園招聘開啟啦!"; key = new String(SecureUtil.generateKey(SymmetricAlgorithm.DES. getValue()).getEncoded()); Assert.assertNotNull(DeEnCoderHutoolUtil.desEncrypt (originalContent, key)); } @Test public void testDesDecrypt() { // case1:String ciphertext =null, String key = null String ciphertext = null, key = null; Assert.assertEquals(DeEnCoderHutoolUtil.desDecrypt(ciphertext, key), null); // case2:String ciphertext ! =null, String key = null String originalContent = "2019屆校園招聘開啟啦!"; String keyTmp = new String(SecureUtil.generateKey(SymmetricAlgorithm.DES. getValue()).getEncoded()); ciphertext = DeEnCoderHutoolUtil.desEncrypt(originalContent, keyTmp); Assert.assertEquals(DeEnCoderHutoolUtil.desDecrypt(ciphertext, key), null); // case3:String ciphertext =null, String key ! = null ciphertext = null; key = new String(SecureUtil.generateKey(SymmetricAlgorithm.DES. getValue()).getEncoded()); Assert.assertEquals(DeEnCoderHutoolUtil.desDecrypt(ciphertext, key), null); // case4:String ciphertext ! =null, String key ! = null ciphertext = DeEnCoderHutoolUtil.desEncrypt(originalContent, key); Assert.assertNotNull(DeEnCoderHutoolUtil.desDecrypt(ciphertext, key)); } }
5.Tink簡介
Tink是由谷歌的一群密碼學家和安全工程師編寫的密碼庫。GitHub開源地址:https://github.com/google/tink/。
Tink的誕生融合了Google產品團隊的豐富工程經驗,現已修復了原有實現中的缺陷,并提供了簡單的API,用戶可以安全使用,無須具備密碼學知識背景。
Tink提供了易于正確使用且不易被誤用的安全API。Tink通過以用戶為中心的設計、嚴謹的代碼實現和代碼審查以及廣泛的測試,顯著減少了工程開發中常見的密碼陷阱。在谷歌,Tink已經被用來保護許多產品的數據,如廣告、谷歌薪酬、谷歌助理、Firebase、Android搜索應用等。
使用Tink最簡單的方法是安裝Bazel,然后構建、運行和播放GitHub中預設的hello world示例。
Tink通過原語執行加密任務,每個原語都是通過指定原語功能的相應接口定義的。例如,對稱密鑰加密是通過AEAD原語(帶有關聯數據的認證加密)提供的,它支持兩種操作:
1)加密(明文,associated _ data),加密給定明文(使用associated _ data作為額外的AEAD輸入),并返回結果密文。
2)解密(密文,associated _ data),它解密給定的密文(使用associated _ data作為額外的AEAD輸入),并返回結果明文。
目前,Tink的Java語言版、Android語言版、C++語言版和Obj-C語言版都已通過了嚴格的測試,并已投入生產部署。當前Tink的最新版本是1.2.0,發布于2018年8月9日。此外,Tink的Go語言版本和JavaScript語言版本正在積極開發中。
6.Tink的使用
Tink可以通過Maven或Gradle來實現依賴管理。在Maven中,Tink的group ID是com.google.crypto.tink, artifact ID是tink。
Java開發人員基于Maven在工程的POM文件中添加Tink的依賴配置如下:
<dependency> <groupId>com.google.crypto.tink</groupId> <artifactId>tink</artifactId> <version>1.2.0</version> </dependency>
1)Tink的初始化
Tink提供了可定制的初始化,允許用戶選擇所需原語的特定實現(由鍵類型標識)。Tink的初始化是通過注冊來實現的,以便Tink“知道”用戶期望的實現方式。
例如,要想使用Tink中的所有原語來實現初始化,則初始化的代碼邏輯如下所示:
import com.google.crypto.tink.config.TinkConfig; public void register() { try { TinkConfig.register(); } catch (Exception e) { e.printStackTrace(); } }
如果僅使用AEAD原語實現,則可以執行以下操作:
import com.google.crypto.tink.aead.AeadConfig; public void register() { try { AeadConfig.register(); } catch (Exception e) { e.printStackTrace(); } }
當然,Tink還支持用戶自定義初始化,即直接通過注冊表類進行注冊,代碼如下所示:
import com.google.crypto.tink.Registry; import com.niudong.demo.util.MyAeadKeyManager; public void register(){ // Register a custom implementation of AEAD. Registry.registerKeyManager(new MyAeadKeyManager()); }
其中MyAeadKeyManager為自定義的KeyManager。
注冊原語實現后,Tink的基本使用分三步進行:
(1)加載或生成加密密鑰(Tink術語中的密鑰集)。
(2)使用key獲取所選原語的實例。
(3)使用原語完成加密任務。
例如,使用Java中的AEAD原語加密解密時的代碼示例如下:
import com.google.crypto.tink.Aead; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadFactory; import com.google.crypto.tink.aead.AeadKeyTemplates; public void aead(byte[] plaintext, byte[] aad){ try { // 1. 配置生成密鑰集 KeysetHandle keysetHandle = KeysetHandle.generateNew( AeadKeyTemplates.AES128_GCM); // 2. 使用key獲取所選原語的實例 Aead aead = AeadFactory.getPrimitive(keysetHandle); // 3. 使用原語完成加密任務 byte[] ciphertext = aead.encrypt(plaintext, aad); } catch (Exception e) { e.printStackTrace(); } }
2)生成新密鑰(組)
每個密鑰管理器KeyManager的實現都提供了新密鑰的生成接口newKey(...),該接口根據用戶設置的密鑰類型生成新密鑰。然而,為了避免敏感密鑰信息的意外泄漏,開發人員在代碼中應該小心將密鑰(集合)生成與密鑰(集合)使用混合。為了支持這些工作之間的分離,Tink包提供了一個名為Tinkey的命令行工具,可用于公共密鑰的管理。
如果用戶需要在Java代碼中直接用新的密鑰生成KeysetHandle,則用戶可以使用keysteadle工具類。例如,用戶可以生成包含隨機生成的AES 128- GCM密鑰的密鑰集,代碼如下所示:
import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadKeyTemplates; public void createKeySet(){ try { KeyTemplate keyTemplate = AeadKeyTemplates.AES128_GCM; KeysetHandle keysetHandle = KeysetHandle.generateNew(keyTemplate); } catch (Exception e) { e.printStackTrace(); } }
3)存儲密鑰
生成密鑰后,用戶可以將其保存到存儲系統中,例如寫入文件,代碼如下:
import com.google.crypto.tink.CleartextKeysetHandle; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadKeyTemplates; import com.google.crypto.tink.JsonKeysetWriter; import java.io.File; public void save2File(){ try { // 創建AES對應的keysetHandle KeysetHandle keysetHandle = KeysetHandle.generateNew( AeadKeyTemplates. AES128_GCM); // 寫入json文件 String keysetFilename = "my_keyset.json"; CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withFile( new File(keysetFilename))); } catch (Exception e) { e.printStackTrace(); } }
用戶還可以使用Google Cloud KMS key來對key加密,其中Google Cloud KMS key位于gcp-kms:/projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar as follows,保存在文件中的代碼示例如下:
import com.google.crypto.tink.JsonKeysetWriter; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadKeyTemplates; import com.google.crypto.tink.integration.gcpkms.GcpKmsClient; import java.io.File; public void save2FileBaseKMS(){ try { // 創建AES對應的keysetHandle KeysetHandle keysetHandle = KeysetHandle.generateNew( AeadKeyTemplates.AES128_GCM); // 寫入json文件 String keysetFilename = "my_keyset.json"; // 使用gcp-kms方式對密鑰加密 String masterKeyUri = "gcp-kms: //projects/tink-examples/locations/global/keyRings/foo/ cryptoKeys/bar"; keysetHandle.write(JsonKeysetWriter.withFile(new File(keysetFilename)), new GcpKmsClient().getAead(masterKeyUri)); } catch (Exception e) { e.printStackTrace(); } }
4)加載密鑰
可以使用KeysetHandle加載加密的密鑰集,代碼示例如下:
import com.google.crypto.tink.JsonKeysetReader; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.integration.awskms.AwsKmsClient; import java.io.File; public void loadKeySet(){ try { String keysetFilename = "my_keyset.json"; // 使用aws-kms方式對密鑰加密 String masterKeyUri = "aws-kms: //arn:aws:kms:us-east-1:007084425826:key/84a65985-f868-4bfc- 83c2-366618acf147"; KeysetHandle keysetHandle = KeysetHandle.read( JsonKeysetReader.withFile(new File(keysetFilename)), new AwsKmsClient().getAead(masterKeyUri)); } catch (Exception e) { e.printStackTrace(); } }
如果加載明文的密鑰集,則需要使用CleartextKeysetHandle類:
import com.google.crypto.tink.CleartextKeysetHandle; import com.google.crypto.tink.KeysetHandle; import java.io.File; public void loadCleartextKeySet(){ try { String keysetFilename = "my_keyset.json"; KeysetHandle keysetHandle = CleartextKeysetHandle.read( JsonKeysetReader.withFile(new File(keysetFilename))); } catch (Exception e) { e.printStackTrace(); } }
5)原語的使用和獲取
原語在Tink中指的是加密操作,因此它們構成了Tink API的核心。
原語是一個接口,它指定了原語能提供的基本操作。一個原語可以有多個實現,用戶可以通過使用某種類型的鍵來設定想要的實現。
表3-1總結了當前可用或計劃中的原語的Java實現。
表3-1 原語的Java實現映射關系表

6)對稱密鑰加密
獲得和使用AEAD(通過認證的加密,以及加密或解密數據)代碼示例如下:
import com.google.crypto.tink.Aead; import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.aead.AeadFactory; import com.google.crypto.tink.aead.AeadKeyTemplates; public void aeadAES(byte[] plaintext, byte[] aad){ try { // 1. 創建AES對應的keysetHandle KeysetHandle keysetHandle = KeysetHandle.generateNew( AeadKeyTemplates.AES128_GCM); // 2. 獲取私鑰 Aead aead = AeadFactory.getPrimitive(keysetHandle); // 3. 用私鑰加密明文 byte[] ciphertext = aead.encrypt(plaintext, aad); // 解密密文 byte[] decrypted = aead.decrypt(ciphertext, aad); } catch (Exception e) { e.printStackTrace(); } }
7)數字簽名
數字簽名的簽名或驗證示例代碼如下所示:
import com.google.crypto.tink.KeysetHandle; import com.google.crypto.tink.PublicKeySign; import com.google.crypto.tink.PublicKeyVerify; import com.google.crypto.tink.signature.PublicKeySignFactory; import com.google.crypto.tink.signature.PublicKeyVerifyFactory; import com.google.crypto.tink.signature.SignatureKeyTemplates; // 簽名 public void signatures(byte[] data){ try{ // 1. 創建ESCSA對應的KeysetHandle對象 KeysetHandle privateKeysetHandle = KeysetHandle.generateNew( SignatureKeyTemplates.ECDSA_P256); // 2. 獲取私鑰 PublicKeySign signer = PublicKeySignFactory.getPrimitive( privateKeysetHandle); // 3. 用私鑰簽名 byte[] signature = signer.sign(data); // 簽名驗證 // 1. 獲取公鑰對應的KeysetHandle對象 KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle(); // 2. 獲取私鑰 PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive( publicKeysetHandle); // 3. 使用私鑰校驗簽名 verifier.verify(signature, data); } catch (Exception e) { e.printStackTrace(); } }
使用Tink的注意事項如下:
(1)不要使用標有@ Alpha注釋的字段和方法的API。這些API可以以任何方式修改,甚至可以隨時刪除。它們僅用于測試,不是官方生產發布的。
(2)不要在com.google.crypto.tink.subtle上使用API。雖然這些API通常使用起來是安全的,但并不適合公眾消費,因為可以隨時以任何方式修改甚至刪除它們。
- Python概率統計
- C++案例趣學
- Mastering Concurrency in Go
- Essential Angular
- JavaScript by Example
- ArcGIS By Example
- Learning SciPy for Numerical and Scientific Computing(Second Edition)
- INSTANT Yii 1.1 Application Development Starter
- Django實戰:Python Web典型模塊與項目開發
- Swift語言實戰晉級
- 零基礎學Python編程(少兒趣味版)
- Python 3快速入門與實戰
- 軟件測試(慕課版)
- 企業級Java現代化:寫給開發者的云原生簡明指南
- 青少年Python趣味編程