本文介绍了加密算法和非加密算法的基本概念,重点讲解了加密算法中的对称加密和非对称加密,以及常用的加密算法如AES、DES、RSA等。同时,介绍了哈希算法,尤其是MD5和SHA系列,并阐述了其在数据完整性验证、密码存储和数字签名等场景的应用。文章还详细描述了在用户注册和登录过程中,如何使用哈希算法进行密码加密和验证,提供了Java代码示例,并通过工具类PasswordUtils和PasswordEncoder实现了密码的安全存储和验证流程。强调了在密码安全领域,应使用更安全的算法如SHA-256替代MD5。
为了保证信息的安全性,就需要采用信息加密技术对信息进行伪装,使得信息非法窃取者无法理解信息的真实合义,信息的合法拥有者可以利用特征码对信息的完整性进行校验。采用加密算法对信息使用者的身份进行认证、识别和确认,以对信息的使用进行控制。
加密技术
包括两个元素:算法
和 秘钥
,密钥加密技术的密码体制分为 对称秘钥体制 和 非对称加密体制 两种。相应地,对数据加密的技术分为两类,即对称加密(私人密钥加密)和非对称加密(公开密钥加密)。
对称加密算法(也称为“私钥加密”)是指加密和解密使用同一密钥的算法。它们的特点是加密和解密过程使用相同的密钥,因此密钥的保护至关重要。如果密钥泄露,整个系统的安全性就会受到威胁。
非对称加密算法(也称为“公钥加密”)使用一对密钥:公钥(公开的密钥)和私钥(秘密的密钥)。公钥用于加密,私钥用于解密。非对称加密算法的安全性基于数学难题(如大数分解或离散对数问题),其最大优点是能够实现密钥的公开分发。
哈希算法不是加密算法,但它在加密和信息安全中起着重要的作用。哈希算法将输入数据映射到固定长度的哈希值,用于数据完整性验证、数字签名等。
哈希算法的目的是将任意长度的输入(通常称为“消息”)映射到固定长度的输出(通常称为“哈希值”或“摘要”)。哈希算法具有以下特点:
MD5 和 SHA(如 SHA-1、SHA-256)是哈希算法(Hashing Algorithms)。哈希算法和加密算法有着不同的设计目的和用途,虽然它们在某些方面有相似性(如都处理数据并生成固定长度的输出),但它们在工作原理和用途上有本质的区别。
目的:确保数据在传输或存储过程中没有被篡改。
如何工作:通过对数据计算哈希值,在数据传输或存储时记录哈希值,接收方或读取方可以重新计算数据的哈希值,并与原先的哈希值进行对比。如果哈希值一致,说明数据未被篡改;如果不一致,说明数据可能已被修改。
示例:
你下载一个文件,比如一个软件包。官网提供了这个文件的SHA-256哈希值。下载后,你计算文件的哈希值并与官网的哈希值进行比较。如果两者一致,说明文件未被篡改。如果不一致,说明文件可能在下载过程中被篡改或损坏。
目的:确保密码在存储时不会暴露,保护用户隐私,即使数据库泄露也能避免密码泄漏。
如何工作:通过哈希算法(如SHA-256、bcrypt等)将密码转换为哈希值。哈希算法是单向的,即无法从哈希值逆向推导出原始密码。为进一步增强安全性,通常会为密码加盐(即在密码前后添加随机字符串)以避免相同密码的哈希值相同。
示例:
用户注册时输入密码 “password123”。系统对这个密码应用哈希算法(如SHA-256),并把哈希值(例如 abc123…)存储在数据库中。即使数据库泄露,攻击者也无法直接获得用户的密码。
如果密码存储中使用了盐(salt),每个用户的密码哈希值都会不同,即使密码相同,哈希值也会不同。
目的:保证数据的来源真实性、完整性和不可否认性。即确保数据没有被篡改,且确实是由发送方发出的。
如何工作:发送方使用 私钥 对数据的哈希值进行加密(即生成数字签名),接收方用发送方的公钥解密签名并验证数据是否被篡改。如果数据哈希值一致,证明数据是未被篡改的且确实来自发送方。
示例:
你发送一份重要合同文件给某人,你用你的私钥对合同文件的哈希值签名。接收方收到文件后,会用你的公钥解密签名并计算文件的哈希值。如果解密后的哈希值与计算值一致,接收方可以确认文件未被篡改,并且确认是由你发送的(你是签名者)。
在区块链中,每个区块都包含上一个区块的哈希值,保证整个链条的数据完整性,并确保每个区块的来源和数据没有被篡改。
概念 | 目的 | 关键技术 | 应用示例 |
---|---|---|---|
数据完整性验证 | 确保数据未被篡改或损坏 | 哈希算法(如MD5、SHA-256) | 文件下载时验证文件是否被篡改 |
密码存储 | 确保用户密码安全,即使数据库泄露也不暴露密码 | 哈希算法 + 盐(salt) | 存储用户密码时进行哈希处理 |
数字签名 | 确保数据的真实性、完整性,并防止否认发送者 | 公钥加密与私钥解密(对数据的哈希值签名) | 电子邮件或文件的签名验证 |
这三者都依赖于哈希算法和公钥/私钥加密技术,但它们分别用于数据完整性验证、密码保护和验证数据来源与完整性,各有不同的应用场景和技术实现。
明文密码(比如123456)->通过 哈希算法
->产生一串字符串,这就是密文密码。
加盐的目的:
当两个用户的密码相同时,单纯使用不加盐的MD5加密方式,会发现数据库中存在相同结构的密码,这样也是不安全的。我们希望即便是两个人的原始密码一样,加密后的结果也不一样。如何做到呢?其实就好像炒菜一样,两道一样的鱼香肉丝,加的盐不一样,炒出来的味道就不一样。MD5加密也是一样,需要进行盐值加密。
流程:
上面注册和登录的流程都没变,就是在使用明文密码加密时,变成了 明文密码+盐 进行加密。这样一来,同样的原文密码,经过加盐的加密之后,存在数据库的密文密码也是不一样的。
MD5(Message Digest Algorithm 5)是一个常见的哈希算法,输出128位的哈希值。尽管 MD5 存在碰撞风险,仍然被广泛用于非敏感的校验场景(如文件完整性验证)。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Example {
public static void main(String[] args) {
String input = "Hello, world!";
try {
// 获取 MD5 摘要实例
MessageDigest md = MessageDigest.getInstance("MD5");
// 计算输入字符串的哈希值
byte[] hashBytes = md.digest(input.getBytes());
// 转换成十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
hexString.append(String.format("%02x", b));
}
// 输出哈希值
System.out.println("MD5 Hash: " + hexString.toString());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
输出案例
MD5 Hash: fc3ff98e8c6a0d3087d515c0473f8677
在这个示例中:
SHA(Secure Hash Algorithm)是一组哈希算法。SHA-256 是其中一种常见的哈希算法,输出 256 位(32 字节)的哈希值,安全性更高。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SHA256Example {
public static void main(String[] args) {
String input = "Hello, world!";
try {
// 获取 SHA-256 摘要实例
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 计算输入字符串的哈希值
byte[] hashBytes = md.digest(input.getBytes());
// 转换成十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
hexString.append(String.format("%02x", b));
}
// 输出哈希值
System.out.println("SHA-256 Hash: " + hexString.toString());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
输出案例
SHA-256 Hash: a591a6d40bf420404a011733cfb7b190d62c65bf0bcdaee23a7c60ab4e61d140
在这个示例中:
使用下面的两个工具类就可以实现对明文密码的加密,PasswordUtils.java 是工具的入口,如果是使用只关注该类即可,里面 有三个方法,如下介绍:
PasswordEncoder.java 是进行加密的主要实现类,PasswordUtils.java是暴露出的接口。
import java.util.UUID;
/**
* @ClassName: PasswordUtils
* @Description: 密码工具类
* @createTime: 2020/2/4 14:44
* @Author: 冯凡利
* @UpdateUser: 冯凡利
* @Version: 0.0.1
*/
public class PasswordUtils {
/**
* 匹配密码
* @param salt 盐
* @param rawPass 明文
* @param encPass 密文
* @return
*/
public static boolean matches(String salt, String rawPass, String encPass) {
return new PasswordEncoder(salt).matches(encPass, rawPass);
}
/**
* 明文密码加密
* @param rawPass 明文
* @param salt
* @return
*/
public static String encode(String rawPass, String salt) {
return new PasswordEncoder(salt).encode(rawPass);
}
/**
* 获取加密盐
* @return
*/
public static String getSalt() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
}
public static void main(String[] args) {
// 给密码 和 salt,看密码密文
String encode = encode("123456", "d3709b4efe9d47fcaba0");
System.out.println(encode);
}
}
package com.feng.companyframe.utils;
import lombok.extern.slf4j.Slf4j;
import java.security.MessageDigest;
/**
* @ClassName: PasswordEncoder
* @Description: 密码加密
* @createTime: 2020/2/4 14:44
* @Author: 冯凡利
* @UpdateUser: 冯凡利
* @Version: 0.0.1
*/
@Slf4j
public class PasswordEncoder {
private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d",
"e", "f" };
private final static String MD5 = "MD5";
private final static String SHA = "SHA";
private Object salt;
private String algorithm;
public PasswordEncoder(Object salt) {
this(salt, MD5);
}
public PasswordEncoder(Object salt, String algorithm) {
this.salt = salt;
this.algorithm = algorithm;
}
/**
* 密码匹配验证
* @param encPass 密文
* @param rawPass 明文
* @return
*/
public boolean matches(String encPass, String rawPass) {
String pass1 = "" + encPass;
String pass2 = encode(rawPass);
return pass1.equals(pass2);
}
/**
* 密码加密
* @param rawPass
* @return
*/
public String encode(String rawPass) {
String result = null;
try {
MessageDigest md = MessageDigest.getInstance(this.algorithm);
// 加密后的字符串
result = byteArrayToHexString(md.digest(mergePasswordAndSalt(rawPass).getBytes("utf-8")));
} catch (Exception ex) {
log.error("加密失败{}",ex.getLocalizedMessage());
}
return result;
}
private String mergePasswordAndSalt(String password) {
if (password == null) {
password = "";
}
if ((salt == null) || "".equals(salt)) {
return password;
} else {
return password + "{" + salt.toString() + "}";
}
}
/**
* 转换字节数组为16进制字串
*
* @param b
* 字节数组
* @return 16进制字串
*/
private String byteArrayToHexString(byte[] b) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
/**
* 将字节转换为16进制
* @param b
* @return
*/
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n = 256 + n;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static void main(String[] args) {
String a=null;
String b=null;
if(a!=b||!a.equals(b)){
System.out.println(a==b);
}
}
}