序言
消息摘要算法平常使用的频率很高,经常我们用它来验证数据是否被篡改。 还有验证网络传输文件时,文件是否被篡改等等。
消息摘要算法有哪些
消息摘要算法主要分为三类: MD 、 SHA 、 MAC
- MD(Message Digest) : 消息摘要
- SHA(Secure Hash Algorithm) : 安全散列
- MAC(Message Authentication Code) : 消息认证码
主要用途 : 验证消息的完整性
主要特性:
- 输入的消息不受限制,但是输出的消息(经过消息摘要算法后的消息)固定;
- 如果输入的消息不同,输出的消息也肯定不同;
- 消息摘要算法是不可逆的; 即不能从摘要后的消息解析出任何源信息相关的信息
MD(Message Digest) : 消息摘要
MD系列算法一般有: MD2 、 MD4 、 MD5, 数字的值越大代表算法的版本越高,安全就越强。 即 MD5 > MD4 > MD2 。 自然破解难度MD5最高,平常用的最多的也是MD5算法。
MD系列算法输出信息的摘要长度都是固定的128bit ,即16字节;
因为JDK不支持MD4算法, 我们需引用第三方的依赖。在 pom.xml下加入这段才能使用MD4
org.bouncycastle
bcprov-jdk15on
1.62
代码示例 :
/**
* @program
* @Desc MD消息摘要算法 测试
* @Author 游戏人日常
* @CreateTime 2019/07/09--15:14
*/
public class MDTest {
private static final String src = "helloWorld";
public static void main(String[] args) {
jdkMD5();
jdkMD2();
bcMD4();
jdkAndBcMd4();
}
//JDK 实现
public static void jdkMD5() {
try {
//得到消息摘要对象
MessageDigest md = MessageDigest.getInstance("MD5");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK 实现
public static void jdkMD2() {
try {
//得到消息摘要对象
MessageDigest md = MessageDigest.getInstance("MD2");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//结合JDK 来实现
public static void jdkAndBcMd4() {
try {
//加入BouncyCastle的支持 也可以不通过代码,需在Java/jdk1.8.0_181/jre/lib/security/java.security加入
Security.addProvider(new BouncyCastleProvider());
// 得到MD 消息摘要对象
MessageDigest md = MessageDigest.getInstance("MD4");
//对src 字符串进行消息摘要
byte[] mD4Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//通过 第三方 bouncy castle 来 实现
public static void bcMD4() {
//创建 MD4Digest 对象
Digest md4Digest = new MD4Digest();
//更新消息摘要 通过src字节数组
md4Digest.update(src.getBytes(), 0, src.getBytes().length);
//初始化字节数组大小
byte[] mD4Bytes = new byte[md4Digest.getDigestSize()];
//关闭摘要,然后得到摘要后的值
md4Digest.doFinal(mD4Bytes, 0);
//对得到的字节数组 转为16进制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
}
}
SHA(Secure Hash Algorithm) : 安全散列
SHA系列算法从版本上一般分为两类: SHA-1 和 SHA-2
SHA-1就是我们常见的SHA1 , SHA-2主要有SHA224 、 SHA256 、 SHA384 、 SHA512等等,不同的数字代表输出消息的位数, 与MD系列算法一样,数字高表示算法的安全性越高,但不同于MD系列算法的是,SHA系列算法输出的信息摘要长度不一样。
- SHA1 摘要算法后,输出的信息为160bit, 即20字节;
- SHA224 摘要算法后,输出的信息为224bit, 即28字节;
- SHA256 摘要算法后,输出的信息为256bit, 即32字节;
- SHA384 摘要算法后,输出的信息为384bit, 即48字节;
- SHA512 摘要算法后,输出的信息为512bit, 即64字节;
代码示例:
**
* @program
* @Desc SHA 测试
* @Author 游戏人日常
* @CreateTime 2019/07/09--17:44
*/
public class SHATest {
public static final String src="helloWorld";
public static void main(String [] args){
jdkSHA1();
bcSHA224();
jdkSHA256();
jdkSHA384();
jdkSHA512();
}
//JDK实现
public static void jdkSHA1(){
try {
//得到消息摘要对象
MessageDigest md=MessageDigest.getInstance("SHA");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 通过 第三方 bouncy castle 来 实现
public static void bcSHA224(){
//创建 MD4Digest 对象
Digest digest = new SHA224Digest();
//更新消息摘要 通过src字节数组
digest.update(src.getBytes(), 0, src.getBytes().length);
//初始化字节数组大小
byte[] mD4Bytes = new byte[digest.getDigestSize()];
//关闭摘要,然后返回摘要后的值
digest.doFinal(mD4Bytes, 0);
//对得到的字节数组 转为16进制
String result = new BigInteger(mD4Bytes).toString(16);
System.out.println(result);
}
//JDK实现
public static void jdkSHA256(){
try {
//得到消息摘要对象
MessageDigest md=MessageDigest.getInstance("SHA-256");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK实现
public static void jdkSHA384(){
try {
//得到消息摘要对象
MessageDigest md=MessageDigest.getInstance("SHA-384");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//JDK实现
public static void jdkSHA512(){
try {
//得到消息摘要对象
MessageDigest md=MessageDigest.getInstance("SHA-512");
//对src 字符串进行消息摘要
byte[] md5Bytes = md.digest(src.getBytes());
//对得到的字节数组 转为16进制
String result = new BigInteger(md5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
MAC(Message Authentication Code) : 消息认证码
MAC算法不同于MD和SHA,因为在MD和SHA算法基础上添加了密钥。 所以安全性相比MD和SHA系列更高。
MD系列:HmacMD2 、HmacMD4 、HmacMD5
SHA系列: HmacSHA1 、 HmacSHA224 、HmacSHA256 、HmacSHA384 、HmacSHA512
摘要后的长度跟MD和SHA系列一样。 如HmacMD5摘要长度跟MD5一样, 即160bit,20字节;
代码示例:
/**
* @program
* @Desc MAC 测试
* @Author 游戏人日常
* @CreateTime 2019/07/09--19:44
*/
public class MACTest {
public static final String src="helloWorld";
public static void main(String [] args){
jdkHmacMD5();
HmacSHA1();
}
//JDK实现
public static void jdkHmacMD5(){
try {
//获取key生成器对象
KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacMD5");
//获取密钥对象
SecretKey secretKey=keyGenerator.generateKey();
//获取密钥
//byte [] key=secretKey.getEncoded();
//可以自己设置密钥的来源, 必须是16进制, 长度是2的倍数
byte [] key=Hex.decode("123456789abcde");
//还原密钥 第二个参数是算法名称
SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacMD5");
//获取Mac对象
Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
//初始化Mac
mac.init(restoreSecretKey);
//对src执行摘要
byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
//对摘要的信息字节数组 转为16进制
String result = new BigInteger(hmacMD5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
//JDK实现
public static void HmacSHA1(){
try {
//获取key生成器对象
KeyGenerator keyGenerator=KeyGenerator.getInstance("HmacSHA1");
//获取密钥对象
SecretKey secretKey=keyGenerator.generateKey();
//获取密钥
//byte [] key=secretKey.getEncoded();
//可以自己设置密钥的来源, 必须是16进制, 长度是2的倍数
byte [] key=Hex.decode("123456789abcde");
//还原密钥 第二个参数是算法名称
SecretKey restoreSecretKey= new SecretKeySpec(key,"HmacSHA1");
//获取Mac对象
Mac mac=Mac.getInstance(restoreSecretKey.getAlgorithm());
//初始化Mac
mac.init(restoreSecretKey);
//对src执行摘要
byte[] hmacMD5Bytes= mac.doFinal(src.getBytes());
//对摘要的信息字节数组 转为16进制
String result = new BigInteger(hmacMD5Bytes).toString(16);
System.out.println(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
//其他的摘要实现一样,大致都差不多,就不实现了。
密钥不同,得到的摘要消息肯定不同。 密钥的来源可以自己来构建,但必须满足是16进制, 长度是2的倍数。
应用场景
- 登陆注册时:用户注册时,我们平常会把关键的信息会进行摘要后,如:密码。 然后持久化在数据库中。 用户登陆时:我们就对密码进行摘要然后去数据库去查询。 一般采取的摘要算法是MD5。
注册登陆时序图如下:
上面只是个简单的用户注册和登陆流程,为什么要对密码进行摘要,不进行摘要的话,一旦如果数据库中的数据泄露了,如: 用户名和密码, 别人就可以拿这个用户名和密码去登陆。 摘要的话,别人即使获取到用户名和密码, 也是登陆不成功。 但是现在的MD5被国人破解了。
- 网络传输数据时: 有些安全性比较高的,会对HTTP/HTTPS请求中的参数进行摘要,然后把摘要信息一并传输。 请求中参数具体怎么摘要这个自己定。一般还会提供个key。
某个SDK的服务器文档:
我们可以看到这个是把secret+uid+psid+roleid+amount+type+htnonce 这些参数值进行摘要,生成的值和原来的各个参数来作为HTTP参数。 这个文档的参数是按照这个规则来进行摘要的,有的会用参数的key进行排序然后来进行摘要。 具体的规则还是客户端和服务器端进行商定。
-
在Android领域,我们会比较apk中md5(指的是对Android签名文件的摘要。 对apk解压后的original\META-INF\XXX.RSA文件中),来比较这个apk是否被二次打包过。 其他的文件也是一样,来判断是否被修改过
这个工具源码: https://github.com/songshuilin/parseApk
总结
上面列出三种摘要算法系列: MD系列 、 SHA系列 、 MAC系列。 但是我们还会见到CRC系列,在WinRAR 、WinZIP等软件中。
一般情况下安全性 MAC > SHA > MD > CRC
消息摘要特点:
- 变长输入、定长输出 : 即 不管输入的消息多长,计算出来的摘要的长度是固定的。
- 输入不同,摘要不同。 输入相同,摘要相同 ;
- 单向不可逆: 即 只能进行正向的摘要,而无法从摘要中恢复原来的任何信息。
- “碰撞” 难找到: 好的摘要算法,很难找到碰撞,虽然碰撞是肯定存在。 即无法找到两个不同的消息,但是他们的摘要相同。