最近项目中设计加密解密的技术应用场景比较多,因此总结一下JAVA中常见的加密技术。今天总结一下MD5加密。
一、MD5的必要性以及实际应用场景
MD5为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。将数据(如汉字)运算为另一固定长度值,是杂凑算法的基础原理,MD5的前身有MD2、MD3和MD4。
1.MD5算法具有以下特点:
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
5、强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
根据以上的特定我们能总结出几个根据以上特点衍生出来可以供我们使用的特性:
1.方便存储:MD5加密出来都是32位的字符串,能够给定固定大小的空间存储,传输,验证
2.文件加密:MD5运用在文件加密上很有优势,因为只需要32为字符串就能对一个巨大的文件进行验证完整性
3.不 可 逆:MD5加密出来只会截取末尾32位,具有良好的安全性,如果是对于参数加密很难伪造MD5
4.加密损耗低:MD5加密对于性能的消耗微乎其微(我获得的结果是:0.001毫秒)
2.实际上需要如何应用呢
我在实践中常常会用到的MD5校验加密一般运用场景:用户密码,请求参数,文件校验
2.1.用户密码
对于用户密码加密最高境界就是:别人获得你数据库的用户资料别人也没有办法获知密码.要达到就要有一套复杂的加密规则,一般常用的规则比如:
MD5(MD5(用户名+用户密码)+MD5(KEY+项目名+公司名)) 这样可以避免和别人碰库不排除别人用MD5来攻击你的服务器来匹配.
当然还可以自定很多种加密方法,就算知道加密方法也几乎无法去推送出用户原密码是什么
2.2.请求参数校验
都与服务器来言排除系统问题最大的问题就是害怕请求被拦截,拦截修改之后就有很多漏洞的可能性了
为了避免被拦截,参数被修改这种文件的常用方法就是对请求参数进行校验,就算拦截了请求参数修改了只要模拟不出MD5加密出来的值,在服务器过滤器直接就会进行拦截.
我这边推荐的请求校验方法在传递参数的时候带上 MD5值 随机数 时间戳 当然这几个都是由客户端生成
MD5=MD5(随机数+时间戳+MD5(KEY+公司名+项目名)) 当然这个规则也是可以定制的
请求参数在服务器拦截器就用客户端传递过来的 随机数 时间戳 来做校验如果不通过就不让继续访问(在这里的随机数 时间戳在后面的请求安全请求唯一性验证中会起到很大的作用所以建议保留)
2.3文件校验
当然对于一些图片已经一些很小很小的文件来说可以不用MD5校验应为基本上都是一次请求就完成了上传,而且显示的时候也不需要验证图片完不整.
但是如果是遇到了大文件上传MD5 就起到作用了,当然不是吧一个几个G 的文件一次性上传使用MD5校验,这边100%会失败 就算传递到服务端了 这个时间是不能被接受的 ,而且服务器最好是对请求做好限制(以后会开一篇来单独探讨文件上传的问题)
我们对于大文件上传的处理方式是进行分片上传,也就是所谓的断点续传,里面的实现机制
如果有一个5MB的文件 客户端把它分割成5份 1MB的文件 在上传的时候 上传两个MD5值 一个是当前上传的片1MB文件流的MD5 还有一个就是拼接之后的MD5(如果现在上传的是第二片 这个MD5就应该是第一片加上第二片的MD5)通过这样的方式能保证文件的完整性
当如果文件传到一半断了,用户换了台机器传 通过验证文件MD5 值就可以得知用户已经传到了第几片 就可以告诉用户从第几片开始传递 就解决了这个问题
二、MD5的加密原理
MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
在MD5算法中,首先需要对信息进行填充,使其字节长度对512求余数的结果等于448。因此,信息的字节长度(Bits Length)将被扩展至N*512+448,即N*64+56个字节(Bytes),N为一个正整数。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后再在这个结果后面附加一个以64位二进制表示的填充前的信息长度。经过这两步的处理,现在的信息字节长度=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍数。这样做的原因是为满足后面处理中对信息长度的要求。MD5中有四个32位被称作链接变量(Chaining Variable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。 当设置好这四个链接变量后,就开始进入算法的四轮循环运算,循环的次数是信息中512位信息分组的数目。
将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。 主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结果加上第四个变量(文本中的一个子分组和一个常数)。
再将所得结果向右环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之一。 以一下是每次操作中用到的四个非线性函数(每轮一个)。
其中,?是异或,∧是与,∨是或, 是反符号。
如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。所有这些完成之后,将A,B,C,D分别加上a,b,c,d。然后用下一分组数据继续运行算法,最后的输出是A,B,C和D的级联。最后得到的A,B,C,D就是输出结果,A是低位,D为高位,DCBA组成128位输出结果。
三、利用JAVA实现MD5加密
在说MD5加密的实现之前,我们需要先学习一下MessageDigest类:
1.概述
MessageDigest 类是一个引擎类,它是为了提供诸如 SHA1 或 MD5 等密码上安全的报文摘要功能而设计的。密码上安全的报文摘要可接受任意大小的输入(一个字节数组),并产生固定大小的输出,该输出称为一个摘要或散列。摘要具有以下属性:
- 无法通过计算找到两个散列成相同值的报文。
- 摘要不反映任何与输入有关的内容。
使用报文摘要可以生成数据唯一且可靠的标识符。有时它们被称为数据的“数字指纹”。
2.使用方法
(1).计算摘要的第一步是创建报文摘要实例。
像所有的引擎类一样,获取某类报文摘要算法的 MessageDigest 对象的途径是调用 MessageDigest 类中的 getInstance 静态 factory 方法:
MessageDigest.getInstance("MD5")
(2)更新报文摘要对象
计算数据的摘要的第二步是向已初始化的报文摘要对象提供数据。这将通过一次或多次调用以下某个 update(更新)方法来完成:
public void update(byte[] input, int offset, int len);(3)计算摘要
通过调用 update 方法提供数据后,程序就调用以下某个digest(摘要)方法来计算摘要:
public byte[] digest() public byte[] digest(byte[] input) public int digest(byte[] buf, int offset, int len) 前两个方法返回计算出的摘要。后一个方法把计算出的摘要储存在所提供的buf 缓冲区中,起点是 offset。len 是 buf中分配给该摘要的字节数。该方法返回实际存储在 buf中的字节数。
对接受输入字节数组变量的 digest方法的调用等价于用指定的输入调用:
public void update(byte[] input) ,接着调用不带参数的 digest 方法.
import com.sinosoft.ebusiness.nsp.service.util.InitAttributeInfos; import java.security.MessageDigest; /** * Created by Tanyunlong on 2016/10/19. */ public class MD5Test { public static void main(String args[]){ String result=getMD5("aaaabc!"); System.err.println(result); System.out.println(result); } /** * 生成MD5 * @return */ public static String getMD5(String message){ String md5Result=""; try{ //1.创建一个提供信息摘要算法的对象,初始化为MD5算法对象 MessageDigest md=MessageDigest.getInstance("MD5"); //2.将消息变为byte数组 byte[] input=message.getBytes(); //3.计算后获得字节数组,128位长度的MD5加密 byte[] buff=md.digest(input); //4.把数组每一个字节(一个字节占8位)换成16进制的md5字符串 md5Result=bytesHex(buff); }catch (Exception e){ e.printStackTrace(); } return md5Result; } public static String bytesHex(byte[]bytes){ StringBuffer md5Result =new StringBuffer(); //把数组每一字节换成换成16进制连成md5字符串 int digital; for (int i=0;i<bytes.length;i++){ digital=bytes[i]; if (digital<0){ digital+=256; } if (digital<16){ md5Result.append("0"); } md5Result.append(Integer.toHexString(digital)); } return md5Result.toString().toUpperCase(); } }
import java.security.MessageDigest; public class MD5Test { private final static String[] hexDigits = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"}; /** * 转换字节数组为16进制字串 * @param b 字节数组 * @return 16进制字串 */ public static String byteArrayToHexString(byte[] b) { StringBuilder resultSb = new StringBuilder(); for (byte aB : b) { resultSb.append(byteToHexString(aB)); } return resultSb.toString(); } /** * 转换byte到16进制 * @param b 要转换的byte * @return 16进制格式 */ private static String byteToHexString(byte b) { int n = b; if (n < 0) { n = 256 + n; } int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } /** * MD5编码 * @param origin 原始字符串 * @return 经过MD5加密之后的结果 */ public static String MD5Encode(String origin) { String resultString = null; try { resultString = origin; //创建信息摘要 MessageDigest md = MessageDigest.getInstance("MD5"); //用明文字符串计算消息摘要 md.update(resultString.getBytes("UTF-8")); //读取消息摘要,并转换为16进制字符串 resultString = byteArrayToHexString(md.digest()); } catch (Exception e) { e.printStackTrace(); } return resultString; } }