目录
一、概述
消息摘要是通过特定的哈希函数将任意长度的消息转换为固定长度的字符串(通常称为“摘要”或“哈希值”)。消息摘要的主要用途是确保数据的完整性,即验证数据在传输或存储过程中是否被篡改。常见的哈希算法包括 MD5、SHA-1、SHA-256 等。
二、消息摘要的特性
- 确定性:对于给定的输入消息,消息摘要算法总是生成相同的输出摘要。
- 不可逆性:从摘要中无法反推出原始消息的内容。
- 抗碰撞性:找到两个不同的消息,它们具有相同的消息摘要是极其困难的(理想情况下是不可能的)。
- 高效性:计算消息摘要的速度应尽可能快。
- 雪崩效应:输入消息中的微小变化会导致输出摘要的巨大变化。
三、常用的消息摘要算法(哈希算法)
哈希函数(Hash Function) 是一种数学函数,它接收一个输入(或一组输入),并生成一个固定长度的字符串(哈希值)。哈希值通常是一个二进制数,表示为十六进制字符串。
- 输入:可以是任意长度的数据,如文本、文件、图像等。
- 输出:固定长度的字符串(哈希值),例如SHA-256生成的哈希值长度为256位(32字节)。
以下是一些常见的哈希算法:
1. MD5 (Message-Digest Algorithm 5)
- 特点:
- 输出长度为128位(16字节)。
- 设计初衷是为了替代MD4,并提供更高的安全性。
- 安全性问题:
- MD5已经被证明存在严重的安全漏洞,容易受到碰撞攻击和预图像攻击,因此不推荐用于安全性要求高的场景。
2. SHA-1 (Secure Hash Algorithm 1)
- 特点:
- 输出长度为160位(20字节)。
- 由美国国家安全局(NSA)设计,广泛应用于数字签名等领域。
- 安全性问题:
- 尽管比MD5更安全,但SHA-1也已被证明存在碰撞攻击的风险,不再推荐用于新的应用。
3. SHA-2 (Secure Hash Algorithm 2)
- 特点:
- 包括多个变体,如SHA-256、SHA-384、SHA-512等,分别输出256位、384位和512位的摘要。
- 目前被认为是安全的,广泛应用于各种需要高安全性的场合。
- 常见变体:
- SHA-256:输出256位摘要,适用于大多数应用场景。
- SHA-512:输出512位摘要,适用于更高安全需求的场景。
4. SHA-3 (Secure Hash Algorithm 3)
- 特点:
- 基于Keccak算法,旨在提供更高的安全性和灵活性。
- 输出长度可以灵活配置,支持224位、256位、384位和512位等多种长度。
- 应用场景:
- 主要用于对SHA-2系列算法的安全性补充,虽然SHA-2目前仍然被认为是安全的,但SHA-3提供了额外的选择。
四、消息摘要的应用场景及示例
消息摘要在多种领域有着广泛的应用,包括但不限于:
1. URL传参校验:确保URL参数未被篡改。
2. 文件完整性校验:确保文件在传输或存储过程中未被修改。
3. 密码存储与验证:安全地存储用户密码,并在登录时验证密码。
应用场景1:URL传参校验
需求:确保URL参数未被篡改。
实现步骤:
- 服务器端生成签名:使用SHA-256算法生成签名,并将其附加到URL中。
- 客户端请求URL:携带签名进行请求。
- 服务器端验证签名:接收请求后重新计算签名并与客户端传递的签名进行比较。
C#版本
服务器端生成签名示例:
using System;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string userId = "12345";
string timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
// 将参数拼接成字符串
string message = $"userId={userId}×tamp={timestamp}";
// 使用 SHA-256 计算消息摘要
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(message));
string signature = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
Console.WriteLine($"Original Message: {message}");
Console.WriteLine($"Signature: {signature}");
}
}
}
客户端请求URL:
https://example.com/api?userId=12345×tamp=1679054760&signature=your_generated_signature_here
服务器端验证签名示例:
using System;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
// 假设从客户端接收到的参数
string userId = "12345";
string timestamp = "1679054760";
string receivedSignature = "your_received_signature_here"; // 客户端传递的签名
// 重新生成消息摘要
string message = $"userId={userId}×tamp={timestamp}";
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(message));
string generatedSignature = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
if (generatedSignature == receivedSignature)
{
Console.WriteLine("签名验证成功!");
}
else
{
Console.WriteLine("签名验证失败!");
}
}
}
}
Java版本
服务器端生成签名示例:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
public class UrlParamSignature {
public static void main(String[] args) throws NoSuchAlgorithmException {
String userId = "12345";
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
// 将参数拼接成字符串
String message = "userId=" + userId + "×tamp=" + timestamp;
// 使用 SHA-256 计算消息摘要
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = sha256.digest(message.getBytes(StandardCharsets.UTF_8));
String signature = bytesToHex(hashBytes);
System.out.println("Original Message: " + message);
System.out.println("Signature: " + signature);
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}
客户端请求URL:
https://example.com/api?userId=12345×tamp=1679054760&signature=your_generated_signature_here
服务器端验证签名示例:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.charset.StandardCharsets;
public class UrlParamSignatureVerification {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 假设从客户端接收到的参数
String userId = "12345";
String timestamp = "1679054760";
String receivedSignature = "your_received_signature_here"; // 客户端传递的签名
// 重新生成消息摘要
String message = "userId=" + userId + "×tamp=" + timestamp;
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = sha256.digest(message.getBytes(StandardCharsets.UTF_8));
String generatedSignature = bytesToHex(hashBytes);
if (generatedSignature.equals(receivedSignature)) {
System.out.println("签名验证成功!");
} else {
System.out.println("签名验证失败!");
}
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}
应用场景2:文件完整性校验
需求:确保文件在传输或存储过程中未被篡改。
实现步骤:
- 生成文件的哈希值:使用SHA-256算法计算文件的哈希值。
- 验证文件完整性:在接收端重新计算文件的哈希值并与发送端提供的哈希值进行比较。
C#版
生成文件哈希值示例:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string filePath = @"C:\path\to\your\file.txt";
using (FileStream fs = new FileStream(filePath, FileMode.Open))
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(fs);
string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
Console.WriteLine($"File Hash: {hashString}");
}
}
}
验证文件完整性示例:
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string filePath = @"C:\path\to\your\file.txt";
string expectedHash = "your_known_hash_value_here"; // 已知的哈希值
using (FileStream fs = new FileStream(filePath, FileMode.Open))
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(fs);
string calculatedHash = BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
if (calculatedHash == expectedHash)
{
Console.WriteLine("文件完整性校验成功!");
}
else
{
Console.WriteLine("文件完整性校验失败!");
}
}
}
}
Java版
生成文件哈希值示例:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityCheck {
public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
File file = new File("C:/path/to/your/file.txt");
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
sha256.update(buffer, 0, bytesRead);
}
}
byte[] hashBytes = sha256.digest();
String hashString = bytesToHex(hashBytes);
System.out.println("File Hash: " + hashString);
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}
验证文件完整性示例:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class FileIntegrityVerification {
public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
File file = new File("C:/path/to/your/file.txt");
String expectedHash = "your_known_hash_value_here"; // 已知的哈希值
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
sha256.update(buffer, 0, bytesRead);
}
}
byte[] hashBytes = sha256.digest();
String calculatedHash = bytesToHex(hashBytes);
if (calculatedHash.equals(expectedHash)) {
System.out.println("文件完整性校验成功!");
} else {
System.out.println("文件完整性校验失败!");
}
}
private static String bytesToHex(byte[] hash) {
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
}
应用场景3:密码存储与验证
需求:安全地存储用户密码,并在登录时验证密码。
实现步骤:
- 存储密码:使用PBKDF2算法结合盐值生成密码的哈希值。
- 验证密码:在登录时重新计算密码的哈希值并与存储的哈希值进行比较。
C#版
存储密码示例:
using System;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string password = "mySecurePassword123!";
byte[] salt = GenerateSalt();
string hashedPassword = HashPassword(password, salt);
Console.WriteLine($"Salt: {BitConverter.ToString(salt).Replace("-", "")}");
Console.WriteLine($"Hashed Password: {hashedPassword}");
}
static byte[] GenerateSalt()
{
using (var rng = new RNGCryptoServiceProvider())
{
var salt = new byte[16];
rng.GetBytes(salt);
return salt;
}
}
static string HashPassword(string password, byte[] salt)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256))
{
byte[] hash = pbkdf2.GetBytes(32);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
验证密码示例:
using System;
using System.Security.Cryptography;
using System.Text;
class Program
{
static void Main()
{
string storedHash = "your_stored_hash_value_here"; // 存储的哈希值
byte[] storedSalt = Convert.FromHexString("your_stored_salt_value_here"); // 存储的盐值
string inputPassword = "mySecurePassword123!"; // 用户输入的密码
string calculatedHash = HashPassword(inputPassword, storedSalt);
if (calculatedHash == storedHash)
{
Console.WriteLine("密码验证成功!");
}
else
{
Console.WriteLine("密码验证失败!");
}
}
static string HashPassword(string password, byte[] salt)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA256))
{
byte[] hash = pbkdf2.GetBytes(32);
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}
}
Java版
存储密码示例:
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Base64;
public class PasswordStorage {
public static void main(String[] args) throws Exception {
String password = "mySecurePassword123!";
byte[] salt = generateSalt();
String hashedPassword = hashPassword(password, salt);
System.out.println("Salt: " + Base64.getEncoder().encodeToString(salt));
System.out.println("Hashed Password: " + hashedPassword);
}
private static byte[] generateSalt() {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return salt;
}
private static String hashPassword(String password, byte[] salt) throws Exception {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
}
}
验证密码示例:
import java.security.spec.KeySpec;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.util.Base64;
public class PasswordVerification {
public static void main(String[] args) throws Exception {
String storedHash = "your_stored_hash_value_here"; // 存储的哈希值
byte[] storedSalt = Base64.getDecoder().decode("your_stored_salt_value_here"); // 存储的盐值
String inputPassword = "mySecurePassword123!"; // 用户输入的密码
String calculatedHash = hashPassword(inputPassword, storedSalt);
if (calculatedHash.equals(storedHash)) {
System.out.println("密码验证成功!");
} else {
System.out.println("密码验证失败!");
}
}
private static String hashPassword(String password, byte[] salt) throws Exception {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 10000, 256);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();
return Base64.getEncoder().encodeToString(hash);
}
}