浅谈加解密算法
背景
早之前,公司有很多套lbs服务,散落在各个部门。大数据部门也拥有自己的一套lbs。这套lbs应用范围较广,功能较齐全。
所以,现阶段,出于成本节约、易用性、统一管理的考虑,我们把其他部门的lbs也整合过来了。
整合过来后,面临一个问题:无法规范地管理业务方,并保证系统的安全性。
因此,我们需要一套加密算法来解决这个问题。
加密和解密
加密
数据加密的基本过程,就是对原来为明文的文件或数据按某种算法进行处理,使其成为 不可读的一段代码,通常称为“密文”。通过这样的途径,来达到保护数据不被非法人窃取、阅读的目的。
解密
加密的逆过程为解密,即将该编码信息转化为其原来数据的过程。
加密算法
加密算法分成对称加密和非对称加密。
- 对称加密的加密与解密的密钥是相同的
- 非对称加密的加密与解密的密钥是不同的
还有一类加密算法是不需要密钥的,它叫散列算法。
对称加密
对称加密算法又称为共享密钥加密算法。在对称加密算法中,使用的密钥只有一个,发送和接收双方都使用这个密钥对数据进行加密和解密。这就要求加密和解密方事先都必须知道加密的密钥。
DES
DES加密算法出自IBM的研究,后来被美国政府正式采用,之后开始广泛流传。但近些年使用越来越少,因为DES使用的是56位密钥,以现代的计算能力,24小时内即可被破解。
顺带说一下3DES(Triple DES),它是DES向AES过渡的加密算法,使用3条56位的密钥对数据进行三次加密。是 DES的一个更安全的变形。它以DES为基本模块,通过组合分组方法设计出分组加密算法。比起最初的DES,3DES更为安全。
使用Java实现DES加密解密,注意密码长度要是8的倍数。
//偏移量,长度必须为8的倍数
private static String IV = "12345678";
//钥匙,长度必须为8的倍数
private static String KEY = "87654321";
public static void main(String[] args) {
//字符串编码
System.out.println("编码结果:" + encrypt("hello world"));
//字符串解码
System.out.println("编码结果:" + decrypt("iug3JXsgvoWaRv0QF3cODw=="));
}
//加密
public static String encrypt(String str) {
try {
byte[] bytes = str.getBytes();
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
bytes = cipher.doFinal(bytes);
bytes = Base64.getEncoder().encode(bytes);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//解密
public static String decrypt(String str) {
try {
byte[] bytes = Base64.getDecoder().decode(str);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "DES");
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
bytes = cipher.doFinal(bytes);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
编码结果:iug3JXsgvoWLcJXvki5H7w==
编码结果:hello word
AES
高级加密标准(Advanced Encryption Standard,缩写:AES),在密码学中被称为Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。简单说就是DES的增强版,比DES的加密强度更高。
AES与DES一样,一共有4种加密模式:电子密码本模式(ECB)、加密分组链接模式(CBC)、加密反馈模式(CFB)和输出反馈模式(OFB);5种填充方式:NoPadding(不填充)、PKCS5Padding(缺几个字节就补充几个字节的几)、PKCS7Padding(缺几个字节就补几个字节的0)、ISO10126Padding(填充字符串由一个字节序列组成,此字节序列的最后一个字节填充字节序列的长度,其余字节填充随机数据)。
//偏移量,必须16位、24位、32位、64位
private static String IV ="asdfgh1234567890";
//钥匙,必须16位、24位、32位、64位
private static String KEY = "1234567890asdfgh";
public static void main(String[] args) {
//字符串编码
System.out.println("编码结果:" + encrypt("hello world"));
//字符串解码
System.out.println("编码结果:" + decrypt("UePyU9tACUkzOEHYFMUsNg=="));
}
//加密
public static String encrypt(String str) {
try {
byte[] bytes = str.getBytes();
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
bytes = cipher.doFinal(bytes);
bytes = Base64.getEncoder().encode(bytes);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//解密
public static String decrypt(String str) {
try {
byte[] bytes = Base64.getDecoder().decode(str);
IvParameterSpec ivParameterSpec = new IvParameterSpec(IV.getBytes());
SecretKeySpec secretKeySpec = new SecretKeySpec(KEY.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
bytes = cipher.doFinal(bytes);
return new String(bytes);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
编码结果:4e0lnjIW6s3vCd2SYMs5Lw==
编码结果:hello word
非对称加密
在通信双方,如果使用非对称加密,一般遵从这样的原则:公钥加密,私钥解密。因为公钥是公开的,若用来解密,那么很容易被不必要的人解密消息。因此,私钥也可以认为是个人身份的证明。
RSA
1977年,三位数学家Rivest、Shamir和Adleman设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字命名,叫做RSA算法。
这种算法非常可靠,密钥越长,它就越难破解。根据已经披露的文献,目前被破解的最长RSA密钥是768个二进制位。也就是说,长度超过768位的密钥,还无法破解(至少没人公开宣布)。因此可以认为,1024位的RSA密钥基本安全,2048位的密钥极其安全。
public static String RSA_ALGORITHM = "RSA";
public static String UTF8 = "UTF-8";
/**
* 密钥长度,DSA算法的默认密钥长度是1024
* 密钥长度必须是64的倍数,在512到65536位之间
* */
private static final int KEY_SIZE=1024;
public static void main(String[] args) throws Exception {
String password = "1234abcd5678";
KeyStore keys = createKeys();
byte[] publicKey = getPublicKey(keys);
byte[] privateKey = getPrivateKey(keys);
System.out.println("公钥:"+ Base64.encode(publicKey));
System.out.println("私钥:"+ Base64.encode(privateKey));
byte[] encryptByPublicKey = encryptByPublicKey(password.getBytes(), publicKey);
System.out.println("使用公钥加密后的数据:"+Base64.encode(encryptByPublicKey));
byte[] decryptByPrivateKey = decryptByPrivateKey(encryptByPublicKey, privateKey);
System.out.println("使用私钥解密后的数据:"+new String(decryptByPrivateKey));
}
/**
* 生成密钥对
* @return 密钥对对象
* @throws NoSuchAlgorithmException
*/
public static KeyStore createKeys() throws NoSuchAlgorithmException {
//KeyPairGenerator用于生成公钥和私钥对。密钥对生成器是使用 getInstance 工厂方法
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(KEY_SIZE);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey)keyPair.getPrivate();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
KeyStore keyStore = new KeyStore( publicKey, privateKey);
return keyStore;
}
/**
* 获取私钥
* @param keyStore
* @return
*/
private static byte[] getPrivateKey(KeyStore keyStore){
return ((RSAPrivateKey)keyStore.privateKey).getEncoded();
}
/**
* 获取公钥
* @param keyStore
* @return
*/
private static byte[] getPublicKey(KeyStore keyStore){
return ((RSAPublicKey)keyStore.publicKey).getEncoded();
}
/**
* 私钥加密
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
* */
public static byte[] encryptByPrivateKey(byte[] data,byte[] key) throws Exception{
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory=KeyFactory.getInstance(RSA_ALGORITHM);
//生成私钥
PrivateKey privateKey=keyFactory.generatePrivate(pkcs8KeySpec);
//数据加密
Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
* @param data
* @param key
* @return
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
* @throws NoSuchPaddingException
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws InvalidKeyException
*/
private static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
//初始化公钥,根据给定的编码密钥创建一个新的 X509EncodedKeySpec。
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(key);
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
* */
public static byte[] decryptByPrivateKey(byte[] data,byte[] key) throws Exception{
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec=new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory=KeyFactory.getInstance(RSA_ALGORITHM);
//生成私钥
PrivateKey privateKey=keyFactory.generatePrivate(pkcs8KeySpec);
//数据解密
Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
* */
public static byte[] decryptByPublicKey(byte[] data,byte[] key) throws Exception{
//实例化密钥工厂
KeyFactory keyFactory=KeyFactory.getInstance(RSA_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec=new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey=keyFactory.generatePublic(x509KeySpec);
//数据解密
Cipher cipher=Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
//定义密钥类
@Data
@AllArgsConstructor
public static class KeyStore{
private Object publicKey;
private Object privateKey;
}
公钥:MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHTwX+IRPCBjoViOiXb2skxjb4BiNR/ig+lRPO
PjF/craPB3OBMyf3Amt940slPTLEsQRKwZgYM6tUOBGLBUvYlN/+wXYXq0WO5qgxu0ZGMCpZmqDH
A6i6+GllPahEJkeb11AQ4/v0vSKJNTgzGkwkzjrt1Kb5/i8CSW3ngBXOkQIDAQAB
私钥:MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAIdPBf4hE8IGOhWI6JdvayTGNvgG
I1H+KD6VE84+MX9yto8Hc4EzJ/cCa33jSyU9MsSxBErBmBgzq1Q4EYsFS9iU3/7BdherRY7mqDG7
RkYwKlmaoMcDqLr4aWU9qEQmR5vXUBDj+/S9Iok1ODMaTCTOOu3Upvn+LwJJbeeAFc6RAgMBAAEC
gYBIR7V60K6eSfdDQ7SEGNfSeDQrABRkG3HiJH7FAMIuQ9gzxZY1IV+q+tbzE7UzumJhYfJaANq/
qvJN9fmhGv4tcebFgMIVh4B256A5S6ejwC936dfSjAfYymAj0sdPBZt0Mbf+JlEujGH+TbbRieUd
HlITMHcv2OmDDT7WRrZ9QQJBAO/vg1dlDOpBJw8qSeleDdE5X5XueUrQhg5xp7SsG4Ensq9Rqwfw
BA0FIaRy5/yFE12e6V1KRF/VixBOu2Npz7kCQQCQXjWGmkO2nJiDMYcuGjZPXyFGwFrt6GXNlNxO
aWK4g3/3TI+nrhnf5cCLhPx89VsFfRUN3QK4RtHAotXuaHGZAkEA1jLB02Sf+gU+JHjBUTgpJ66L
meMyn0q3Uu++Izo+t7fD+mXzRPvN8f2xwgje9/2pgzvZ4/HoRpHyOxW+joqo6QJAeaVISND3uYmd
4P38v9YKaTrpWSp/8/NcxKtoLxIRwxPa4dA7Q7kpyEVZxrYZ4lkDh05sTTArOIdD+qO6y5ZqEQJB
AJbIOFnPaIqbsn76ATGCi10fJorZUQGGc3UYm4/pFsqrDawDTVI/3ogkbnFFLn5RWQbls4l3Yhu+
99w65lNhSHM=
使用公钥加密后的数据:LDW0q/vjx8tOCAUIHTyQU+Talf9oiavQvR942ANc+sCFSyJwIRELW5CIbukffdFks08ugxFnkFD2
FeU63tjhqVY6G1R2Qmq7vNsBj4SWceOrIJ4gam95XJO6dCN1YCnNq46GPcenGi4f9zi91r9UeTUs
P+fjidEFNyRxfotZd9A=
使用私钥解密后的数据:1234abcd5678
散列算法
散列算法可以将消息转换成一个固定长度的唯一的字符串。唯一的意思是不同的消息转换成的摘要是不同的,并且能狗保证唯一。
这个过程是不可逆的,即不能通过摘要反推出明文。
利用这一特性,散列算法可以用来验证消息的完整性。
MD5
MD5是将任意长度的数据字符串转化成短小的固定长度的值的单向操作,任意两个字符串不应该有相同的散列值。
MD5主要做数据的一致性验证、数字签名和安全访问认证,而不是用作加密。
MD5算法具有以下特点:
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
/**
* MD5方法
*
* @param text 明文
* @param salt 盐
* @return 密文
* @throws Exception
*/
public static String md5(String text, String salt) throws Exception {
//加密后的字符串
String encodeStr = DigestUtils.md5Hex(String.format("%s%s", text , salt));
return encodeStr;
}
各算法对比
算法类型 | 特点 | 优势 | 缺陷 | 代表算法 | 应用场景 |
---|---|---|---|---|---|
对称加密 | 加密、解密的密钥相或可推算 | 性能好 | 需要提前共享密钥,易泄漏 | DES、AES、 | 敏感数据加密 |
非对称加密 | 加密、解密的密钥毫无相关 | 提供公钥给业务方即可 | 性能较差 | RSA、DSA | 密钥 |
散列算法 | 不可逆 | MD5、SHA1 | 验证消息完整性 |