前言
本文主要講解常用加密算法,消息摘要,二進制字符變換等的java實現,對於加密算法本身的原理只會做簡單的介紹,詳細的原理可百度。
相關概念
- 加密
加密是指將可讀取的明文作爲輸入,通過特定的變換操作得到不易讀取的輸出(通常是二進制序列),目前常用的加密算法包括 對稱加密的AES/DES,非對稱加密的RSA/DSA/EC,加密很重要的一點就是解密,無論多複雜的數學變換,一定可以通過相應的逆變換得到原始輸入,這是的加密行爲纔有意義。
- hash(哈希變換)
hash值又稱散列值或者消息摘要,對輸入的字符串或者二進制序列通過一定的變換得到
固定長度的輸出
,它是一個不可逆的過程,理解這個不可逆的過程可以從數學中的求餘函數理解,例如:11/10 = 1 ... 1餘數是1,以除以10
作爲變換,餘數
1作爲輸出,不可能通過餘數是1得到被除數是11,因爲有可能是21、31、41。。。。。。等等,同時和求餘類似,hash碰撞指的就是不同的輸入可能得到相同的輸出。當然對於真正的hash變換,不可能像求餘過程如此簡單,但是這個不可逆過程的原理是類似的。常用的hash變換有MD5/SHA1/HmacSHA1/HmacMD5....
等,hash變換的目的並不是讓輸入不可讀取,而是讓輸入不可改變。
- 字節變換
文件通常會分爲文本文件和二進制文件,文本文件通過(Unicode/UTF-8/ASCII)編碼之後是可以讀取的,而二進制文件是不可讀的,因爲部分數值沒有對應的編碼。但是在開發過程中,很多時候需要將不可讀的二進制數據轉成可讀的字符串進行傳輸,因此就有了字節變換操作,常用的字節變換操作有
Base64,UrlEncoder
,還有通過將二進制轉成十六進制字符進行變換,在MD5和SHA1變換中常用。字節變換最主要的目的是:將不易讀取或者不易傳輸的數據轉成易讀取或者易傳輸的字符串
相關api介紹
java中對於加密的支持api都在
java.security
和javax.crypto
包下,主要用到的類有:
Cipher
主要用於加密行爲,如進行AES/DES/RSA等加密行爲
- 初始化對象
static Cipher getInstance(String transformation)
transformation
的組成可以概括爲algorithm/mode/padding,algorithm
用於指定加密的方式,mode
用於指定特定加密方式的變換模式,padding
是字節填充規則。mode
和padding
可以不寫,可用的transformation
組合有:
//括號數值爲所需祕鑰的長度
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
- 初始化參數
void init(int opmode, Key key)
opmode
用於指定該對象是要進行加密還是解密,key
是加密所用的祕鑰信息。
- 加密方法
byte[] doFinal(byte[] input)
cipher.doFinal(byte[] input)
等同於cipher.update(byte[] input); cipher.doFinal();
示例代碼:
- AES加解密
@org.junit.Test
public void testCipherAES() throws Exception {
//指定使用AES加密
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//使用KeyGenerator生成key,參數與獲取cipher對象的algorithm必須相同
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
//指定生成的密鑰長度爲128
keyGenerator.init(128);
Key key = keyGenerator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal("helloworld".getBytes());
System.out.println("AES加密: " + Base64.getEncoder().encodeToString(bytes));
//由於AES加密在CBC模式下是需要有一個初始向量數組byte[] initializeVector ,
// 而解密的時候也需要同樣的初始向量,因此需要使用加密時的參數初始化解密的cipher,否則會出錯
byte[] initializeVector = cipher.getIV();
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializeVector);
cipher.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
//上面三步操作可以用此操作代替 cipher.init(Cipher.DECRYPT_MODE, key, cipher.getParameters());
bytes = cipher.doFinal(bytes);
System.out.println("AES解密: " + new String(bytes));
}
//輸出
AES加密: pRy4ZbW7qgZ33iWBJ60BDA==
AES加密: helloworld
- DES加解密
@org.junit.Test
public void testCipherDES() throws Exception {
//指定使用DES加密
Cipher cipher = Cipher.getInstance("DES");
//使用KeyGenerator生成key,參數與獲取cipher對象的algorithm必須相同
KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
//DES的祕鑰長度必須是56位
keyGenerator.init(56);
Key key = keyGenerator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal("helloworld".getBytes());
System.out.println("DES加密: " + Base64.getEncoder().encodeToString(bytes));
//與AES不同,由於DES並不需要初始向量,因此解密的時候不需要第三個參數
cipher.init(Cipher.DECRYPT_MODE, key);
bytes = cipher.doFinal(bytes);
System.out.println("DES解密: " + new String(bytes));
}
//輸出
DES加密: XoG4lEjZN4VBlZYTXjw6BQ==
DES解密: helloworld
- RSA加解密
@org.junit.Test
public void testCipherRSA() throws Exception {
//獲取cipher對象
Cipher cipher = Cipher.getInstance("RSA");
//通過KeyPairGenerator來生成公鑰和私鑰
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();//公鑰
PrivateKey privateKey = keyPair.getPrivate();//私鑰
/*加密*/
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(TEXT.getBytes());
final String encryptText = Base64.getEncoder().encodeToString(bytes);
System.out.println("RSA公鑰加密:" + encryptText);
/*解密*/
cipher.init(Cipher.DECRYPT_MODE, privateKey);
bytes = cipher.doFinal(Base64.getDecoder().decode(encryptText));
System.out.println("RSA解密:" + new String(bytes));
}
//輸出
RSA公鑰加密:bKbbpARpcHcCqcMdGmA/WzvyO2G3eXFJhmrK5F0yFlJsoGohg4XIq5egNc1eBQwP7BRD6m7c12byB/KpYNgWg7J5Y3kupWBahZyhJ7SWWF0YY9CrdWf55zQ/CPyn+KlWQg1ViBnIBnejABFuqjDgBmZ3Q3txT1tD4MIpGPCE+NY=
RSA私鑰解密:helloworld
Mac
該類主要用作Hmac運算,初始化方法
Mac.getInstance(String algorithm)
的可用參數有
HmacMD5
HmacSHA1
HmacSHA224
HmacSHA256
HmacSHA384
HmacSHA512
示例代碼
@org.junit.Test
public void testHmac() throws Exception {
Mac mac = Mac.getInstance("HmacMD5");
//第一個參數可以是任意字符串,第二個參數與獲取Mac對象的algorithm相同
SecretKeySpec secretKeySpec = new SecretKeySpec("123456".getBytes(), "HmacMD5");
mac.init(secretKeySpec);
byte[] bytes = mac.doFinal("helloworld".getBytes());
System.out.println("HmacMD5結果:" + HexUtil.toHexString(bytes));
mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec("123456".getBytes(), "HmacSHA1"));
bytes = mac.doFinal("helloworld".getBytes());
System.out.println("HmacSHA1結果:" + HexUtil.toHexString(bytes));
}
//輸出
HmacMD5結果:2449233556af565ecbb2fd6266df853b
HmacSHA1結果:ef9079b9e2e79c67a962f87e2a87af4f35c2ae37
Signature
signature類用於提供數字簽名,用於保證數據的完整性
示例代碼,Signature.getInstance(String algorithm)
的可用參數有
NONEwithRSA
MD2withRSA
MD5withRSA
SHA1withRSA
SHA224withRSA
SHA256withRSA
SHA384withRSA
SHA512withRSA
NONEwithDSA
SHA1withDSA
SHA224withDSA
SHA256withDSA
NONEwithECDSA
SHA1withECDSA
SHA224withECDSA
SHA256withECDSA
SHA384withECDSA
SHA512withECDSA
示例代碼:
@org.junit.Test
public void testSignature() throws Exception {
Signature signature = Signature.getInstance("NONEwithRSA");
//KeyPairGenerator生成公鑰和私鑰
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
//用私鑰初始化signature
signature.initSign(privateKey);
//更新原始字符串
signature.update(TEXT.getBytes());
byte[] bytes = signature.sign();
String sign = Base64.getEncoder().encodeToString(bytes);
System.out.println("數字簽名: " + sign);
//用公鑰初始化signature
signature.initVerify(publicKey);
//更新原始字符串
signature.update(TEXT.getBytes());
//校驗簽名是否正確
boolean result = signature.verify(Base64.getDecoder().decode(sign));
System.out.println("簽名校驗結果: " + result);
}
MessageDigest
MessageDigest
主要是做hash變換(也稱消息摘要或者散列值)
示例代碼:
@org.junit.Test
public void testMessageDigest() throws Exception {
//參數可以是 MD5,MD2,MD5,SHA-1,SHA-224,SHA-256,SHA-384,SHA-512
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] bytes = messageDigest.digest("helloworld".getBytes());
//將二進制數組轉成16進制字符串輸出
System.out.println("MD5哈希變換:" + HexUtil.toHexString(bytes));
messageDigest = MessageDigest.getInstance("SHA-1");
bytes = messageDigest.digest("helloworld".getBytes());
System.out.println("SHA1哈希變換:" + HexUtil.toHexString(bytes));
}
//HexUtil
public static String toHexString(byte[] data) {
StringBuilder builder = new StringBuilder();
int len = data.length;
String hex;
for (int i = 0; i < len; i++) {
hex = Integer.toHexString(data[i] & 0xFF);
if (hex.length() == 1) {
builder.append("0");
}
builder.append(hex);
}
return builder.toString();
}
//輸出 可以通過標準在線工具檢驗輸出結果的準確性
MD5哈希變換:fc5e038d38a57032085441e7fe7010b0
SHA1哈希變換:6adfb183a4a2c94a2f92dab5ade762a47889a5a1
KeyGenerator
用於生成祕鑰,
KeyGenerator.getInstance(String algorithm)
支持的參數有
AES (128)
DES (56)
DESede (168)
HmacSHA1
HmacSHA256
示例代碼
@org.junit.Test
public void testKeyGenerator() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
//初始化方法有多種,根據需要選擇
keyGenerator.init(128);
// keyGenerator.init(new SecureRandom("1234567".getBytes()));
SecretKey key = keyGenerator.generateKey();
//key的二進制編碼 將它保存到文件中
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] bytes = cipher.doFinal("helloworld".getBytes());
System.out.println("加密數據: " + Base64.getEncoder().encodeToString(bytes));
/*=========保存key的二進制編碼=========*/
byte[] keyBytes = key.getEncoded();
FileOutputStream fos = new FileOutputStream("F://test/key.txt");
fos.write(keyBytes);
fos.flush();
fos.close();
/*============從文件中讀取編碼並恢復key==============*/
FileInputStream fis = new FileInputStream("F://test/key.txt");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
fis.close();
/*==============使用SecretKeySpec重新生成key============*/
SecretKeySpec secretKeySpec = new SecretKeySpec(bos.toByteArray(), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, cipher.getParameters().getParameterSpec(IvParameterSpec.class));
bytes = cipher.doFinal(bytes);
System.out.println("解密數據: " + new String(bytes));
}
//輸出
加密數據: 0vUaeC2VWEvVpUWeDfgGhg==
解密數據: helloworld
KeyPairGenerator
KeyPairGenerator用於生成一對密鑰對,用於做非對稱加密操作。
KeyPairGenerator.getInstance(String alorithm)
的可用參數爲:
DSA
RSA
EC
代碼生成的密鑰對通常需要將公鑰和私鑰保存到文件中,這樣才能夠持久化進行操作,下面演示兩種保存的實現
- 分別保存公鑰和私鑰的二進制編碼
@org.junit.Test
public void testSaveKeyPair2() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PublicKey oldPbk = keyPair.getPublic();
PrivateKey oldPrk = keyPair.getPrivate();
Cipher cipher = Cipher.getInstance("RSA");
/*============使用原始私鑰加密,重新生成的公鑰解密===============*/
cipher.init(Cipher.ENCRYPT_MODE, oldPrk);
byte[] bytes = cipher.doFinal("helloworld".getBytes());
System.out.println("原始私鑰加密: " + Base64.getEncoder().encodeToString(bytes));
/*提取公鑰的比特編碼經過Base64轉換後保存到文件,注意公鑰的比特編碼是X.509格式*/
byte[] pbks = Base64.getEncoder().encode(oldPbk.getEncoded());
File file = new File("F://test/public.key");
FileOutputStream fos = new FileOutputStream(file);
fos.write(pbks);
fos.flush();
fos.close();
/*從文件中提取公鑰比特編碼並恢復成公鑰*/
file = new File("F://test/public.key");
FileInputStream fis = new FileInputStream(file);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
pbks = Base64.getDecoder().decode(bos.toByteArray());
X509EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(pbks);
//重新得到公鑰
PublicKey newPbk = KeyFactory.getInstance("RSA").generatePublic(encodedKeySpec);
cipher.init(Cipher.DECRYPT_MODE, newPbk);
bytes = cipher.doFinal(bytes);
System.out.println("新的公鑰解密: " + new String(bytes));
/*============使用原始公鑰加密,重新生成的私鑰解密===============*/
cipher.init(Cipher.ENCRYPT_MODE, oldPbk);
bytes = cipher.doFinal("helloworld".getBytes());
System.out.println("原始私鑰加密: " + Base64.getEncoder().encodeToString(bytes));
/*省略了文件存取操作,與公鑰相同*/
byte[] prks = oldPrk.getEncoded();
/*私鑰的比特編碼是pkcs8格式*/
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(prks);
PrivateKey newPrk = KeyFactory.getInstance("RSA").generatePrivate(pkcs8EncodedKeySpec);
cipher.init(Cipher.DECRYPT_MODE, newPrk);
bytes = cipher.doFinal(bytes);
System.out.println("新的私鑰解密: " + new String(bytes));
}
//輸出
原始私鑰加密: CO7FU1hQsEd8fYV4ZfWXquo/qrktte2n/WakdHJuw001aa9RM/mYl6yC6jLMGlm0fxuYlH92Zv9jA7k/0TVuor8Csvzmbm00RMBhnQCme+aQQbSoZDZEwJj1HtW6aK5MJRI4l/1W+g5X+Fs/6TLlbXpJM0k4epGMKUWwhO6cUiM=
新的公鑰解密: helloworld
原始私鑰加密: ixqqoM3aRig3P6GGPICsSOdH8KXRrlFn9GB1OVIWt46Q9ROsS84BW693fKB9ea8CnLJayc2KU1yhPlHHqq08gU8WOxVYeBQ4Bi3MnoJzUluE/UWNaMYZt/jCB6NZx57XEpNJ6uKG5TUmZJm+eoK0BF7A8sOX96UbPuZlHd4lD0w=
新的私鑰解密: helloworld
- 保存密鑰對的特徵值 公鑰(N,e)私鑰(N,d)
@org.junit.Test
public void testSaveKeyPair() throws Exception {
final String algorithm = "RSA";
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm);
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
/*特徵值N e d*/
BigInteger N = publicKey.getModulus();
BigInteger e = publicKey.getPublicExponent();
BigInteger d = privateKey.getPrivateExponent();
/**/
String nStr = Base64.getEncoder().encodeToString(N.toByteArray());
String eStr = Base64.getEncoder().encodeToString(e.toByteArray());
String dStr = Base64.getEncoder().encodeToString(d.toByteArray());
/*將這三個字符串保存到文件或者數據庫,通常n,e可以保存在客戶端,而n,d的數據必須保存在服務端*/
N = new BigInteger(Base64.getDecoder().decode(nStr));
e = new BigInteger(Base64.getDecoder().decode(eStr));
d = new BigInteger(Base64.getDecoder().decode(dStr));
/*根據N,e生成公鑰*/
RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(N, e);
PublicKey pbk = KeyFactory.getInstance(algorithm).generatePublic(publicKeySpec);
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE, pbk);
//bytes 是加密後的數據
byte[] bytes = cipher.doFinal("helloworld".getBytes());
//用base64轉換輸出
System.out.println("加密數據:" + Base64.getUrlEncoder().encodeToString(bytes));
/*根據N,d生成私鑰*/
RSAPrivateKeySpec ps = new RSAPrivateKeySpec(N, d);
PrivateKey prk = KeyFactory.getInstance(algorithm).generatePrivate(ps);
cipher.init(Cipher.DECRYPT_MODE, prk);
bytes = cipher.doFinal(bytes);
System.out.println("解密數據:" + new String(bytes));
}
//輸出
加密數據:nVqRtqMDvnm-4pjW0R1Q6sRCRbLpK4WRtG342ydEa8069Kv2OVRGE1Rm3iEFZjFCyh_z_0jlf5liqCgDCkN9I2Ci1qWvrvQo9wZKQG5g86OrxWHs7n1Kg_SXR3rNC-55jPxQAYUXpw-U9XX4ls7aQ85pk2BMZLYoRbwo3ktZAxM=
解密數據:helloworld