一、加密算法选择
密码学中两种常见的密码算法为:对称密码算法和非对称密码算法。
对称密钥加密,又称私钥加密,即信息的发送方和接收方用一个密钥去加密和解密数据。它的最大优势是加/解密速度快,适合于对大数据量进行加密,但密钥管理困难。
非对称密钥加密,又称公钥密钥加密。它需要使用一对密钥来分别完成加密和解密操作,一个公开发布,即公开密钥,另一个由用户自己秘密保存,即私用密钥。信息发送者用公开密钥去加密,而信息接收者则用私用密钥去解密。公钥机制灵活,但加密和解密速度却比对称密钥加密慢得多。
对称加密算法用来对敏感数据等信息进行加密,常用的算法包括:
DES(Data Encryption Standard):数据加密标准,速度较快,适用于加密大量数据的场合。
3DES(Triple DES):是基于DES,对一块数据用三个不同的密钥进行三次加密,强度更高。
AES(Advanced Encryption Standard):高级加密标准,是下一代的加密算法标准,速度快,安全级别高;
Mysql官网手册
https://dev.mysql.com/doc/refman/5.7/en/encryption-functions.html#function_aes-encrypt
MySQL的内置加密函数里已经不推荐DES算法,所以选用AES算法来加解密账号密码。
二、加密策略
AES算法加解密都需要同一个key,如果这个key用来加密所有账号的密码,那么密码相同的不同账号得到的加密结果是相同的,这样会降低安全性,所以用可以唯一标识账号的aid来当做key,为了更不容易反向推导出key,实际应用中会用一个内部的salt字符串拼上账号aid来作为key。
三、实现
为了灵活和重用,没使用MySQL内置函数,改用Java实现。
AES算法实现与操作系统有关,本实现兼容Windows/Linux。
加密后获得的是byte[]类型结果,因此数据库内相应字段的类型应为BLOB,节省空间用TinyBlob类型就够。
加密算法需要使用外部类库javabase64-1.3.1.jar
Java实现有两个类:
AESUtils:提供加解密静态方法
Base64Utils :辅助AESUtils类
AESUtils常用方法说明:
方法 |
返回 |
说明 |
参数 |
getSecretKey(String seed) |
String |
生成key |
seed: salt+aid |
encrypt(byte[] data, String key) |
byte[] |
加密 |
data: 密码明文getBytes(“UTF-8”) key: getScretKey方法结果 |
decrypt(byte[] data, String key) |
byte[] |
解密 |
data: 密码密文 key: getScretKey方法结果 |
AESUtils.java:
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; public class AESUtils { private static final String ALGORITHM = "AES"; private static final int KEY_SIZE = 128; private static final int CACHE_SIZE = 1024; public static String getSecretKey() throws Exception { return getSecretKey(null); } public static String getSecretKey(String seed) throws Exception { try { KeyGenerator _generator = KeyGenerator.getInstance(ALGORITHM); SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); secureRandom.setSeed(seed.getBytes("UTF-8")); _generator.init(KEY_SIZE, secureRandom); SecretKey secretKey = _generator.generateKey(); return Base64Utils.encode(secretKey.getEncoded()); } catch (Exception e) { throw new RuntimeException("初始化密钥出现异常"); } } 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); } 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(); } } 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); } 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(); } } private static Key toKey(byte[] key) throws Exception { SecretKey secretKey = new SecretKeySpec(key, ALGORITHM); return secretKey; } }
Base64Utils.java:
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; public class Base64Utils { private static final int CACHE_SIZE = 1024; public static byte[] decode(String base64) throws Exception { return Base64.decode(base64.getBytes("UTF-8")); } public static String encode(byte[] bytes) throws Exception { return new String(Base64.encode(bytes)); } public static String encodeFile(String filePath) throws Exception { byte[] bytes = fileToByte(filePath); return encode(bytes); } public static void decodeToFile(String filePath, String base64) throws Exception { byte[] bytes = decode(base64); byteArrayToFile(bytes, filePath); } 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; } 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(); } }