数据摘要算法(信息摘要)是密码学算法中非常重要的一个分支,它通过对所有数据提取指纹信息以实现数据签名、数据完整性校验等功能,由于算法具有其不可逆性,有时候也会被用做敏感信息加密。
消息摘要算法的主要特征是加密过程不需要密钥,并且经过加密的数据无法被解密,可以被解密逆向的只有CRC32算法,只有输入相同的明文数据经过相同的消息摘要算法才能得到相同的密文。
消息摘要算法也叫散列函数、哈希函数,严格的说并不算是一种加密算法。消息摘要算法的主要作用不是完成数据的加密与解密工作的,它是用来验证数据完整性的重要技术。通过摘要函数,可以为数据创建“数字指纹”(散列值)。
需要指出的是MAC(Message Authentication Code)算法是支持密钥的一种摘要算法。
2 特点碰撞
)在计算机领域,我们希望能知道别人传递的消息是否完整、是否有被篡改,这里就用到信息摘要算法。
例如我们要在数据库中保存用户密码,首先肯定不能保存明文,既然要使用密文保存,那使用对称加密还是非对称加密呢?结合实际场景和安全性考虑,两种都不合适!因为这两种都要考虑密钥的管理,如果密钥泄漏那密码则有被破解的风险。在这种场景下,我们可以使用单向散列函数来解决这个问题,这样即使算法和密文都泄漏,也无法逆向计算出明文。
当然散列函数还有更多用途,比如文件一致性校验、数字签名等。
4 常用的非对称加密算法信息摘要算法来源于CRC算法,最初CRC算法是用来验证数据完整性的,即我们常见的奇偶校验码、循环冗余校验,在CRC基础上发展出了MD和SHA两大算法家族,CRC比这些算法都要早,MD算法比SHA算法早,SHA算法是对MD算法的改进。再后来则发展出了可以带有密码的信息摘要算法-MAC算法。
信息摘要算法包括三大类,MD、SHA和MAC算法,MD的分类是按照版本规定的,SHA一般是按照产生的消息长度分类的,但是SHA系列算法在学术上会按照算法版本区分SHA-0、SHA-1、SHA-2、SHA-3,
MAC算法是Hmac加融合的其他算来命名的。
MD5算法是典型的消息摘要算法,它由MD4、MD3、MD2算法改进而来。无论是哪种MD算法,它们都需要获得一个随机长度的信息并产生一个128位的二进制信息摘要,转换成16进制就是32位的字符串。我们见到的大部分MD5算法的数字指纹都是32位十六进制的字符串。
随着计算机发展水平的提高,MD5算法已不再适合安全要求较高的场合。
4.2 SHA(Secure Hash Algorithm)安全散列SHA算法基于MD4算法基础之上,其与MD算法不同之处在于摘要长度,SHA算法的摘要长度更长,安全性更高。
SHA-0算法,1983年第一个版本,因降低密码安全性撤回
SHA-1算法,1995年发布,通常我们也把SHA-1算法简称为SHA算法。其在安全协定中使用广泛。包括TLS/SSL,SSH等。SHA-0和SHA-1算法可对大长度为264的字节信息做摘要处理,得到160位的摘要信息,40为16进制的字符串
SHA-2算法,是SHA-224,SHA-256,SHA-384,SHA-512四种算法的总称。
MAC算法结合了MD5和SHA算法的优势,并加入了密钥的支持,是一种更为安全的消息摘要算法。我们也通常把MAC算法称为HMAC算法(keyed-Hash Message Authentication Code)。
MD系列算法与SHA系列算法分别是两种不同形式的信息摘要算法,都是没有密钥的散列函数算法。MAC兼容了MD与SHA这两种的特性,并且在这个基础上添加了密钥。通常又把MAC称为HMAC,即含有密钥的散列函数算法。
MAC算法则为MD与SHA系列算法的基础上添加了密钥,构成了新的算法:HmacMD2、HmacMD4、HmacMD5、HmacSHA1、HmacSHA224、HmacSHA356、HmacSHA384、HmacSHA512。
5 JDK支持的信息摘要算法JDK8原生算法列表,可参第一篇博文: https://blog.csdn.net/yunyun1886358/article/details/128592503#311_JDK_Provider_63
6 Bouncy Castle 支持的信息摘要算法Bouncy Castle算法列表,可参第一篇博文:
https://editor.csdn.net/md/?articleId=128592503#323_Bouncy_Castle_Provider_568
碰撞
和不可逆
是破解摘要算法的基础。
碰撞
就是先基于消息1用信息摘要算法算出一个摘要值,再根据这个值,逆算出另外一个不同的消息2,但是它们的摘要值是一样的。
如果我们能找到一个消息2,生成的摘要与原始消息1是一样的,那么无论消息1和2是否一样,我们都可以将消息2发送给校验方,并通过校验。因为校验方只持有摘要值,并且根据摘要值无法还原消息1,校验方也不知道消息1是什么,他只校验消息2通过算法生成的摘值和他持有的是一致,校验就通过了。
2004年,我国中科院院士王小云证实md5算法无法防止碰撞,因此,不适用于安全性认证。她的研究成果表明了给定消息 M1,能够计算获取 M2,使得 M2 产生的散列值与 M1 产生的散列值相同。
7.2 MD5的破解都说MD5已经被证明是一种不安全的算法,那么如何对MD5进行碰撞呢 ?
7.2.1 穷举法穷举法就是不停地尝试各种字符的排列组合,看哪一个组合的MD5码能对上。缺点是太耗费时间。举个例子,假设我们要破解一个6位大小写字母和数字混合的密码,那么一共有 (26 + 26 + 10) ^ 6 种组合。这个数的大小超过500亿。
7.2.2 字典法字典法就是把计算结果以映射表的形式存放起来,一个原文对应着一个MD5值。将已知的MD5码查表,就可直接反查出原文。字典法体现了算法设计的“以空间换时间”的思想。缺点是比较耗费空间,而且实际上还是要穷举一遍所有的输入,只不过把穷举的结果存了起来。
一个用字典法实现md5解密的网站:https://md5.cn/
如果说穷举法太耗费时间,字典法太耗费存储空间的话,我们能不能考虑在时间消耗和空间消耗之间折中呢?我们可以考虑用链表将一系列有意义的原文和 MD5 码串起来。
大概方法就是将一部分MD5摘要值和原文按照相近性分别保存到不同的哈希链表中,然后根据要破解的MD5摘要值计算出原始消息可能存在那条链表中,然后这条链表的头开始计算,然后得到碰撞值。
一个已经计算好的彩虹表: http://project-rainbowcrack.com/table.htm
7.2.4 差分攻击上面介绍的穷举法、字典法和彩虹表法都是暴力破解,适用于任何的消息摘要算法。真正意义上 MD5 算法的破解,是 2004 年山东大学王小云教授提出的 MD5 碰撞方法。她所用到的方法正是差分攻击。
这种方法概括起来说是这样的:给定一个 1024 位的原文 M1,加上一个特定的常数得到的新的明文 M2。M1 和 M2 的 MD5 码是一样的。
7.3 降低MD5的破解概率有一种方法可以降低MD5碰撞的概率,那就是在计算摘要的时候加盐
,也就是
MD5(原文 + 盐)
盐可以是任意字母、数字、或是字母或数字的组合,但必须是随机产生的,最好每次摘要加的盐都不一样。
例如网站用户注册,保存用户密码时,为每个用户生成一个随机的盐,然后将 MD5( 明文密码 + 盐)摘要值存入用户表,并且在用户表中添加一个字段,用来保存盐。
由于加了盐,即便数据库泄露了,但是由于密码都是加了盐之后的散列,数据字典已经无法直接匹配,明文密码被碰撞成功的概率也大大降低。
是不是加了盐之后就绝对安全了呢?当然没有。攻击者还是可以他们数据字典中的密码,加上我们泄露数据库中的 Salt,然后散列,然后再匹配。这要就需要重新生成数据字典,但是由于我们的盐是随机产生的,假如我们的用户数据表中有 100w 条数据,那么就有100w个盐。如数据字典中有 1000w 条数据,那么如果要生成新的数据字典,那么数字典的明文加上盐之后再散列的数据字典数据量就应该是 1,000,000* 10,000,000 = 10,000,000,000,000,共10兆条数据,成本非常之高。
8 算法调用示例下面的代码将JDK提供的几种信息摘要算法用枚枚举类进行了封装。
package com.qupeng.crypto.algorithm.oop;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class DigestAlgorithmTest {@Rule
public final ExpectedException exception = ExpectedException.none();
@Test
public void digest() throws NoSuchAlgorithmException {Assert.assertEquals("Ow7l+x4vPrlj/ZrwYVPHjw==", DigestAlgorithm.MD2.digest("12345678901234567890"));
Assert.assertEquals("/YXmLZvrRUKHcexohBiycQ==", DigestAlgorithm.MD5.digest("12345678901234567890"));
Assert.assertEquals("fgoSQr2O+QRPJ9ykX19yrVoRJb8=", DigestAlgorithm.SHA_1.digest("12345678901234567890"));
Assert.assertEquals("LGQud880sKr3FSrFxbJtG9c/24h0vLDZHHyRPw==", DigestAlgorithm.SHA_224.digest("12345678901234567890"));
Assert.assertEquals("t72NaA22sXKHBeXh1BlYyTryjk2KKJ4A3DckaRxbXTw+RBznZieVycTGAgbzNWkp", DigestAlgorithm.SHA_384.digest("12345678901234567890"));
Assert.assertEquals("btZF7w4avqG/Hk6TX/BPnhjTmBI4f2PNo0FbRiQPBAU=", DigestAlgorithm.SHA_256.digest("12345678901234567890"));
Assert.assertEquals("qjt73ZjsRK8fOVu9X38npc2VadeU0DJ0cyO/SxUh++dyWHWmi0QKvfBVneUBW6+HO7nAHK5j7OqTrVR6c5dBbg==", DigestAlgorithm.SHA_512.digest("12345678901234567890"));
}
@Test(expected=NoSuchAlgorithmException.class)
public void digestBySha512224() throws NoSuchAlgorithmException {DigestAlgorithm.SHA_512_224.digest("12345678901234567890");
}
@Test
public void digestBySha512256() throws NoSuchAlgorithmException {exception.expect(NoSuchAlgorithmException.class);
exception.expectMessage("SHA-512/256 MessageDigest not available");
DigestAlgorithm.SHA_512_256.digest("12345678901234567890");
}
@Test
public void digestMAC() throws InvalidKeyException, NoSuchAlgorithmException {String digestStr = DigestAlgorithm.HMAC_MD5.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_MD5.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
digestStr = DigestAlgorithm.HMAC_SHA1.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_SHA1.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
digestStr = DigestAlgorithm.HMAC_SHA224.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_SHA224.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
digestStr = DigestAlgorithm.HMAC_SHA256.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_SHA256.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
digestStr = DigestAlgorithm.HMAC_SHA384.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_SHA384.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
digestStr = DigestAlgorithm.HMAC_SHA512.digestMAC("12345678901234567890", 128);
Assert.assertEquals(DigestAlgorithm.HMAC_SHA512.getDigestMessageLength(), Base64.getDecoder().decode(digestStr).length * 8);
}
}
package com.qupeng.crypto.algorithm.oop;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public enum DigestAlgorithm {MD2("MD2", 128),
MD5("MD5", 128),
SHA_1("SHA-1", 160),
SHA_224("SHA-224", 224),
SHA_256("SHA-256", 256),
SHA_384("SHA-384", 384),
SHA_512("SHA-512", 512),
SHA_512_224("SHA-512/224", 512),
SHA_512_256("SHA-512/256", 512),
HMAC_MD5("HmacMD5", 128),
HMAC_SHA1("HmacSHA1", 160),
HMAC_SHA224("HmacSHA224", 224),
HMAC_SHA256("HmacSHA256", 256),
HMAC_SHA384("HmacSHA384", 384),
HMAC_SHA512("HmacSHA512", 512);
private String algorithm = "";
private int digestMessageLength = -1;
DigestAlgorithm(String algorithm, int digestMessagLength) {this.algorithm = algorithm;
this.digestMessageLength = digestMessagLength;
}
public String digest(String plainText) throws NoSuchAlgorithmException {MessageDigest messageDigest = MessageDigest.getInstance(this.algorithm);
byte [] bytes = messageDigest.digest(plainText.getBytes());
String digestStr = Base64.getEncoder().encodeToString(bytes);
System.out.println(String.format("%s(%d) plain text: %s ->digest text: %s", this.algorithm, this.digestMessageLength, plainText, digestStr));
return digestStr;
}
public String digestMAC(String plainText, int keyLength) throws NoSuchAlgorithmException, InvalidKeyException {KeyGenerator keyGenerator = KeyGenerator.getInstance(this.algorithm);
keyGenerator.init(keyLength);
SecretKey secretKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance(this.algorithm);
mac.init(secretKey);
byte[] hmacMD5Bytes = mac.doFinal(plainText.getBytes());
String digestStr = Base64.getEncoder().encodeToString(hmacMD5Bytes);
System.out.println(String.format("%s(%d) plain text: %s ->digest text: %s", this.algorithm, this.digestMessageLength, plainText, digestStr));
return digestStr;
}
public int getDigestMessageLength() {return digestMessageLength;
}
}
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧