Java AES 加密小試牛刀
在java開發過程中,很多時候我們都需要加密數據,例如聲音、敏感資訊等。我們通常使用的是 MD5加密、SHA加密、DES 加密、AES 加密等。今天我們就看看AES 加密。
問題出處
在項目中,程式碼寫的好好的,本地測試什麼都沒問題,打包發布,高高興興的回家,第二天到公司,發現加密的數據,下載時解密失敗。這什麼情況,哪出了問題,汗直接流了出來。不經意間的一個想法:windows和linux 有什麼差別呢?於是開始調查,有了如下的總結。
解決方法
方法一
程式碼如下:
private void encryptAES1(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
CipherInputStream cipherInputStream = null;
try{
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128, new SecureRandom(key.getBytes(StandardCharsets.UTF_8)));
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyEncoded = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(keyEncoded, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// 解密 Cipher.DECRYPT_MODE
cipherInputStream = new CipherInputStream(inputStream, cipher);
byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = cipherInputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, numBytes);
}
}catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
inputStream.close();
if (null != cipherInputStream){
cipherInputStream.close();
}
}
}
此方法在windows 系統是能夠加密和解密。但是在linux 系統中,加密解密會失敗。原因是因為windows 和linux 內核不同,產生的隨機數不同,所以導致了linux 系統加密解密失效。解決方法參照 方法二
方法二
程式碼如下:
private void encryptAES2(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
CipherInputStream cipherInputStream = null;
try{
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes(StandardCharsets.UTF_8));
keyGenerator.init(128, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
byte[] keyEncoded = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(keyEncoded, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);//解密 Cipher.DECRYPT_MODE
cipherInputStream = new CipherInputStream(inputStream, cipher);
byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = cipherInputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, numBytes);
}
}catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
inputStream.close();
if (null != cipherInputStream){
cipherInputStream.close();
}
}
}
此方法是能夠解決不同系統加密演算法的差異,能夠成功的實現windows、linux 系統加密解密。但是經過程式碼檢測,發現【SecureRandom.getInstance(“SHA1PRNG”);】中的 SHA1PRNG 是有程式碼風險的。通過查找資料,發現可以替換成 NativePRNG ,但是程式碼書寫完成後,運行失敗。經過請教公司前輩們,的到 方法三 的方案。
方法三
程式碼如下:
private void encryptAES3(String key, ByteArrayInputStream inputStream, ByteArrayOutputStream outputStream) throws IOException {
CipherInputStream cipherInputStream = null;
try{
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(key.getBytes(StandardCharsets.UTF_8));
byte[] keyBytes = new byte[16];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);// Cipher.DECRYPT_MODE
cipherInputStream = new CipherInputStream(inputStream, cipher);
byte[] bytes = new byte[64];
int numBytes;
while ((numBytes = cipherInputStream.read(bytes)) != -1){
outputStream.write(bytes, 0, numBytes);
}
}catch (NoSuchPaddingException | NoSuchAlgorithmException | IOException | InvalidKeyException e) {
e.printStackTrace();
} finally {
outputStream.flush();
outputStream.close();
inputStream.close();
if (null != cipherInputStream){
cipherInputStream.close();
}
}
}
此方法去除了隨機數,使用MessageDigest 類,可以生成和 KeyGenerator 生成相同位數的的密匙。有沒有程式碼風險或著缺陷還沒有發現,路過的朋友要是知道的化請留言告知。
補充
- 以上三種方法中 keyGenerator.init(param1, param2) 方法中,第一個參數是產生隨機數的位數,他可以是128(16位元組)、192(24位元組)、256(32位元組)。需要加密的數據,不滿16位元組的,加密後會生成16位元組的加密數據;等於16位元組的數據,加密後會生成32位元組的加密數據。所以方法三中 【byte[] keyBytes =newbyte[16];】是16。
- 建議:若需要對加密後數據拼接的,且整個文件解密的,例如實時的聲音數據、大篇幅的文本消息(分段加密)等,最好加密的數據是16的倍數,加密後減去末尾的16位元組再拼接(最後一段數據加密不需要減16)。
總結
這裡只是簡單的介紹了AES 加密,為什麼沒有詳細的說明DES、AES、MD5 等加密的原理和差別呢,因為我還不夠了解他們的原理🤦。感興趣的小夥伴可以網上搜索下他們的原理和差別。