最近正好在項目中用到數(shù)據(jù)加密,于是從網(wǎng)上查閱一些資料债热,了解各種加密方式并寫代碼驗證砾嫉,就在本篇文章中做個總結(jié)吧。
我將從這幾個方面介紹 Java 中的加密方式以及相關(guān)的概念:
1. 異或加密
2. MD5 算法
3. Base64 編碼
4. DES 加密
5. AES 加密
6. RSA 加密
從嚴(yán)格意義上來說窒篱,MD5 和 Base64 不屬于加密焕刮,它們分別是信息摘要算法和編碼方式舶沿,但是網(wǎng)上好多人都說 MD5 加密、Base64 加密配并,我覺得有必要糾正一下括荡。對于其他的幾種加密方式,下面我會一一進(jìn)行舉例說明溉旋。
1. 異或加密
異或運算(xor)有個特點:數(shù) a 兩次異或同一個數(shù) b 仍然為 a畸冲,即(a^b)^b=a
。利用這個原理可以實現(xiàn)數(shù)據(jù)的加密和解密功能观腊。
舉個栗子:a=10100001邑闲,b=00000110
a=a^b; // a=10100111
b=b^a梧油; // b=10100001苫耸,此時 b 等于 a
異或運算直接對二進(jìn)制數(shù)據(jù)進(jìn)行操作,對每一位(bit)上的數(shù)據(jù)進(jìn)行變換儡陨。所以輸入和輸出的數(shù)據(jù)長度相同褪子,不占用額外的空間,可以用于字符和文件的加密骗村,效率比較高嫌褪。下面我們用代碼實踐一下:
// 加密的密鑰,構(gòu)造一定長度的字節(jié)數(shù)組
private final static byte[] KEY_BYTES = "Vp6flFpGW86g7hi6MhD3Zl2eThJTjPnIjXE4".getBytes();
private final static int KEY_LENGTH = KEY_BYTES.length;
/**
* 異或運算加密
*
* @param input 要加密的內(nèi)容
* @return 加密后的數(shù)據(jù)
*/
public static byte[] xorEncode(byte[] input) {
int keyIndex = 0;
int length = input.length;
for (int i = 0; i < length; i++) {
input[i] = (byte) (input[i] ^ KEY_BYTES[(keyIndex++ % KEY_LENGTH)]);
}
return input;
}
/**
* 異或運算解密
*
* @param input 要解密的內(nèi)容
* @return 解密后的數(shù)據(jù)
*/
public static byte[] xorDecode(byte[] input) {
int keyIndex = 0;
int length = input.length;
for (int i = 0; i < length; i++) {
input[i] = (byte) (input[i] ^ KEY_BYTES[(keyIndex++ % KEY_LENGTH)]);
}
return input;
}
為了方便查看加密后的內(nèi)容叙身,這里對輸出結(jié)果做了一下 Base64 編碼渔扎。
輸入:123456abcdef,輸出:Z0IFUl1aJzooFCIx
2. MD5 編碼
MD5 是將任意長度的數(shù)據(jù)字符串轉(zhuǎn)化成短小的固定長度的值的單向操作信轿,任意兩個字符串不應(yīng)有相同的散列值晃痴。因此 MD5 經(jīng)常用于校驗字符串或者文件,因為如果文件的 MD5 不一樣财忽,說明文件內(nèi)容也是不一樣的倘核,如果發(fā)現(xiàn)下載的文件和給定的 MD5 值不一樣,就要慎重使用即彪。
MD5 主要用做數(shù)據(jù)一致性驗證紧唱、數(shù)字簽名和安全訪問認(rèn)證,而不是用作加密隶校。比如說用戶在某個網(wǎng)站注冊賬戶時漏益,輸入的密碼一般經(jīng)過 MD5 編碼,更安全的做法還會加一層鹽(salt)深胳,這樣密碼就具有不可逆性绰疤。然后把編碼后的密碼存入數(shù)據(jù)庫,下次登錄的時候把密碼 MD5 編碼舞终,然后和數(shù)據(jù)庫中的作對比轻庆,這樣就提升了用戶賬戶的安全性癣猾。
使用 Java 實現(xiàn)簡單的 MD5 編碼:
/**
* 計算字符串的 MD5
*
* @param text 原文
* @return 密文
*/
public static String md5Encode(String text) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(text.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
String hexString = Integer.toHexString(b & 0xFF);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
sb.append(hexString);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
logger.error(e);
}
return null;
}
輸入:123456abcdef,輸出:6f3b8ded65bd7a4db11625ac84e579bb
3. Base64 編碼
Base64 編碼是我們程序開發(fā)中經(jīng)常使用到的編碼方法余爆,它用 64 個可打印字符來表示二進(jìn)制數(shù)據(jù)纷宇。這 64 個字符是:小寫字母 a-z、大寫字母 A-Z蛾方、數(shù)字 0-9像捶、符號"+"、"/"(再加上作為墊字的"="转捕,實際上是 65 個字符)作岖,其他所有符號都轉(zhuǎn)換成這個字符集中的字符唆垃。Base64 編碼通常用作存儲五芝、傳輸一些二進(jìn)制數(shù)據(jù)編碼方法,所以說它本質(zhì)上是一種將二進(jìn)制數(shù)據(jù)轉(zhuǎn)成文本數(shù)據(jù)的方案辕万。
在 Java 中使用 Base64 很簡單枢步,系統(tǒng)的 API 已經(jīng)封裝好方法,我們直接調(diào)用即可渐尿。
// 編碼
String encode = Base64.encodeToString(" 123456abcdef".getBytes(), Base64.DEFAULT);
// 解碼
byte[] decodeByte = Base64.decode(encode .getBytes(), Base64.DEFAULT);
String decode = new String(decodeByte);
輸入:123456abcdef醉途,輸出:MTIzNDU2YWJjZGVm
無論是編碼還是解碼都會有一個參數(shù) flags,系統(tǒng) API 提供了以下幾種:
- DEFAULT:表示使用默認(rèn)的方法來加密砖茸。
- NO_PADDING:表示省略加密字符串最后的 "="隘擎。
- NO_WRAP:表示省略所有的換行符(設(shè)置后 CRLF 就會失去作用)。
- CRLF:表示使用 CR凉夯、LF 這一對作為一行的結(jié)尾而不是 Unix 風(fēng)格的 LF货葬。
- URL_SAFE:表示加密時不使用對 URL 和文件名有特殊意義的字符來作為加密字符,就是以 "-" 和 "_" 代替 "+" 和 "/"劲够。
4. DES 加密
DES 是一種對稱加密算法震桶,所謂對稱加密算法就是:加密和解密使用相同密鑰的算法。DES 加密算法出自 IBM 的研究征绎,后來被美國政府正式采用蹲姐,之后開始廣泛流傳。但近些年使用越來越少人柿,因為 DES 使用 56 位密鑰柴墩,以現(xiàn)代的計算能力,24 小時內(nèi)即可被破解凫岖。
順便說一下 3DES(Triple DES)江咳,它是 DES 向 AES 過渡的加密算法,使用 3 條 56 位的密鑰對數(shù)據(jù)進(jìn)行三次加密隘截。是 DES 的一個更安全的變形扎阶。它以 DES 為基本模塊汹胃,通過組合分組方法設(shè)計出分組加密算法。比起最初的 DES东臀,3DES 更為安全着饥。
使用 Java 實現(xiàn) DES 加密解密,注意密碼長度要是 8 的倍數(shù)惰赋。加密和解密的 Cipher 構(gòu)造參數(shù)一定要相同宰掉,不然會報錯。
/* 加密使用的 key */
private final static byte[] KEY_BYTES = "Vp6fhlFXKpGW8k6QPRg7Q6Jb7HyAhRi6MIhJ2YtGD3Zl26eTthJTj5PnIjXH5EI4".getBytes();
/**
* DES 加密
*
* @param content 待加密內(nèi)容
* @param key 加密的密鑰
* @return 加密后的字節(jié)數(shù)組
*/
public static byte[] encryptDES(byte[] content, byte[] key) {
try {
SecureRandom random = new SecureRandom();
DESKeySpec desKey = new DESKeySpec(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKey);
// DES 是加密方式, EBC 是工作模式, PKCS5Padding 是填充模式
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
return cipher.doFinal(content);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* DES 解密
*
* @param content 待解密內(nèi)容
* @param key 解密的密鑰
* @return 解密的數(shù)據(jù)
*/
public static byte[] decryptDES(byte[] content, byte[] key) {
try {
SecureRandom random = new SecureRandom();
DESKeySpec desKey = new DESKeySpec(key);
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
SecretKey secretKey = keyFactory.generateSecret(desKey);
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey, random);
return cipher.doFinal(content);
} catch (Exception e) {
logger.error(e);
}
return null;
}
輸入:123456abcdef赁濒,輸出:j1kR1+ZraO2Tg78dHueoTg==
5. AES 加密
高級加密標(biāo)準(zhǔn)(英語:Advanced Encryption Standard轨奄,縮寫:AES),在密碼學(xué)中又稱 Rijndael 加密法拒炎,是美國聯(lián)邦政府采用的一種區(qū)塊加密標(biāo)準(zhǔn)挪拟。這個標(biāo)準(zhǔn)用來替代原先的 DES,已經(jīng)被多方分析且廣為全世界所使用击你。簡單說就是 DES 的增強版玉组,比 DES 的加密強度更高。
AES 與 DES 一樣丁侄,一共有四種加密模式:電子密碼本模式(ECB)惯雳、加密分組鏈接模式(CBC)、加密反饋模式(CFB)和輸出反饋模式(OFB)鸿摇。關(guān)于加密模式的介紹石景,推薦這篇文章:高級加密標(biāo)準(zhǔn)AES的工作模式(ECB、CBC拙吉、CFB潮孽、OFB)
/* 加密使用的 key */
private static final String AES_KEY = "KUbHwTqBy6TBQ2gN";
/* 加密使用的 IV */
private static final String AES_IV = "pIbF6GR3XEN1PG05";
/**
* AES 加密
*
* @param content 待解密內(nèi)容
* @param key 密鑰
* @return 解密的數(shù)據(jù)
*/
public static byte[] encryptAES(byte[] content, byte[] key) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
// AES 是加密方式, CBC 是工作模式, PKCS5Padding 是填充模式
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// IV 是初始向量,可以增強密碼的強度
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(AES_IV.getBytes()));
return cipher.doFinal(content);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* AES 解密
*
* @param content 待解密內(nèi)容
* @param key 密鑰
* @return 解密的數(shù)據(jù)
*/
public static byte[] decryptAES(byte[] content, byte[] key) {
try {
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(AES_IV.getBytes()));
return cipher.doFinal(content);
} catch (Exception e) {
logger.error(e);
}
return null;
}
輸入:123456abcdef庐镐,輸出:ho9cn9SvmeisfJy6Pv96oQ==
6. RSA 加密
RSA算法是一種非對稱加密算法恩商,所謂非對稱就是該算法需要一對密鑰,若使用其中一個加密必逆,則需要用另一個才能解密怠堪。目前它是最有影響力和最常用的公鑰加密算法,能夠抵抗已知的絕大多數(shù)密碼攻擊名眉。從提出到現(xiàn)今的三十多年里粟矿,經(jīng)歷了各種攻擊的考驗,逐漸為人們接受损拢,普遍認(rèn)為是目前最優(yōu)秀的公鑰方案之一陌粹。
該算法基于一個的數(shù)論事實:將兩個大質(zhì)數(shù)相乘十分容易,但是想要對其乘積進(jìn)行因式分解卻極其困難福压,因此可以將乘積公開作為加密密鑰掏秩。由于進(jìn)行的都是大數(shù)計算或舞,RSA 最快的情況也比 DES 慢上好幾倍,比對應(yīng)同樣安全級別的對稱密碼算法要慢 1000 倍左右蒙幻。所以 RSA 一般只用于少量數(shù)據(jù)加密映凳,比如說交換對稱加密的密鑰。
使用 RSA 加密主要有這么幾步:生成密鑰對邮破、公開公鑰诈豌、公鑰加密私鑰解密、私鑰加密公鑰解密抒和。
public static final String AES = "AES";
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
/**
* 隨機生成 RSA 密鑰對
*
* @param keyLength 密鑰長度, 范圍: 512~2048, 一般 1024
* @return 密鑰對
*/
public static KeyPair generateRSAKeyPair(int keyLength) {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
kpg.initialize(keyLength);
return kpg.genKeyPair();
} catch (NoSuchAlgorithmException e) {
logger.error(e);
}
return null;
}
/**
* 公鑰加密
*
* @param data 原文
* @param publicKey 公鑰
* @return 加密后的數(shù)據(jù)
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPublic);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 私鑰加密
*
* @param data 待加密數(shù)據(jù)
* @param privateKey 密鑰
* @return 加密后的數(shù)據(jù)
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 公鑰解密
*
* @param data 待解密數(shù)據(jù)
* @param publicKey 密鑰
* @return 解密后的數(shù)據(jù)
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
/**
* 私鑰解密
*
* @param data 待解密的數(shù)據(jù)
* @param privateKey 私鑰
* @return 解密后的數(shù)據(jù)
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey) {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
try {
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
} catch (Exception e) {
logger.error(e);
}
return null;
}
密鑰對生成后是一串灰常長特別長的數(shù)據(jù)矫渔,大家可以對其 Base64 后看看公鑰和隨機性如何。使用測試數(shù)據(jù)123456abcdef
加密過摧莽、Base64 編碼后的結(jié)果如下庙洼。加密這么短的字符串竟然生成這么長的密文,難怪 RSA 算法這么慢范嘱!
密文 Base64 后:X6yx1XfkVk4DZpnzcCSZr2oK+WMP7Azm4fBcGNEwWPrRdtf9isfMeKgQsI6kqOF5Vb5b5IYAIqHZRE5QcDbIM/3bTWVTVg/t7enGCUSxValIvJ/A37syWTUXlh59DZzBMgzG4rbziGCc8CGyO03XFq8gCncr4NMZXQwkKI8Alds=
一般來說送膳,客戶端和服務(wù)端的通信過程是這樣的:服務(wù)端生成 RSA 加密的密鑰對,把公鑰給客戶端丑蛤,私鑰偷偷保留∷貉郑客戶端在首次使用公鑰加密數(shù)據(jù)受裹,然后發(fā)送給服務(wù)端。服務(wù)端接收并處理后虏束,會把對稱加密的密鑰下發(fā)給客戶端棉饶。客戶端接收到對稱加密的密鑰镇匀,以后的通信就會使用對稱加密的方式照藻。當(dāng)然也可以由客戶端生成對稱加密的密鑰,然后用公鑰加密發(fā)給服務(wù)端汗侵。這樣在雙方交換密鑰時保證了安全幸缕,之后的對稱加密保證了效率。
好了晰韵,關(guān)于 Java 加密和編碼的方式就先介紹這么多发乔,歡迎大家留言交流~