消息摘要算法

消息摘要算法是一种单向的编码算法,算法通过Hash函数,可将任意数据映射到一个固定长度的消息摘要散列值上。由于结果空间足够大,在可预见的数据范围内,不同的数据可看作对应唯一的散列值。

消息摘要算法的特点是:(1)可预见的数据范围内不同的数据对应散列值结果唯一(2)结果不可逆。基于这两个特点,一个对消息摘要比较形象的描述的数据指纹。

常用的消息摘要算法有:CRC系列、MD系列、SHA系列。

消息摘要算法的用途

消息摘要算法的用途非常广泛,很多基础功能都有涉及,例如:

  1. 数据校验,防篡改
  2. 与非对称加密结合使用,实现数字签名,防抵赖
  3. 不存储明文实现密码验证

常见消息摘要算法简介

下面我们简单介绍一些常见的消息摘要算法。

MD5

MD5(Message-Digest Algorithm 5)消息摘要算法,由美国密码学家罗纳德·李维斯特设计。MD5算法能够将数据映射到128bit散列值,在实际使用中,也经常用128bit结果的十六进制字符串文本存储。

MD5由MD4发展而来,算法诞生于1991年,其后得到广泛应用。2004年,来自中国的王小云教授公布了快速构造MD5碰撞的方法。

Java下BouncyCastle库实现MD5计算:

byte[] data = "Hello, world!".getBytes();

Digest digest = new MD5Digest();
digest.update(data, 0, data.length);
byte[] resultBytes = new byte[digest.getDigestSize()];
digest.doFinal(resultBytes, 0);
String resultHex = Hex.toHexString(resultBytes);

尽管MD5已经不能说是具有理论安全性的算法了,但目前应用还是非常多的。对于历史遗留代码,我们需要根据使用场景仔细判断此处使用MD5是否合适,新项目建议使用SHA-256等代替MD5。

SHA-2

SHA-2(Secure Hash Algorithm 2)由美国国家安全局研发,由美国国家标准与技术研究院(NIST)在2001年发布,SHA-2属于SHA系列算法,是已被证明为不安全的SHA-1后继者。其下又分为几种不同的算法标准,包括:SHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256。

Java下BouncyCastle库实现SHA-256计算:

byte[] data = "Hello, world!".getBytes();

Digest digest = new SHA256Digest();
digest.update(data, 0, data.length);
byte[] resultBytes = new byte[digest.getDigestSize()];
digest.doFinal(resultBytes, 0);
String resultHex = Hex.toHexString(resultBytes);

SHA-2系列是目前比较安全可靠的消息摘要算法,截止目前为止未发现碰撞数据。除此之外,SHA-256目前广泛用于HTTPS证书签名、比特币区块产生等。

CRC-32

CRC-32(Cyclic Redundancy Check 32)全称为循环冗余校验算法,CRC系列是一类比较古老的算法,它和MD/SHA系列用途也不同。CRC系列的特点是算法简单、性能高,很容易用简单的硬件实现,其主要用于硬件、通信领域的数据校验和检错。

HMAC

HMAC(Hash-based Message Authentication Code)指基于散列运算的消息验证码,它可以和SHA系列等消息摘要算法结合使用,基于原始信息和一个额外的密钥来生成散列值,实现密码验证。

我们事先分发一个密钥给用户,用户和我们通信的消息附上一个用该密钥生成的HMAC,而攻击者无法生成正确的HMAC,达到防篡改的目的。

Java下BouncyCastle库实现HMAC SHA-256计算:

String secret = "a8@!px";
byte[] data = ("Hello, world!").getBytes();

Mac sha256HMac = new HMac(new SHA256Digest());
sha256HMac.init(new KeyParameter(secret.getBytes()));
byte[] resultBytes = new byte[sha256HMac.getMacSize()];
sha256HMac.update(data, 0, data.length);
sha256HMac.doFinal(resultBytes, 0);
String resultHex = Hex.toHexString(resultBytes);

针对消息摘要算法的常见威胁

构造散列碰撞

虽然消息摘要算法是不可逆的,但在已知散列值的条件下,如果能够构造出和其原始数据有相同散列值的不同数据,那么就会对数字签名系统等产生严重威胁。因为此时数据可以被篡改、抵赖。

如MD5、SHA-1系列已被证明为存在构造碰撞的方法,因此不应用于HMAC、数据校验和防抵赖场景。

构建彩虹表

构建彩虹表主要针对使用消息摘要算法进行密码验证的场景。

假如用户的密码为abc123,为了进行密码验证,我们数据库中存储hash(abc123),这样即使数据库中内容被窃取泄漏,攻击者理论上也得不到原始密码。然而攻击者可能根据密码字典、常见位数的密码区间构建了彩虹表,如果用户使用了弱密码,还是会泄漏一部分原始账号密码信息。

关于抗彩虹表,常见的错误做法是直接使用HMAC或如下实现哈希加“盐”:

// !!! 错误示范 !!!
String salt = "a8@!px";
byte[] data = ("Hello, world!" + salt).getBytes();

Digest digest = new SHA256Digest();
digest.update(data, 0, data.length);
byte[] resultBytes = new byte[digest.getDigestSize()];
digest.doFinal(resultBytes, 0);
String resultHex = Hex.toHexString(resultBytes);

“盐”通俗来说就是一个随机值,攻击者构建的彩虹表没有加盐运算,他重新构建彩虹表的成本又较高,因此给攻击者增加了成本。

然而,实际上加“盐”有效的前提是盐值未泄漏,但这是不靠谱的,盐值(包括固定盐和随机盐)泄漏的风险和数据库泄漏的风险同样高。以现在的算力条件,重建加盐的彩虹表只在一瞬间。使用HMAC也同理。

那么怎样才是抗彩虹表的正确做法呢?实际上有两种方案:

  1. 增大重建彩虹表的计算量,如加盐并迭代哈希1000次以上
  2. 消耗更多内存,降低重建彩虹表的并行程度,如使用超大盐值

注:彩虹表攻击和碰撞攻击是完全不同的两种攻击方式,迭代哈希、加盐等操作和算法的抗碰撞能力无关!

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap