Reference Core Java Volume Ⅱ 10th Edition 1 對稱加密 “Java密碼擴展”包含了一個Cipher,它是所有密碼演算法的超類。通過getInstance(algorithmName)可以獲得一個密碼對象。 cipher.init(mode, key);模式有以 ...
Reference Core Java Volume Ⅱ 10th Edition
1 對稱加密
“Java密碼擴展”包含了一個Cipher
,它是所有密碼演算法的超類。通過getInstance(algorithmName)
可以獲得一個密碼對象。
cipher.init(mode, key);模式有以下四種:
Cipher.ENCRYPT;
Cipher.DECRYPT;
Cipher.WRAP_MODE和Cipher.UNWRAP_MODE會用一個秘鑰對另一個秘鑰進行加密
// 可以一直調用cipher.update(),進行加密
int blockSize = cipher.getBlockSize();
byte[] inBytes = new byte[blockSize];
... // read inBytes
int outputSize = cipher.getOutputSize(blockSize);
byte[] outBytes = new byte[outputSize];
int outLength = cipher.update(inBytes, 0, outputSize, outBytes);
... // write outBytes
//完成上述操作後,最後必須調用doFinal返回加密後的數據
//如果最後一個輸入數據塊小於blockSize:
outBytes = cipher.doFinal(inBytes, 0, inLength);
//如果所有數據都已加密:
outBytes = cipher.doFinal();
//如果輸入數組長度不夠blockSize,update方法返回的數組為空,長度為0
doFinal調用時必要的,因為它會對最後的數據塊進行填充,常用填充方案是RSA Security公司在公共秘鑰密碼標準#5(Public Key Cryptography Standard, PKCS)中描述的方案
該方案最後一個數據塊不是全用0填充,而是等於填充位元組數量的值最為填充值
2 秘鑰生成
我們需要確保秘鑰生成時隨機的。這需要遵循下麵的步驟:
1). 為加密演算法獲取KeyGenerator
2). 用隨機源來初始化秘鑰發生器。如果密碼長度是可變的,還需要指定期望的密碼塊長度
3). 調用generateKey方法
例如:
KeyGenerator keygen = KeyGenerator.getInstance("AES");
SecureRandom random = new SecureRandom();
keygen.init(random);
SecretKey key = keygen.generateKey();
或者可以從一組固定的原生數據(也許是口令或者隨機鍵產生的)中生成一個密鑰,這時可以使用SecretKeyFactory
byte[] keyData = ...; // 16 byte for AES
SecretKey key = new SecretKeySpec(keyData, "AES");
如果要生成密鑰,必須使用“真正的隨機”數。例如,在常見的Random中的常規的隨即發生器。是根據當前的日期和時間來產生的,因此它不夠隨機。假設電腦時鐘可以精確到1/10秒,那麼每天最多存在864000個種子。如果攻擊者知道密鑰的日期(通常可以由消息日期或證書有效日期推算出來),那麼就可以很容易的產生那一天的所有可能的種子。
SecureRandom類產生的隨機數,遠比由Random類產生的那些隨機數字安全得多。也可以由我們提供種子。由setSeed(byre[] b)方法傳遞給它。
如果沒有隨機數發生器提供種子,那麼它將通過啟動線程,是他們睡眠,然後測量他們被喚醒的準確時間,一次來計算自己的20個位元組的種子
!註意: 這個演算法仍然被人為是安全的。而且,在過去,依靠對諸如硬碟訪問之間的類的其他電腦組件進行計時的演算法, 後來也被證明不也是完全隨機的。
3 密碼流
密碼流能夠透明地調用update和doFinal方法,所以非常方便,源碼也很簡單。
API javax.crypto.CipherInputStream 1.4
CipherInputStream(InputStream in, Cipher cipher)
構建一個輸入流,讀取in中的數據,並且使用指定的密碼對數據進行解密和加密。int read()
int read(byte[] b, int off, int len)
讀取輸入流中的數據,該數據會被自動解密和加密
API javax.crypto.CipherOutputStream 1.4
CipherOutputSream(OutputStream out, Cipher cipher)
構建一個輸出流,以便將數據寫入out,並且使用指定的秘密對數據進行加密和解密。void write(int ch)
void write(byte b, int off, int len)
將數據寫入輸出流,該數據會被自動加密和解密。void flush()
刷新密碼緩衝區,如果需要的化,執行填充操作。
CipherInputStream.read讀取完數據後,如果不夠一個數據塊,會自動調用doFinal方法填充後返回
CipherOutputStream.close方法如果發現,還有沒寫完的數據,會調用doFinal方法返回數據然後輸出
4 工具類
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.*;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;
import java.util.Objects;
/**
* 加解密字元串、文件工具類
* @author YYang 13047
* @version 2022/10/25 12:10
*/
public class AESUtils {
public static final String AES = "AES";
//PKCS: Public Key Cryptographic Standard
public static final String AES_ECB = "AES/ECB/PKCS5Padding";
public static final String AES_CBC = "AES/CBC/PKCS5Padding";
public static final String AES_CFB = "AES/CFB/PKCS5Padding";
public static final int KEY_SIZE = 128;
public static final int BUFFER_SIZE = 512;
public static String encodeToString(byte[] unEncoded) {
return Base64.getEncoder().encodeToString(unEncoded);
}
public static byte[] decode(String encoded) {
return Base64.getDecoder().decode(encoded);
}
public static String generateAESKey() throws NoSuchAlgorithmException {
return generateAESKey(KEY_SIZE, null);
}
/**
* @param keySize keySize must be equal to 128, 192 or 256;
* @param seed 隨機數種子
* @see #generateAESKey0()
*/
public static String generateAESKey(int keySize, String seed) throws NoSuchAlgorithmException {
KeyGenerator keyGen = KeyGenerator.getInstance(AES);
SecureRandom random = (seed == null || seed.length() == 0) ?
new SecureRandom() : new SecureRandom(seed.getBytes(StandardCharsets.UTF_8));
//如果不初始化,SunJCE預設使用new SecureRandom()
keyGen.init(keySize, random);
SecretKey secretKey = keyGen.generateKey();
return encodeToString(secretKey.getEncoded());
}
/**
* @return 密鑰,不初始化,使用預設的
*/
public static String generateAESKey0() throws NoSuchAlgorithmException {
return encodeToString(KeyGenerator.getInstance(AES).generateKey().getEncoded());
}
/**
* @param algorithm 演算法名
* @return 返回一個當前演算法BlockSize大小的隨機數組,然後Base64轉碼
* @see #generateAESIv()
*/
public static String generateAESIv(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
Cipher cipher = Cipher.getInstance(algorithm);
int blockSize = cipher.getBlockSize();
byte[] ivByte = new byte[blockSize];
new SecureRandom().nextBytes(ivByte);
return encodeToString(ivByte);
}
public static String generateAESIv() {
//AES blockSize == 16
byte[] bytes = new byte[16];
new SecureRandom().nextBytes(bytes);
return encodeToString(bytes);
}
public static AlgorithmParameterSpec getIv(String ivStr) {
if (ivStr == null || ivStr.length() < 1) return null;
return new IvParameterSpec(decode(ivStr));
}
/**
* @return 指定秘鑰和演算法,返回Key對象
*/
public static Key getKey(String keyStr, String algorithm) {
return new SecretKeySpec(decode(keyStr), algorithm);
}
public static Cipher initCipher(String algorithm, int cipherMode, Key key, AlgorithmParameterSpec param)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, InvalidAlgorithmParameterException {
Cipher cipher = Cipher.getInstance(algorithm);
if (param == null) {
cipher.init(cipherMode, key);
} else {
cipher.init(cipherMode, key, param);
}
return cipher;
}
public static String encrypt(String algorithm, String keyStr, String ivStr, String unencryptedStr) throws Exception {
return encrypt(algorithm, keyStr, ivStr, unencryptedStr, StandardCharsets.UTF_8);
}
public static String encrypt(String algorithm, String keyStr, String ivStr, String unencryptedStr, Charset charset) throws Exception {
Cipher cipher = initCipher(algorithm, Cipher.ENCRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
byte[] encrypted = cipher.doFinal(unencryptedStr.getBytes(charset));
return encodeToString(encrypted);
}
public static String decrypt(String algorithm, String keyStr, String ivStr, String encryptedStr) throws Exception {
return decrypt(algorithm, keyStr, ivStr, encryptedStr, StandardCharsets.UTF_8);
}
public static String decrypt(String algorithm, String keyStr, String ivStr, String encryptedStr, Charset charset) throws Exception {
Cipher cipher = initCipher(algorithm, Cipher.DECRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
byte[] decrypted = cipher.doFinal(decode(encryptedStr));
return new String(decrypted, charset);
}
/**
* 解密文件
*/
public static void encryptFile(String algorithm, String keyStr, String ivStr, File source, File target) throws Exception {
checkPath(source, target);
Cipher cipher = initCipher(algorithm, Cipher.ENCRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
try (FileOutputStream fos = new FileOutputStream(target);
CipherInputStream cis = new CipherInputStream(new FileInputStream(source), cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = cis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
fos.flush();
}
}
/**
* 加密文件
*/
public static void decryptFile(String algorithm, String keyStr, String ivStr, File source, File target) throws Exception {
checkPath(source, target);
Cipher cipher = initCipher(algorithm, Cipher.DECRYPT_MODE, getKey(keyStr, AES), getIv(ivStr));
try (FileInputStream fis = new FileInputStream(source);
CipherOutputStream cos = new CipherOutputStream(new FileOutputStream(target), cipher)) {
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = fis.read(buffer)) != -1) {
cos.write(buffer, 0, len);
}
cos.flush();
}
}
public static void checkPath(File source, File target) throws IOException {
Objects.requireNonNull(source);
Objects.requireNonNull(target);
if (source.isDirectory() || !source.exists()) {
throw new FileNotFoundException(source.toString());
}
if (Objects.equals(source.getCanonicalPath(), target.getCanonicalPath())) {
throw new IllegalArgumentException("sourceFile equals targetFile");
}
File parentDirectory = target.getParentFile();
if (parentDirectory != null && !parentDirectory.exists()) {
Files.createDirectories(parentDirectory.toPath());
}
}
public static void main(String[] args) throws Exception {
System.out.println(generateAESKey());
System.out.println(generateAESIv(AES_ECB));
String keyStr = "dN2VIV86Z2ShT47pEC1XwQ==";
String ivStr = "00hDTDhCxa9t11TrQSso3w==";
String encrypted = encrypt(AES_CBC, keyStr, ivStr, "中國深圳");
System.out.println("encrypted:" + encrypted);
System.out.println(decrypt(AES_CBC, keyStr, ivStr, encrypted));
File source = new File("README.md");
File encryptedFile = new File("out/README1.md");
File decryptedFile = new File("out/README2.md");
encryptFile(AES_CBC, keyStr, ivStr, source, encryptedFile);
decryptFile(AES_CBC, keyStr, ivStr, encryptedFile, decryptedFile);
}
}