加解密系列之Hash算法
本篇文章主要介绍hash加密算法相关的实现及应用场景
一、算法简介
哈希算法(Hash Algorithm)是一种将任意长度的数据转换为固定长度输出的算法。这个输出通常是一个二进制字符串,称为哈希值、散列值或消息摘要。哈希算法具有以下主要特性:
1、确定性:相同的输入总是产生相同的哈希值。
2、快速计算: 对于任何给定的输入,哈希值可以快速计算出来。
3、不可逆性: 从哈希值反推原始输入数据在计算上是不可行的,即很难通过哈希值找到原始的输入数据。
4、雪崩效应: 即使输入数据有微小的变化,产生的哈希值也会有很大的不同。
5、唯一性: 理想情况下,不同的输入应该产生不同的哈希值,但实际上由于哈希值长度有限,存在发生碰撞(两个不同的输入产生相同哈希值)的可能性,但优秀的哈希算法会使这种可能性极低。
常见的哈希算法包括:
MD5: Message Digest 5,产生128位(16字节)的哈希值。尽管MD5曾经非常流行,但由于安全性的原因,现在不再推荐用于安全性要求高的场景(现在密码存储及验证一般选用BCrypt或Argon2这种密码散列算法来防止暴力破解或彩虹表攻击)
SHA系列:
SHA-1: Secure Hash Algorithm 1,产生160位(20字节)的哈希值。虽然比MD5更安全,但现在也因为已知的碰撞攻击而不再建议使用。
SHA-256/SHA-512: 属于SHA-2家族,分别产生256位和512位的哈希值。这些算法目前被认为是安全的,并被广泛使用。
SHA-3: 是NIST组织的一次竞赛中胜出的算法,与SHA-2不兼容,提供额外的安全保障。
哈希算法在密码学中有多种用途,如数据完整性检查、数字签名、安全存储密码等。在区块链技术中,哈希算法也是构建区块的基础,确保了链上的数据不可篡改。
二、算法规则
哈希算法的算法规则依赖于具体实现的算法类型,但大多数哈希函数遵循一些共同的原则和结构。下面是一些通用的算法规则,以SHA-256(一种安全散列算法)为例进行说明,尽管这些原则也适用于其他哈希算法,如MD5、SHA-1、SHA-3等。
- 初始化
算法开始时,会初始化一组寄存器(通常是一系列的固定值),这些寄存器将参与后续的运算。 - 预处理
输入数据会被预处理,通常包括:
(1)填充:如果输入数据的长度不是算法所要求的块大小的倍数,会在数据末尾添加一些比特,通常是1比特的’1’后跟足够多的’0’比特,直到整个消息的长度加上原始长度编码后的长度达到某个特定的模数。
(2)附加原始长度:在填充后的数据末尾添加原始消息的长度,通常是64位或128位,以便算法能够知道原始输入的大小。 - 分块
预处理后的数据被分成一系列固定大小的块,每个块的大小取决于具体的算法(例如,SHA-256的块大小是512位)。 - 哈希处理
对每个块执行迭代的处理,这通常包括:
(1)扩展:将输入块扩展成一系列词,这一步可能涉及循环左移、异或等操作。
(2)压缩函数:使用压缩函数处理每个词,这通常涉及复杂的布尔逻辑运算、加法、循环左移和常量加法。压缩函数将一个固定大小的输入转换为另一个固定大小的输出,这个输出将与寄存器的当前值结合,更新寄存器状态。
(3)更新寄存器:在每个块处理完后,寄存器的值会被更新,这些值将成为下一块处理的初始值。 - 输出
当所有块都处理完毕后,寄存器中的值就是最终的哈希值。这个值被串接在一起形成最终的哈希输出。
特殊算法规则
(1)非线性组合:为了增加算法的复杂度和抵抗攻击,哈希函数通常包含非线性组合,比如使用S盒(S-boxes)或复杂的布尔函数。
(2)扩散:通过循环左移、异或等操作,确保输入中的每一个比特位都能影响输出中的每一个比特位,这就是所谓的“雪崩效应”。
(3)混淆:使用常量和随机数生成器来混淆中间状态,使得攻击者难以预测算法的行为。
上述步骤是对哈希算法算法规则的简化描述,实际的算法实现会更为复杂,涉及许多数学和密码学原理。
三、算法实现
以下是Hash算法的Java实现:(因为用JDK8封装SHA-3有点麻烦,所以暂时使用外部库BouncyCastleProvider):
package com.cenho.demo.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Arrays;
import java.util.Optional;
public class HashEncryption {
// BC_PROVIDER
private static final BouncyCastleProvider BC_PROVIDER = new BouncyCastleProvider();
static {
Security.addProvider(BC_PROVIDER);
}
/**
* 对明文进行摘要计算
* @param algorithm
* @param plainText
* @return
* @throws NoSuchAlgorithmException
*/
public static byte[] digest(String algorithm,String plainText) throws NoSuchAlgorithmException{
MessageDigest md= MessageDigest.getInstance(algorithm, BC_PROVIDER);
return md.digest(plainText.getBytes(StandardCharsets.UTF_8));
}
/**
* 使用私钥对数据进行数字签名
* @param algorithm
* @param privateKey
* @param data
* @return
* @throws Exception
*/
public static byte[] signData(String algorithm,PrivateKey privateKey, String data) throws Exception {
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.sign();
}
/**
* 校验数字签名
* @param algorithm
* @param publicKey
* @param data
* @param signatureBytes
* @return
* @throws Exception
*/
public static boolean verifySignature(String algorithm,PublicKey publicKey, String data, byte[] signatureBytes) throws Exception {
Signature signature = Signature.getInstance(algorithm);
signature.initVerify(publicKey);
signature.update(data.getBytes(StandardCharsets.UTF_8));
return signature.verify(signatureBytes);
}
/**
* 创建HMAC
* @param algorithm
* @param key
* @param data
* @return
* @throws Exception
*/
public static byte[] createHmac(String algorithm,String key, String data) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
Mac mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
return mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
}
/**
* 校验HMAC
* @param algorithm
* @param key
* @param data
* @param hmacBytes
* @return
* @throws Exception
*/
public static boolean verifyHmac(String algorithm,String key, String data, byte[] hmacBytes) throws Exception {
byte[] calculatedHmac = createHmac(algorithm,key, data);
return Arrays.equals(calculatedHmac, hmacBytes);
}
/**
* 将字节数组转换为16进制字符串
* @param bytes
* @return
*/
public static String toHex(byte[] bytes){
return Optional.ofNullable(bytes)
.map(b->{
StringBuilder sb=new StringBuilder();
for(byte bt:b){
sb.append(String.format("%02x", bt));
}
return sb.toString();
})
.orElse("");
}
}
基于封装好的加解密,我们可以进行如下测试::
@Test
public void testHash() throws Exception{
byte[] md5 =HashEncryption.digest("MD5", "123456");
System.out.println("123456字符串 MD5 Hash加密结果:"+HashEncryption.toHex(md5));
byte[] sha1 =HashEncryption.digest("SHA-1", "123456");
System.out.println("123456字符串 SHA-1 Hash加密结果:"+HashEncryption.toHex(sha1));
byte[] sha256 =HashEncryption.digest("SHA-256", "123456");
System.out.println("123456字符串 SHA-256 Hash加密结果:"+HashEncryption.toHex(sha256));
byte[] sha512 =HashEncryption.digest("SHA-512", "123456");
System.out.println("123456字符串 SHA-512 Hash加密结果:"+HashEncryption.toHex(sha512));
byte[] sha3 =HashEncryption.digest("SHA3-256", "123456");
System.out.println("123456字符串 SHA3-256 Hash加密结果:"+HashEncryption.toHex(sha3));
}
@Test
public void testSignature() throws Exception{
String algorithm = "SHA256withRSA";
// 原始数据
String plaintext = "123456";
try {
// 生成RSA密钥对
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 签名数据
byte[] signature = HashEncryption.signData(algorithm,privateKey, plaintext);
// 验证签名
boolean isValid = HashEncryption.verifySignature(algorithm,publicKey, plaintext, signature);
System.out.println("签名:" + (isValid?"有效":"无效"));
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void testHmac() throws Exception{
String algorithm = "HmacSHA256";
String key = "abc123@cenho!";
String plaintext = "123456";
try {
byte[] hmacBytes = HashEncryption.createHmac(algorithm, key, plaintext);
boolean isVerified = HashEncryption.verifyHmac(algorithm, key, plaintext, hmacBytes);
if (isVerified) {
System.out.println("认证成功.");
} else {
System.out.println("认证失败.");
}
} catch (Exception e) {
e.printStackTrace();
}
}
上面测试结果可以看下图效果:
四、算法应用
以下是哈希算法在不同场景下的综合应用总结,包括数字签名和HMAC:
- 数据完整性与验证:
文件校验:在文件传输过程中,通过对比文件的哈希值确保数据在传输过程中的完整性和未被篡改。
数据库校验:维护数据库的健康状态,通过计算数据页的哈希值检测潜在的硬件或软件错误。 - 安全认证与授权:
数字签名:结合公钥加密技术,用于验证文档、代码、电子邮件等的完整性和来源,确保信息的真实性和未被篡改。
HMAC(基于散列的消息认证码):在共享密钥的双方之间验证消息的完整性和来源,适用于需要高度安全的通信协议和API认证。 - 密码安全:
密码存储:以哈希形式存储用户密码,增强安全性,即使数据库泄露,也难以直接获取原始密码。 - 高效数据结构:
散列表:利用哈希函数快速定位和检索数据,是许多数据库和编程语言内部实现的基础。 - 数据去重与优化:
数据去重:在大数据处理中,通过哈希值识别重复数据,节省存储空间和处理资源。
内容分发网络(CDN):利用哈希值标识静态资源,实现高效缓存和快速分发。 - 法律与合规:
安全审计与取证:在法律合规性和企业审计中,使用哈希值验证关键系统配置和日志文件的完整性。 - 新兴技术:
区块链技术:利用哈希链保持交易记录的不可篡改性,确保区块链的安全性和透明度。 - 软件工程与版本控制:
软件开发:版本控制系统如Git,使用哈希值唯一标识提交,确保版本历史的完整性和可追溯性。 - 内容保护与版权管理:
数字版权管理(DRM):通过哈希值保护数字媒体,确保内容的合法访问和分发。 - 网络安全防御:
反垃圾邮件和反病毒软件:基于已知恶意软件和垃圾邮件的哈希值,快速识别和阻止潜在威胁。
综上所述,哈希算法在确保数据安全、促进高效数据处理、维护系统完整性和支持新兴技术方面发挥着核心作用。无论是保护敏感信息、优化网络性能,还是推动技术创新,哈希算法都是不可或缺的基石。如果想要详细了解具体场景,可以点击查看hash算法原理及应用漫谈
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)