一.什么是对称加密
常见的加密方式分为三种:
1.正向加密:如MD5,加密后密文固定,目前还没有办法破解,但是能够通过数据库撞库有一定概率找到,不过现在一般用这种方式加密都会加上盐值。
2.对称加密:通过一个固定的对称密钥,对需要传输的数据进行加密,速度快,但是安全性不高,主要用于企业级内部系统中数据传输。
3.非对称加密:N把公钥,一把私钥,私钥存放在服务器一方保管,公钥可以放在任意一个客户端,客户端向服务器请求的密文只有拿到了秘钥的服务器一端可以解密。
本文主要介绍对称加密。对称加密是一种使用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。对称加密也称为密钥加密。所谓对称,就是采用这种加密方法的双方使用方式用同样的密钥进行加密和解密。密钥是控制加密和解密过程的指令。算法是一组规则,规定如何进行加密和解密。因此加密的安全性不仅取决于加密算法本身,密钥管理的安全性更是重要。因为加密和解密都使用同一个密钥,如何把密钥安全地传递到解密者手上就成了必须要解决的问题。
二.什么是随机盐值
wiki百科对盐值的介绍如下:
盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。
安全因素
通常情况下,当字段经过散列处理(如MD5),会生成一段散列值,而散列后的值一般是无法通过特定算法得到原始字段的。但是某些情况,比如一个大型的彩虹表,通过在表中搜索该MD5值,很有可能在极短的时间内找到该散列值对应的真实字段内容。
加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。
实现原理:
比如用户使用“abcd”这个字符串作为密码,经过MD5散列后得到了:E2FC714C4727EE9395F324CD2E7F331F
但是由于用户密码较简单,短密码的散列结果很容易通过撞库破解。
如果我们定义一个只有自己知道的字符串,放在要加密的文字任何地方,比如在abcd的第二个字符前加上:helloworld123123,那么要加密的字符变成了:ahelloword123123worldbcd
我们再对这个字符串进行散列得到978E1014EEFF5E0A708314DB2E7D6DA1,这样用撞库的方式就很难破解。这里的盐值的作用相当于扰乱了正常要加密的字符,而这个规律只有开发者,服务端的人知道。
三.使用异或的特性实现简单的对称加密
异或的运算规则是二进制运算中,相同得0,不同得1。异或还有一个特殊的性质就是逆运算,一个数异或相同得数两次还会得到它本身。通过异或这种特殊的运算性质,用它来做对称加密是比较好的选择。
下面给一个Java的简单实现
以abc作为密钥对1234进行加密。先将1,2,3,4分别转成ASCII码。查询ASCII码表,1,2,3,4分别对应着49,50,51,52。
加密:
49 -> 加密过程为:49 ^ 密钥 ^ 盐值 -> m1
50 -> 加密过程为:50 ^ 密钥 ^ m1 -> m2
51 -> 加密过程为:51 ^ 密钥 ^ m2 -> m3
52 -> 加密过程为:52 ^ 密钥 ^ m3 -> m4
经过上述运算后,密文就变成了m1、m2、m3、m4。
解密:
解密是上面加密的逆运算:已知条件为m1、m2、m3、m4
m4 -> 解密过程为:m4 ^ m3 ^ 密钥 -> 52
m3 -> 解密过程为:m3^ m2 ^ 密钥 -> 51
m2 -> 解密过程为:m2 ^ m1 ^ 密钥 -> 50
m1 -> 解密过程为:m1 ^ 盐值 ^ 密钥 -> 49
下面是实现代码:
package com.zuikc.test; public class Test6 { public static void main(String[] args) { String content = "1234"; // 需要加密的字符 String key = "abc"; // 密钥 byte[] result = encryption(content, key); System.out.println("1234加密后的值:" + new String(result)); System.out.println("---------------"); System.out.println("1234解密后的值:" + new String(decipher(new String(result), key))); } public static byte[] encryption(String content, String key) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte salt = 0; // 随机盐值 byte[] result = new byte[contentBytes.length]; for (int i = 0; i < contentBytes.length; i++) { salt = (byte) (contentBytes[i] ^ dkey ^ salt); result[i] = salt; } return result; } public static byte[] decipher(String content, String key) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte salt = 0; // 随机盐值 byte[] result = new byte[contentBytes.length]; for (int i = contentBytes.length - 1; i >= 0; i--) { if (i == 0) { salt = 0; } else { salt = contentBytes[i - 1]; } result[i] = (byte) (contentBytes[i] ^ dkey ^ salt); } return result; } }
运行后控制台输出如下
但是这样通过查看源码可以获取盐值和密钥可以轻易破解,如果将秘钥和盐值不以明码写在代码中可以提高安全性,改善后的代码如下:
package com.zuikc.test; import java.util.Random; public class Test6 { private static byte salt = (byte) new Random().nextInt(); //生成随机盐值 private static String key = getRandomString(new Random().nextInt()); //生成随机密钥 public static void main(String[] args) { String content = "1234"; // 需要加密的字符 byte[] result = encryption(content, key,salt); System.out.println("1234加密后的值:" + new String(result)); System.out.println("---------------"); System.out.println("1234解密后的值:" + new String(decipher(new String(result), key,salt))); } //加密 public static byte[] encryption(String content, String key,byte salt) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte[] result = new byte[contentBytes.length]; for (int i = 0; i < contentBytes.length; i++) { salt = (byte) (contentBytes[i] ^ dkey ^ salt); result[i] = salt; } return result; } //解密 public static byte[] decipher(String content, String key,byte salt) { byte[] contentBytes = content.getBytes(); byte[] keyBytes = key.getBytes(); byte dkey = 0; for (byte b : keyBytes) { dkey ^= b; } byte[] result = new byte[contentBytes.length]; for (int i = contentBytes.length - 1; i >= 0; i--) { if (i == 0) { salt = 0; } else { salt = contentBytes[i - 1]; } result[i] = (byte) (contentBytes[i] ^ dkey ^ salt); } return result; } //生成随机字符串的方法 public static String getRandomString(int length) { String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; Random random = new Random(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < length; i++) { int number = random.nextInt(62); sb.append(str.charAt(number)); } return sb.toString(); } }