首先,加密技术通常分为两种:1.对称加密。2.非对称加密。
对称式加密技术
对称式加密就是加密和解密使用同一个密钥,通常称之为“Session Key ”这种加密技术在当今被广泛采用,如美国政府所采用的DES加密标准就是一种典型的“对称式”加密法,它的Session Key长度为56bits。
非对称式加密技术
非对称式加密就是加密和解密所使用的不是同一个密钥,通常有两个密钥,称为“公钥”和“私钥”,它们两个必需配对使用,否则不能打开加密文件。这里的“公钥”是指可以对外公布的,“私钥”则不能,只能由持有人一个人知道。它的优越性就在这里,因为对称式的加密方法如果是在网络上传输加密文件就很难不把密钥告诉对方,不管用什么方法都有可能被别窃听到。而非对称式的加密方法有两个密钥,且其中的“公钥”是可以公开的,也就不怕别人知道,收件人解密时只要用自己的私钥即可以,这样就很好地避免了密钥的传输安全性问题。
常见的加密算法:
DSA(Digital Signature Algorithm):数字签名算法,是一种标准的 DSS(数字签名标准),严格来说不算加密算法;
AES(Advanced Encryption Standard):高级加密标准,对称算法,是下一代的加密算法标准,速度快,安全级别高,在21世纪AES 标准的一个实现是 Rijndael 算法;
BLOWFISH,它使用变长的密钥,长度可达448位,运行速度很快;
MD5:严格来说不算加密算法,只能说是摘要算法;收件人解密时只要用自己的私钥即可以,这样就很好地避免了密钥的传输安全性问题。
其实之前,我也没有接触过这些加密算法,但是之前有个需求,需要对传输的文件加密。一开始学习这些加密算法,确实头晕,对称,非对称,公钥,私钥,密钥......等一堆的词汇,虽然之前准备软件设计师考试的时候,信息安全这一块有学习过,但是真正在开发中,我没有尝试写过加密。把对称和非对称的都大致了解了一下,从DES、AES和PBE再到DH、RSA和ELGamal。一开始盲目的选择了使用了RSA,这种非对称加密算法,却忽略了,这种非对称加密算法,不能对稍微大一点儿的加密,加密字符串还是可以的。所以,一般在实际开发中,会先采用对称加密,然后再对密钥进行非对称加密(有理解不对的地方,还希望大神可以指点)。于是我选择了AES,速度快且安全级别高。具体实现看以下代码。
项目需要的jar包
base64编码包
package cn.blogTest.aes;
import it.sauronsoftware.base64.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* <p>
* BASE64编码解码工具包
* </p>
* <p>
* 依赖javabase64-1.3.1.jar
* </p>
*
* @version 1.0
*/
public class Base64Utils {
/**
* 文件读取缓冲区大小
*/
private static final int CACHE_SIZE = 1024;
/**
* <p>
* BASE64字符串解码为二进制数据
* </p>
*
* @param base64
* @return
* @throws Exception
*/
public static byte[] decode(String base64) throws Exception {
return Base64.decode(base64.getBytes());
}
/**
* <p>
* 二进制数据编码为BASE64字符串
* </p>
*
* @param bytes
* @return
* @throws Exception
*/
public static String encode(byte[] bytes) throws Exception {
return new String(Base64.encode(bytes));
}
/**
* <p>
* 将文件编码为BASE64字符串
* </p>
* <p>
* 大文件慎用,可能会导致内存溢出
* </p>
*
* @param filePath 文件绝对路径
* @return
* @throws Exception
*/
public static String encodeFile(String filePath) throws Exception {
byte[] bytes = fileToByte(filePath);
return encode(bytes);
}
/**
* <p>
* BASE64字符串转回文件
* </p>
*
* @param filePath 文件绝对路径
* @param base64 编码字符串
* @throws Exception
*/
public static void decodeToFile(String filePath, String base64) throws Exception {
byte[] bytes = decode(base64);
byteArrayToFile(bytes, filePath);
}
/**
* <p>
* 文件转换为二进制数组
* </p>
*
* @param filePath 文件路径
* @return
* @throws Exception
*/
public static byte[] fileToByte(String filePath) throws Exception {
byte[] data = new byte[0];
File file = new File(filePath);
if (file.exists()) {
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
in.close();
data = out.toByteArray();
}
return data;
}
/**
* <p>
* 二进制数据写文件
* </p>
*
* @param bytes 二进制数据
* @param filePath 文件生成目录
*/
public static void byteArrayToFile(byte[] bytes, String filePath) throws Exception {
InputStream in = new ByteArrayInputStream(bytes);
File destFile = new File(filePath);
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
OutputStream out = new FileOutputStream(destFile);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
in.close();
}
}
然后创建一个AES加密解密工具包
package cn.blogTest.aes;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* <p>
* AES加密解密工具包
* </p>
* @version 1.0
*/
public class AESUtils {
private static final String ALGORITHM = "AES";
private static final int KEY_SIZE = 128;
private static final int CACHE_SIZE = 1024;
/**
* <p>
* 生成随机密钥
* </p>
*
* @return
* @throws Exception
*/
public static String getSecretKey() throws Exception {
return getSecretKey(null);
}
/**
* <p>
* 生成密钥
* </p>
*
* @param seed 密钥种子
* @return
* @throws Exception
*/
public static String getSecretKey(String seed) throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
SecureRandom secureRandom;
if (seed != null && !"".equals(seed)) {
secureRandom = new SecureRandom(seed.getBytes());
} else {
secureRandom = new SecureRandom();
}
keyGenerator.init(KEY_SIZE, secureRandom);
SecretKey secretKey = keyGenerator.generateKey();
return Base64Utils.encode(secretKey.getEncoded());
}
/**
* <p>
* 加密
* </p>
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] encrypt(byte[] data, String key) throws Exception {
Key k = toKey(Base64Utils.decode(key));
byte[] raw = k.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* <p>
* 文件加密
* </p>
*
* @param key
* @param sourceFilePath
* @param destFilePath
* @throws Exception
*/
public static void encryptFile(String key, String sourceFilePath, String destFilePath) throws Exception {
File sourceFile = new File(sourceFilePath);
File destFile = new File(destFilePath);
if (sourceFile.exists() && sourceFile.isFile()) {
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(destFile);
Key k = toKey(Base64Utils.decode(key));
byte[] raw = k.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
CipherInputStream cin = new CipherInputStream(in, cipher);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = cin.read(cache)) != -1) {
out.write(cache, 0, nRead);
out.flush();
}
out.close();
cin.close();
in.close();
}
}
/**
* <p>
* 解密
* </p>
*
* @param data
* @param key
* @return
* @throws Exception
*/
public static byte[] decrypt(byte[] data, String key) throws Exception {
Key k = toKey(Base64Utils.decode(key));
byte[] raw = k.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
return cipher.doFinal(data);
}
/**
* <p>
* 文件解密
* </p>
*
* @param key
* @param sourceFilePath
* @param destFilePath
* @throws Exception
*/
public static void decryptFile(String key, String sourceFilePath, String destFilePath) throws Exception {
File sourceFile = new File(sourceFilePath);
File destFile = new File(destFilePath);
if (sourceFile.exists() && sourceFile.isFile()) {
if (!destFile.getParentFile().exists()) {
destFile.getParentFile().mkdirs();
}
destFile.createNewFile();
FileInputStream in = new FileInputStream(sourceFile);
FileOutputStream out = new FileOutputStream(destFile);
Key k = toKey(Base64Utils.decode(key));
byte[] raw = k.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(raw, ALGORITHM);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
CipherOutputStream cout = new CipherOutputStream(out, cipher);
byte[] cache = new byte[CACHE_SIZE];
int nRead = 0;
while ((nRead = in.read(cache)) != -1) {
cout.write(cache, 0, nRead);
cout.flush();
}
cout.close();
out.close();
in.close();
}
}
/**
* <p>
* 转换密钥
* </p>
*
* @param key
* @return
* @throws Exception
*/
private static Key toKey(byte[] key) throws Exception {
SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
return secretKey;
}
}
再创建一个工具方法,便于操作
package cn.blogTest.aes;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
/**
* 文件加密解密操作类
*
*/
public class AesTool {
//文件解析密钥
private static String key = getKey();
public AesTool(){}
/**
*
* 注意:对文件的读写操作一定要指定编码,否则会导致很多未知的编码问题。
*/
//文件加密
public static void encryptFile(String sourceFilePath,String destFilePath ) throws Exception {
//测试路径
// sourceFilePath = "E:\\work\\yonyou-workday\\2016基础资料.sql";
// destFilePath = "E:\\work\\yonyou-workday\\2016基础资料_encrypted.sql";
AESUtils.encryptFile(key, sourceFilePath, destFilePath);
}
//文件解密
public static void decryptFile(String sourceFilePath,String destFilePath) throws Exception {
//测试路径
// sourceFilePath = "E:\\work\\yonyou-workday\\2016基础资料_encrypted.sql";
// destFilePath = "E:\\work\\yonyou-workday\\2016基础资料decrypted.sql";
AESUtils.decryptFile(key, sourceFilePath, destFilePath);
}
//获得密钥
private static String getKey()
{
BufferedReader bRead = null;
StringBuffer sb = null;
try {
bRead = new BufferedReader(new FileReader("AesKey.txt"));
sb = new StringBuffer();
sb.append("");
String line = null;
while((line = bRead.readLine()) != null){
sb.append(line);
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
finally{
try {
bRead.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
最后,再写我们的测试类,我这里用了Junit
public class AesTest {
@Test
public void testGetKey() {
BufferedWriter bw = null;
try {
String secretKey = AESUtils.getSecretKey();
File file = new File("AesKey.txt");
bw = new BufferedWriter(new FileWriter(file));
bw.write(secretKey);
bw.flush();
} catch (Exception e) {
try {
bw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}
}
}
第一步先执行这个方法,得到密钥,我把密钥存放在了AesKey.txt方便读取。根据实际功能需求的不同,存放在不同路径,我这里存放在了项目下,执行该方法后,刷新项目:
第二步,对文件进行加密,这里我测试的是一个TXT文件,里面是忽然之间的歌词
//文件加密
@Test
public void testAesEncrypt() throws Exception {
AesTool.encryptFile("E:\\work\\忽然之间.txt", "E:\\work\\忽然之间Encrypt.txt");
}
执行完以后,可以发现在E:\work\目录下
而加密后的文件
已经是一堆乱码了,好慌,文件难道中毒了??
别慌,我们来解密
//文件解密
@Test
public void testDecrypt() throws Exception {
AesTool.decryptFile("E:\\work\\忽然之间Encrypt.txt", "E:\\work\\忽然之间Decrypt.txt");
}
执行完后,你会发现
多了一个解密后的文件Decrypt,我们来开来看看:
又回来了!
完美实现了AES的加密解密。
但是AES加密后,文件都是乱码,在一些需求里,如果让客户看到这样的乱码,他们或许会怀疑自己的电脑中毒了吧~有的时候,需求就是要明文,所以,我选择了MD5生成校验码,再去验证。
MD5验证其实很简单,归根究底还是IO流的操作。代码晚些再贴。