最近正好在項目中用到數(shù)據(jù)加密,于是從網(wǎng)上查閱一些資料,了解各種加密方式并寫代碼驗證,就在本篇文章中做個總結(jié)吧。
我將從這幾個方面介紹 Java 中的加密方式以及相關(guān)的概念:
1. 異或加密
2. MD5 算法
3. Base64 編碼
4. DES 加密
5. AES 加密
6. RSA 加密
從嚴格意義上來說,MD5 和 Base64 不屬于加密,它們分別是信息摘要算法和編碼方式,但是網(wǎng)上好多人都說 MD5 加密、Base64 加密,我覺得有必要糾正一下。對于其他的幾種加密方式,下面我會一一進行舉例說明。
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
異或運算直接對二進制數(shù)據(jù)進行操作,對每一位(bit)上的數(shù)據(jù)進行變換。所以輸入和輸出的數(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ù)字簽名和安全訪問認證,而不是用作加密。比如說用戶在某個網(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 個可打印字符來表示二進制數(shù)據(jù)。這 64 個字符是:小寫字母 a-z、大寫字母 A-Z、數(shù)字 0-9、符號"+"、"/"(再加上作為墊字的"=",實際上是 65 個字符),其他所有符號都轉(zhuǎn)換成這個字符集中的字符。Base64 編碼通常用作存儲、傳輸一些二進制數(shù)據(jù)編碼方法,所以說它本質(zhì)上是一種將二進制數(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:表示使用默認的方法來加密。
- 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ù)進行三次加密。是 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 加密
高級加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學(xué)中又稱 Rijndael 加密法,是美國聯(lián)邦政府采用的一種區(qū)塊加密標準。這個標準用來替代原先的 DES,已經(jīng)被多方分析且廣為全世界所使用。簡單說就是 DES 的增強版,比 DES 的加密強度更高。
AES 與 DES 一樣,一共有四種加密模式:電子密碼本模式(ECB)、加密分組鏈接模式(CBC)、加密反饋模式(CFB)和輸出反饋模式(OFB)。關(guā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)歷了各種攻擊的考驗,逐漸為人們接受,普遍認為是目前最優(yōu)秀的公鑰方案之一。
該算法基于一個的數(shù)論事實:將兩個大質(zhì)數(shù)相乘十分容易,但是想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。由于進行的都是大數(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ā)給客戶端??蛻舳私邮盏綄ΨQ加密的密鑰,以后的通信就會使用對稱加密的方式。當然也可以由客戶端生成對稱加密的密鑰,然后用公鑰加密發(fā)給服務(wù)端。這樣在雙方交換密鑰時保證了安全,之后的對稱加密保證了效率。
好了,關(guān)于 Java 加密和編碼的方式就先介紹這么多,歡迎大家留言交流~