AES加密過程分析
最近做前後端AES加解密遇到了一個麻煩的問題,因為之前只是按照網上的Demo照著Copy了一份,當時測試可用:
public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception { Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES")); return cipher.doFinal(stringToBytes(content)); } /** Cipher 類為加密和解密提供密碼功能 Cipher 的 getInstance ()引數有兩種格式:“演算法/模式/填充”或“演算法”,後一種情況下, 使用模式和填充方案特定於提供者的預設值 cipher.init(a,b),兩個引數:a表示當前的加密模式,b代表用於加密的最終金鑰 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 用於表示要解包的金鑰為“祕密金鑰”的常量。 */
這裡我們把約定的加密Key:encryptKey直接轉為byte[]陣列的形式來用於加密:encryptKey.getBytes()。我們知道AES加密是分段加密的,不清楚的可以參考:AES詳解,每一段的長度是約定好的,128或256,同時,金鑰Key長度也是定好的,128或192或256。因為AES是對稱加密,也就是加密完的東西要能完整的解密出來,所以這些約定的規則很重要。剛剛的程式碼,我們把約定的key直接用於加密了,所以約定的key長度也必須符合128/192/256位。當我們希望key更隨意,不是必須為32位的倍數時,我們需要在執行加密前對金鑰做一次加工:
KeyGenerator kgen = KeyGenerator.getInstance("AES"); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(stringToBytes(encryptKey)); kgen.init(256, secureRandom); Cipher cipher = Cipher.getInstance("AES"); byte[] tt= kgen.generateKey().getEncoded(); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(tt, ENCRYPT_AES_TYPE)); return cipher.doFinal(stringToBytes(content));
這裡我們沒有直接對金鑰encryptKey.getBytes(),而是利用KeyGenerator 和SecureRandom 兩個類對金鑰先做處理,將其轉化為32的倍數位。SecureRandom.getInstance("SHA1PRNG")指定使用隨機數的演算法, secureRandom.setSeed(stringToBytes(encryptKey))指定隨機數生成的種子,它的實現是偽隨機數生成器 (PRNG) 形式,這意味著它們將使用確定的演算法根據實際的隨機種子生成偽隨機序列。kgen.init(256, secureRandom)指定生成的金鑰位數是256位,隨機源是剛剛的隨機數物件。這樣當我們使用 kgen.generateKey().getEncoded();生成金鑰時,金鑰就是256位的根據約定的encryptKey產生的新的金鑰了。
前端我們使用的是JS庫CryptoJs,如何對應實現和java匹配的加密呢?先安裝CryptoJs.js包,再用法如下:
encrypt(word) {
const key = CryptoJS.enc.Hex.parse("這裡是真實的金鑰,16位字串形式");//方式一
// const key = CryptoJS.enc.Utf8.parse("這裡是約定的金鑰,普通字串形式");//方式二
const srcs = CryptoJS.enc.Utf8.parse(word);
const encrypted = CryptoJS.AES.encrypt(srcs, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.ciphertext.toString();
}
我遇到的問題是直接把雙方約定的金鑰按方式二的形式使用。但是我們知道,我們上面真正解密前是對約定的金鑰做了處理的,所以這裡不能直接用,否則必然不匹配。
怎麼辦呢?,很簡單,只需要把處理後的結果拷貝過來當作最終金鑰就好了!
上述java程式碼中:
byte[] tt= kgen.generateKey().getEncoded();
//得到字元陣列,再將陣列轉化為16進位制字串如:6D6F6FB1290134B63038E7B384E89A7F79CBCFB3F7B235580742C70F5B3AF269
用這個金鑰當作最終金鑰,就可以前後端加解密成功了。
這裡貼出一個CrypJs的快速開發指南:https://blog.csdn.net/u013821237/article/details/83176037
非常的有用!