消息摘要(Message Digest)又称为数字摘要(Digital Digest)。它是一个唯一对应一个消息或文本的固定长度的值,它由一个单向Hash加密函数对消息进行作用而产生。如果消息在途中改变了,则接收者通过对收到消息的新产生的摘要与原摘要比较,就可知道消息是否被改变了。因此消息摘要保证了消息的完整性。
消息摘要采用单向Hash函数将需加密的明文"摘要"成一串固定位数(如128bit)的密文,这一串密文亦称为数字指纹(Finger Print),它有固定的长度,且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。这样这串摘要便可成为验证明文是否是“真身”的“指纹”了。
消息摘要具有不可逆性,在消息摘要生成过程中,会丢失很多原文的信息,而且无法找回。一个好的摘要算法,是极难产生Hash碰撞的,也就是找到另一段明文经计算后产生相同的摘要。
相关文章:
有以下几种:
MD5 (Message Digest algorithm 5 消息摘要算法版本5)
MD是应用非常广泛的一个算法家族,尤其是 MD5(Message-Digest Algorithm 5,消息摘要算法版本5),它由MD2、MD3、MD4发展而来,由Ron Rivest(RSA公司)在1992年提出,目前被广泛应用于数据完整性校验、数据(消息)摘要、数据加密等。MD2、MD4、MD5 都产生16字节(128位)的校验值,一般用32位十六进制数表示。MD2的算法较慢但相对安全,MD4速度很快,但安全性下降,MD5比MD4更安全、速度更快。
目前在互联网上进行大文件传输时,都要得用MD5算法产生一个与文件匹配的、存储MD5值的文本文件(后缀名为 .md5或.md5sum),这样接收者在接收到文件后,就可以利用与 SFV 类似的方法来检查文件完整性,目前绝大多数大型软件公司或开源组织都是以这种方式来校验数据完整性,而且部分操作系统也使用此算法来对用户密码进行加密,另外,它也是目前计算机犯罪中数据取证的最常用算法。与MD5 相关的工具有很多,如 WinMD5等。
案例实现:
/**
* MD5 测试
*
* 参考:https://www.jianshu.com/p/be000cf837b2
*/
class Md5Test {
public static void main(String[] args) throws NoSuchAlgorithmException {
/* 得注意加密后字符串的大小写 */
// 待加密字符
String originalStr = "111111";
System.out.println(String.format("待加密字符: %s", originalStr));
// 已加密字符
String alreadyDigestStr = "96E79218965EB72C92A549DD5A330112";
System.out.println(String.format("已加密字符: %s", alreadyDigestStr));
/* jdk 实现 */
System.out.println("--------------jdk 实现-----------------");
// 获取信息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 完成摘要
byte[] digest = md5.digest(originalStr.getBytes());
// 将摘要转换成 16 进制字符 (大写)
String javaMd5Str = DatatypeConverter.printHexBinary(digest);
System.out.println(String.format("%s 加密结果:%s", originalStr, javaMd5Str));
// 匹配验证
System.out.println(String.format("验证结果:%b", Objects.equals(javaMd5Str, alreadyDigestStr)));
/* Apache commons-codec 实现 */
System.out.println("---------------Apache commons-codec 实现------------");
// 小写
String apacheMd5Str = DigestUtils.md5Hex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, apacheMd5Str));
System.out.println(String.format("验证结果:%b", Objects.equals(apacheMd5Str, alreadyDigestStr)));
/* spring提供 */
System.out.println("-----------spring提供-----------");
// 小写
String springMd5Str = org.springframework.util.DigestUtils.md5DigestAsHex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, springMd5Str));
System.out.println(String.format("验证结果:%b", Objects.equals(springMd5Str, alreadyDigestStr)));
}
}
运行效果如下:
MD算法可用于密码保护。在用户注册时,利用MD算法取密码的摘要值,存入数据库。在用户登录时,将用户密码再次消息摘要后,与数据库密码进行比较即可得出用户登录结果。流程图如下:
MD5加密,是属于不可逆的。我们知道正常使用MD5加密技术,同一字符,加密后的16进制数是不变的,自从出现彩虹表,对于公司内部员工来说,可以反查数据,获取不可能的权限,所以出现了salt算法。
md5加盐算法,代码如下:
package com.blog.www.util.coder;
import org.apache.commons.codec.digest.DigestUtils;
import java.util.Objects;
import java.util.Random;
/**
* MD5 加随机盐加密
*
* 参考: MD5加密+加盐
*
* 基于:{@link DigestUtils}
*
* 创建人:leigq
* 创建时间:2018-12-04 15:42
*
* 修改人:
* 修改时间:
* 修改备注:
*
*/
public class MD5WithSalt {
/**
* 生成含有随机盐的加密字符串
*
创建人: leigq
*
创建时间: 2018-12-04 15:35
*
*
* @param data 待加密的字符
* @return 加密后的字符(含 16 位随机盐),大写
*/
public static String encrypt(String data) {
Random r = new Random();
StringBuilder sb = new StringBuilder(16);
sb.append(r.nextInt(99999999))
.append(r.nextInt(99999999));
int len = sb.length();
if (len < 16) {
for (int i = 0; i < 16 - len; i++) {
sb.append("0");
}
}
String salt = sb.toString();
String md5WithSaltStr = DigestUtils.md5Hex(data + salt);
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = md5WithSaltStr.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = md5WithSaltStr.charAt(i / 3 * 2 + 1);
}
return new String(cs).toUpperCase();
}
/**
* 校验密码是否正确
*
创建人: leigq
*
创建时间: 2018-12-04 15:38
*
*
* @param data 待验证的字符(明文)
* @param md5StrContainRandomSalt 加密后的字符(含 16 位随机盐)
* @return 验证结果
*/
public static boolean verify(String data, String md5StrContainRandomSalt) {
// 32 位加密字符(不含盐)
char[] cs1 = new char[32];
// 16 位的随机盐
char[] cs2 = new char[16];
for (int i = 0; i < 48; i += 3) {
cs1[i / 3 * 2] = md5StrContainRandomSalt.charAt(i);
cs1[i / 3 * 2 + 1] = md5StrContainRandomSalt.charAt(i + 2);
cs2[i / 3] = md5StrContainRandomSalt.charAt(i + 1);
}
String salt = new String(cs2);
return Objects.equals(DigestUtils.md5Hex(data + salt).toUpperCase(), new String(cs1));
}
}
测试:
/**
* md5 加随机盐测试
*/
class MD5WithSaltTest {
public static void main(String[] args) {
// 待加密字符
String originalStr = "111111";
// 已加盐加密字符
String md5WithSaltStr = MD5WithSalt.encrypt(originalStr);
System.out.println(String.format("%s 加密结果:%s", originalStr, md5WithSaltStr));
// 不能直接再次加密验证,因为使用了随机盐,每次加密出来的字符串都不同
boolean verifyResult = MD5WithSalt.verify(originalStr, md5WithSaltStr);
System.out.println(String.format("验证结果:%b", verifyResult));
}
}
测试结果如下:
SHA (Secure Hash Algorithm 安全散列算法)
SHA(Secure Hash Algorithm)是由美国专门制定密码算法的标准机构——美国国家标准技术研究院(NIST)制定的,SHA系列算法的摘要长度分别为:SHA-1为20字节(160位)、SHA-224为32字节(224位)、SHA-256为32字节(256位)、SHA-384为48字节(384位)、SHA-512为64字节(512位),由于它产生的数据摘要的长度更长,因此更难以发生碰撞,因此较之MD5更为安全,它是未来数据摘要算法的发展方向。由于SHA系列算法的数据摘要长度较长,因此其运算速度与MD5相比,也相对较慢。
目前SHA1的应用较为广泛,主要应用于CA和数字证书中,另外在目前互联网中流行的BT软件中,也是使用SHA1来进行文件校验的。
代码实现:
/**
* SHA 加密算法测试
*/
class SHATest {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 待加密字符
String originalStr = "111111";
System.out.println(String.format("待加密字符: %s", originalStr));
// 已加密字符
String alreadyDigestStr = "3D4F2BF07DC1BE38B20CD6E46949A1071F9D0E3D";
System.out.println(String.format("已加密字符: %s", alreadyDigestStr));
/* jdk 实现 */
// 获取信息摘要对象 (SHA1),另外还有 SHA-224、SHA-256、SHA-384、SHA-512
MessageDigest md5 = MessageDigest.getInstance("SHA");
// 完成摘要
byte[] digest = md5.digest(originalStr.getBytes());
// 将摘要转换成 16 进制字符 (大写)
String javaShaStr = DatatypeConverter.printHexBinary(digest);
System.out.println(String.format("%s 加密结果:%s", originalStr, javaShaStr));
// 匹配验证
System.out.println(String.format("验证结果:%b", Objects.equals(javaShaStr, alreadyDigestStr)));
/* Apache commons-codec 实现 */
String apacheShaStr = DigestUtils.sha1Hex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, apacheShaStr));
System.out.println(String.format("验证结果:%b", Objects.equals(apacheShaStr, alreadyDigestStr)));
/* SHA256 */
System.out.println(DigestUtils.sha256Hex(originalStr.getBytes()));
/* SHA384 */
System.out.println(DigestUtils.sha384Hex(originalStr.getBytes()));
/* SHA512 */
System.out.println(DigestUtils.sha512Hex(originalStr.getBytes()));
}
}
运行结果如下:
消息鉴别:
MAC (Hash Message Authentication Code 散列消息鉴别码)
MAC算法 (Message Authentication Codes消息认证码算法) 含有密钥的散列函数算法,兼容了MD和SHA算法的特性,并在此基础上加上了密钥。因此MAC算法也经常被称作HMAC算法。消息的散列值由只有通信双方知道的密钥来控制。此时Hash值称作MAC。
经过MAC算法得到的摘要值也可以使用十六进制编码表示,其摘要值得长度与实现算法的摘要值长度相同。例如 HmacSHA算法得到的摘要长度就是SHA1算法得到的摘要长度,都是160位二进制数,换算成十六进制的编码为40位。
流程分析:
甲乙双方进行数据交换可以采取如下流程:
1.甲方向乙方公布摘要算法(就是指定要使用的摘要算法名)
2.甲乙双方按照约定构造密钥,双方拥有相同的密钥(一般是一方构造密钥后通知另外一方,此过程不需要通过程序实现,就是双方约定个字符串,但是这个字符串可不是随便设定的,也是通过相关算法获取的)
3.甲方使用密钥对消息做摘要处理,然后将消息和生成的摘要消息一同发送给乙方
4.乙方收到消息后,使用甲方已经公布的摘要算法+约定好的密钥 对收到的消息进行摘要处理。然后比对自己的摘要消息和甲方发过来的摘要消息。甄别消息是否是甲方发送过来的。
代码实现:
/**
* MAC 算法测试
*/
class MacTest {
public static void main(String[] args) throws NoSuchAlgorithmException, DecoderException, InvalidKeyException {
// 待加密字符
String originalStr = "111111";
System.out.println(String.format("待加密字符: %s", originalStr));
// 已加密字符
String alreadyDigestStr = "74B31DBA9E16CCF752B294A18271BC6A";
System.out.println(String.format("已加密字符: %s", alreadyDigestStr));
/* jdk 实现 */
// 初始化 KeyGenerator, jdk 提供 HmacMD5、HmacSHA1、HmacSHA256、HmacSHA384和 HmacSHA512 四种算法
// KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacMD5");
// 产生密钥
// SecretKey secretKey = keyGenerator.generateKey();
// 默认密钥
// byte[] defaultKey = secretKey.getEncoded();
// 自定义密钥
byte[] myKey = Hex.decodeHex(new char[]{'a','a','a','a','a','a','a','a','a','a'});
// 还原密钥
SecretKey restoreSecretKey = new SecretKeySpec(myKey, "HmacMD5");
// 实例化 MAC
Mac mac = Mac.getInstance(restoreSecretKey.getAlgorithm());
// 初始化 MAC
mac.init(restoreSecretKey);
//执行摘要
byte[] hmacMD5Bytes = mac.doFinal(originalStr.getBytes());
String encodeHexString = Hex.encodeHexString(hmacMD5Bytes).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, encodeHexString));
System.out.println(String.format("验证结果:%b", Objects.equals(encodeHexString, alreadyDigestStr)));
/* apache 实现 */
// HmacMD5
HmacUtils hmacMd5 = new HmacUtils(HmacAlgorithms.HMAC_MD5, myKey);
String apacheHmacMd5 = hmacMd5.hmacHex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, apacheHmacMd5));
System.out.println(String.format("验证结果:%b", Objects.equals(apacheHmacMd5, alreadyDigestStr)));
// HmacSHA1
HmacUtils hmacSha1 = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, KeyGenerator.getInstance(HmacAlgorithms.HMAC_SHA_1.getName()).generateKey().getEncoded());
String apacheHmacHex1 = hmacSha1.hmacHex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, apacheHmacHex1));
// HmacSHA256
HmacUtils hmacSha256 = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, KeyGenerator.getInstance(HmacAlgorithms.HMAC_SHA_256.getName()).generateKey().getEncoded());
String apacheHmacSha256 = hmacSha256.hmacHex(originalStr.getBytes()).toUpperCase();
System.out.println(String.format("%s 加密结果:%s", originalStr, apacheHmacSha256));
// HmacSHA384 , 类似上面
// HmacSHA512, 类似上面
}
}
运行结果:
上面这几种摘要算法,可谓是非可逆加密,就是不可解密的加密方法。我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠。