引言
在项目开发过程中,注册时需要向数据库写入用户账号信息,其中主要包括用户名和密码。
那么,密码你是明文显示还是经过加密了呢?
如果直接明文显示,在数据库中可以直接查看用户密码;如果经过加密,则数据库中无法直接获取用户密码。
很显然,密码采用明文的方式实在是太不安全了。一方面,系统的维护管理人员可以直接获得用户密码;另一方面,如果信息被网络拦截后,用户密码很容易被非法盗取。
所以,密码不加密非常不安全。
密码加密方式
加密算法整体上可以分为不可逆加密,以及可逆加密,可逆加密又可以分为对称加密和非对称加密。
不可逆算法
不可逆加密的算法的加密是不可逆的,密文无法被还原成原文。
散列算法,就是一种不可逆算法。散列算法中,明文通过散列算法生成散列值,散列值是长度固定的数据,和明文长度无关。
散列算法的具体实现有很多种,常见的包括MD5、SHA1、SHA-224、SHA-256等等。
散列算法常用于数字签名、消息认证、密码存储等场景。
散列算法是不需要密钥的,当然也有一些不可逆算法,需要密钥,例如HMAC算法。
MD5
MD5,全称为“Message-Digest Algorithm 5”,翻译过来叫“信息摘要算法”。它可以将任意长度的数据通过散列算法,生成一个固定长度的散列值。MD5算法的输出长度为128位,通常用32个16进制数表示。
public class MD5 {
private static final String MD5_ALGORITHM = "MD5";
public static String encrypt(String data) throws Exception {
// 获取MD5算法实例
MessageDigest messageDigest = MessageDigest.getInstance(MD5_ALGORITHM);
// 计算散列值
byte[] digest = messageDigest.digest(data.getBytes());
Formatter formatter = new Formatter();
// 补齐前导0,并格式化
for (byte b : digest) {
formatter.format("%02x", b);
}
return formatter.toString();
}
public static void main(String[] args) throws Exception {
String data = "Hello World";
String encryptedData = encrypt(data);
System.out.println("加密后的数据:" + encryptedData);
}
}
MD5有一些优点,比如计算速度快、输出长度固定、应用广泛等等。
但是作为一个加密算法,它有一个天大的缺点,那就是不安全。
MD5 算法已经被攻破,而且 MD5 算法的输出长度有限,攻击者可以通过暴力破解或彩虹表攻击等方式,找到与原始数据相同的散列值,从而破解数据。
虽然可以通过加盐,也就是对在原文里再加上一些不固定的字符串来缓解,但是完全可以用更安全的SHA系列算法替代。
SHA-256
SHA(Secure Hash Algorithm)系列算法是一组密码散列函数,用于将任意长度的数据映射为固定长度的散列值。SHA系列算法由美国国家安全局(NSA)于1993年设计,目前共有SHA-1、SHA-2、SHA-3三种版本。
其中SHA-1系列存在缺陷,已经不再被推荐使用。
SHA-2算法包括SHA-224、SHA-256、SHA-384和SHA-512四种散列函数,分别将任意长度的数据映射为224位、256位、384位和512位的散列值。
public class SHA256 {
private static final String SHA_256_ALGORITHM = "SHA-256";
public static String encrypt(String data) throws Exception {
//获取SHA-256算法实例
MessageDigest messageDigest = MessageDigest.getInstance(SHA_256_ALGORITHM);
//计算散列值
byte[] digest = messageDigest.digest(data.getBytes());
StringBuilder stringBuilder = new StringBuilder();
//将byte数组转换为15进制字符串
for (byte b : digest) {
stringBuilder.append(Integer.toHexString((b & 0xFF) | 0x100), 1, 3);
}
return stringBuilder.toString();
}
public static void main(String[] args) throws Exception {
String data = "Hello World";
String encryptedData = encrypt(data);
System.out.println("加密后的数据:" + encryptedData);
}
}
SHA-2 算法之所以比 MD5 强,主要有两个原因:
-
散列值长度更长:例如 SHA-256 算法的散列值长度为256位,而 MD5 算法的散列值长度为 128 位,这就提高了攻击者暴力破解或者彩虹表攻击的难度。
-
更强的碰撞抗性:SHA 算法采用了更复杂的运算过程和更多的轮次,使得攻击者更难以通过预计算或巧合找到碰撞。
当然,SHA-2 也不是绝对安全的,散列算法都有被暴力破解或者彩虹表攻击的风险,所以,在实际的应用中,加盐还是必不可少的。
对称加密算法
对称加密算法,使用同一个密钥进行加密和解密。
加密和解密过程使用的是相同的密钥,因此密钥的安全性至关重要。如果密钥泄露,攻击者可以轻易地破解加密数据。
常见的对称加密算法包括DES、3DES、AES等。其中,AES算法是目前使用最广泛的对称加密算法之一,具有比较高的安全性和加密效率。
DES
DES(Data Encryption Standard)算法是一种对称加密算法,由IBM公司于1975年研发,是最早的一种广泛应用的对称加密算法之一。
DES算法使用56位密钥对数据进行加密,加密过程中使用了置换、替换、异或等运算,具有较高的安全性。
public class DES {
private static final String DES_ALGORITHM = "DES";
/**
* DES加密
*
* @param data 待加密的数据
* @param key 密钥,长度必须为8位
* @return 加密后的数据,使用Base64编码
*/
public static String encrypt(String data, String key) throws Exception {
// 根据密钥生成密钥规范
KeySpec keySpec = new DESKeySpec(key.getBytes());
// 根据密钥规范生成密钥工厂
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
// 根据密钥工厂和密钥规范生成密钥
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
// 根据加密算法获取加密器
Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
// 初始化加密器,设置加密模式和密钥
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
// 加密数据
byte[] encryptedData = cipher.doFinal(data.getBytes());
// 对加密后的数据进行Base64编码
return Base64.getEncoder().encodeToString(encryptedData);
}
/**
* DES解密
*
* @param encryptedData 加密后的数据,使用Base64编码
* @param key 密钥,长度必须为8位
* @return 解密后的数据
*/
public static String decrypt(String encryptedData, String key) throws Exception {
// 根据密钥生成密钥规范
KeySpec keySpec = new DESKeySpec(key.getBytes());
// 根据密钥规范生成密钥工厂
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(DES_ALGORITHM);
// 根据密钥工厂和密钥规范生成密钥
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
// 对加密后的数据进行Base64解码
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
// 根据加密算法获取解密器
Cipher cipher = Cipher.getInstance(DES_ALGORITHM);
// 初始化解密器,设置解密模式和密钥
cipher.init(Cipher.DECRYPT_MODE, secretKey);
// 解密数据
byte[] decryptedData = cipher.doFinal(decodedData);
// 将解密后的数据转换为字符串
return new String(decryptedData);
}
public static void main(String[] args) throws Exception {
String data = "Hello World";
String key = "12345678";
String encryptedData = encrypt(data, key);
System.out.println("加密后的数据:" + encryptedData);
String decryptedData = decrypt(encryptedData, key);
System.out.println("解密后的数据:" + decryptedData);
}
}
DES的算法速度较快,但是在安全性上面并不是最优选择,因为DES算法的密钥长度比较短,被暴力破解和差分攻击的风险比较高,一般推荐用一些更安全的对称加密算法,比如3DES、AES。
AES
AES(Advanced Encryption Standard)即高级加密标准,是一种对称加密算法,被广泛应用于数据加密和保护领域。AES算法使用的密钥长度为128位、192位或256位,比DES算法的密钥长度更长,安全性更高。
public class AES {
private static final String AES_ALGORITHM = "AES";
// AES加密模式为CBC,填充方式为PKCS5Padding
private static final String AES_TRANSFORMATION = "AES/CBC/PKCS5Padding";
// AES密钥为16位
private static final String AES_KEY = "1234567890123456";
// AES初始化向量为16位
private static final String AES_IV = "abcdefghijklmnop";
/**
* AES加密
*
* @param data 待加密的数据
* @return 加密后的数据,使用Base64编码
*/
public static String encrypt(String data) throws Exception {
// 将AES密钥转换为SecretKeySpec对象
SecretKeySpec secretKeySpec = new SecretKeySpec(AES_KEY.getBytes(), AES_ALGORITHM);
// 将AES初始化向量转换为IvParameterSpec对象
IvParameterSpec ivParameterSpec = new IvParameterSpec(AES_IV.getBytes());
// 根据加密算法获取加密器
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
// 初始化加密器,设置加密模式、密钥和初始化向量
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
// 加密数据
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 对加密后的数据使用Base64编码
return Base64.getEncoder().encodeToString(encryptedData);
}
/**
* AES解密
*
* @param encryptedData 加密后的数据,使用Base64编码
* @return 解密后的数据
*/
public static String decrypt(String encryptedData) throws Exception {
// 将AES密钥转换为SecretKeySpec对象
SecretKeySpec secretKeySpec = new SecretKeySpec(AES_KEY.getBytes(), AES_ALGORITHM);
// 将AES初始化向量转换为IvParameterSpec对象
IvParameterSpec ivParameterSpec = new IvParameterSpec(AES_IV.getBytes());
// 根据加密算法获取解密器
Cipher cipher = Cipher.getInstance(AES_TRANSFORMATION);
// 初始化解密器,设置解密模式、密钥和初始化向量
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
// 对加密后的数据使用Base64解码
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
// 解密数据
byte[] decryptedData = cipher.doFinal(decodedData);
// 返回解密后的数据
return new String(decryptedData, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
String data = "Hello World";
String encryptedData = encrypt(data);
System.out.println("加密后的数据:" + encryptedData);
String decryptedData = decrypt(encryptedData);
System.out.println("解密后的数据:" + decryptedData);
}
}
AES算法采用的密钥长度更长,密钥空间更大,安全性更高,能够有效地抵抗暴力破解攻击。
当然,因为密钥长度较长,需要的存储也更多。
对于对称加密算法而言,最大的痛点就在于密钥管理困难,相比而言,非对称加密就没有这个担忧。
非对称加密算法
非对称加密算法需要两个密钥,这两个密钥互不相同,但是相互匹配,一个称为公钥,另一个称为私钥。
使用其中的一个加密,则使用另一个进行解密。例如使用公钥加密,则需要使用私钥解密。
RSA
RSA算法是是目前应用最广泛的非对称加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman三人在1978年发明,名字来源三人的姓氏首字母。
public class RSA {
private static final String RSA_ALGORITHM = "RSA";
/**
* 生成RSA密钥对
*
* @return RSA密钥对
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
keyPairGenerator.initialize(2048); // 密钥大小为2048位
return keyPairGenerator.generateKeyPair();
}
/**
* 使用公钥加密数据
*
* @param data 待加密的数据
* @param publicKey 公钥
* @return 加密后的数据
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedData);
}
/**
* 使用私钥解密数据
*
* @param encryptedData 加密后的数据
* @param privateKey 私钥
* @return 解密后的数据
*/
public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedData = cipher.doFinal(decodedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String data = "Hello World";
String encryptedData = encrypt(data, publicKey);
System.out.println("加密后的数据:" + encryptedData);
String decryptedData = decrypt(encryptedData, privateKey);
System.out.println("解密后的数据:" + decryptedData);
}
}
RSA算法的优点是安全性高,公钥可以公开,私钥必须保密,保证了数据的安全性;可用于数字签名、密钥协商等多种应用场景。
缺点是加密、解密速度较慢,密钥长度越长,加密、解密时间越长;密钥长度过短容易被暴力破解,密钥长度过长则会增加计算量和存储空间的开销。
BCryptPasswordEncoder 加密
BCryptPasswordEncoder 是一种不可逆加密方式, 它是 Spring Security 中使用的一个加密方法。BCryptPasswordEncoder 方法采用了 SHA-256+随机盐+密钥对密码进行加密。
- encode 方法加密:在 BCryptPasswordEncoder 中使用 encode 方法对密码进行加密,因为是通过 hash 算法进行加密,同样的密码输出的密文是不同的
- matches 方法解密/匹配: 虽然通过 encode 方法加密的密码是不能解密的,但是在 BCryptPasswordEncoder 中提供了一个 matches方法来匹配密码,它的原理是把需要配对的密码经过同一个 hash 函数计算,把计算得到的 hash 值到数据库中匹配,相同的hash值则说明是同一个密码。
BCryptPasswordEncoder 牛逼的地方在同一密码,比如 “12345”,每次使用 BCryptPasswordEncoder 加密后的结果居然不一样。其原理是这样的,每次加密时,BCryptPasswordEncoder 会生成一个随机盐,将密码和生成的随机盐一起加密,自然加密后的密文都不相同。
当用户注册时,输入原始密码,比如“12345”,那么经过 BCryptPasswordEncoder 加密后将得到一段密文,如 “$10$hGzrjH.uEkHxDvbPmfY5QOT./8aU6OsGnTu2TWvkphUKuf6Dsa9NC”。然后,将密文存放在数据库中,由于 BCryptPasswordEncoder 是采用不可逆加密方式,这样任何人都不知道用户的原始密码。
现在问题来了,如果用户输入原始密码登录,那怎么知道用户输入的密码和数据库里存储的密文是匹配的呀?
答案就是 BCryptPasswordEncoder 虽然采用不可逆加密方式,是无法从数据库中的密文推算出原始密码,但是却可以从密文中获得加密时使用的随机盐。BCryptPasswordEncoder 只需要将用户输入的原始密码和获得的随机盐一起进行加密,将得到的密文和数据库中存放的密文比较,匹配则说明用户输入密码正确,不匹配则说明用户输入密码错误。
BCryptPasswordEncoder 案例
使用 BCryptPasswordEncoder 需要导入 Spring Security 依赖,如下:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
package com.binge;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author binge
* @Description BCryptPasswordEncoder 加密案例
* @date 2023年10月18日 上午 10:17
*/
public class BCryptPasswordEncoderDemo {
public static void main(String[] args) {
//获取 PasswordEncoder 实例
PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//原始密码
String rawPassword = "12345";
//密码加密
String encodePassword = passwordEncoder.encode(rawPassword);
System.out.println(encodePassword);
//判断密码是否匹配
boolean matches = passwordEncoder.matches(rawPassword,encodePassword);
System.out.println("判断密码是否匹配:"+ matches);
}
}
BCryptPasswordEncoder 优势和缺点
优势:BCryptPasswordEncoder 由于采用的是 SHA-256+随机盐的方式,安全性高
缺点:BCryptPasswordEncoder 每次加密都要调用 Hash 函数,性能偏低
标签:AES,加密,String,BCryptPasswordEncoder,账号,算法,密钥,加密算法,encryptedData From: https://www.cnblogs.com/binbingg/p/17771640.html