一、BCrypt加密
1.1 BCrypt简述
BCrypt是一种密码散列函数,即单向函数,无法解密BCrypt哈希
是强哈希算法,结合了SHA-256、随机盐和密钥来增强安全性
特点:
唯一性:每次加密生成的盐不一样所以密码的值也不一样;
不可逆:只能验证两个BCrypt哈希值是否相同,从而验证提供的密码是否与原始密码匹配
适用的场景:用户密码的加密
加密后的字符由4部分组成:
$2a$10$N9qo8uLOickxx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZmL17lhWy
标识符:BCrypt算法的标识符通常由$2a或$2b开头,2a是加密版本号;
代价因子:其中10表示代价因子,这里是2的10次方,也就是1024轮;
盐:在后面就是22位的盐;盐值是一个16字节(128位)的随机值,经过Base64编码后变成22个字符的字符串;
哈希值:最后的31位字符串就是哈希值;通常是24字节(192位)的原始哈希值,经过Base64编码后变成31个字符的字符串;
修改密码的话,可以向用户发送一次性密码重置链接,使用秘密问题或其他一些方式来确认用户身份信息,让他们设置新密码
1.2 代码示例
BCryptPasswordEncoder是一种使用BCrypt加密算法来加密密码的方法
主要目的是为了防止密码被明文存储在数据库中;
是Spring Security中用来加密用户密码的一个类
在使用时如果是springBoot项目需要引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring‐boot‐starter‐security</artifactId>
</dependency>
代码示例:
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
// 加密;返回加密后字符串
bcryptPasswordEncoder.encoder(password)
// 比较;返回true或false
// rawPassword 密码;encodedPassword加密后字符
bcrytPasswordEncoder.matches(rawPassword,encodedPassword)
二、MD5加密
2.1 MD5简述
MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希算法,将任意长度的输入转换成一个128位的二进制数
MD5 加密特性:
不可逆
相同的字符串内容加密后结果相同
MD5应用:
MD5 容易受到碰撞攻击,不适用于安全性认证;
存在碰撞就是指:在对两个不同的内容使用 MD5算法运算的时候,有可能得到一对相同的结果值
可以用于消息或文件的完整性校验
2.2 代码示例
使用 Java 内置的 MessageDigest 类
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class TestMD5 {
public static String encryptMD5(String input) {
try {
// 创建MD5加密对象
MessageDigest md = MessageDigest.getInstance("MD5");
// 执行加密操作
byte[] messageDigest = md.digest(input.getBytes());
// 将字节数组转换为16进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : messageDigest) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
// 返回加密后的字符串
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
/**
* 测试
*/
public static void main(String[] args) {
String str = "test1";
String encrypted = encryptMD5(str);
System.out.println("加密后字符串: " + encrypted);
}
}
三、RSA加密
3.1 RSA简述
RSA加密是一种非对称加密。可以在不直接传递密钥的情况下,完成解密;使用一对公私秘钥,公钥可以对外公开,私钥保密
加密是为了防止信息泄露,保证安全通信
签名是为了防止信息被篡改,确保消息的完整性和来源认证
公钥加密、私钥解密、私钥签名、公钥验签;
实际使用一般情况加密和验签同时使用
RSA加密流程:
A和B有自己的公钥和私钥;将公钥给对方系统
1、A要给B发送消息时,先用B的公钥对消息加密,再对加密的字符串使用A的私钥进行加签
2、A将加密后的字符串和加签后的字符串当作参数传给B
3、B先用A的公钥进行验签,没问题后在使用B的私钥解密
4、B处理完后,返回的参数用A的公钥加密
5、A用A的私钥解密返回参数
3.2 代码示例
import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
import org.apache.commons.codec.binary.Base64;
public class TestRSA {
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
/**
* 获取密钥对
*
* @return 密钥对
*/
public static KeyPair getKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(1024);
return generator.generateKeyPair();
}
/**
* 获取私钥
*
* @param privateKey 私钥字符串
* @return
*/
public static PrivateKey getPrivateKey(String privateKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(privateKey.getBytes());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
return keyFactory.generatePrivate(keySpec);
}
/**
* 获取公钥
*
* @param publicKey 公钥字符串
* @return
*/
public static PublicKey getPublicKey(String publicKey) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.decodeBase64(publicKey.getBytes());
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey);
return keyFactory.generatePublic(keySpec);
}
/**
* RSA加密
*
* @param data 待加密数据
* @param publicKey 公钥
* @return
*/
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
int inputLen = data.getBytes().length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data.getBytes(), offset, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data.getBytes(), offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
// 获取加密内容使用base64进行编码,并以UTF-8为标准转化成字符串
// 加密后的字符串
return new String(Base64.encodeBase64String(encryptedData));
}
/**
* RSA解密
*
* @param data 待解密数据
* @param privateKey 私钥
* @return
*/
public static String decrypt(String data, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.decodeBase64(data);
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offset = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offset > 0) {
if (inputLen - offset > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offset, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offset, inputLen - offset);
}
out.write(cache, 0, cache.length);
i++;
offset = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
// 解密后的内容
return new String(decryptedData, "UTF-8");
}
/**
* 签名
*
* @param data 待签名数据
* @param privateKey 私钥
* @return 签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
byte[] keyBytes = privateKey.getEncoded();
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey key = keyFactory.generatePrivate(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initSign(key);
signature.update(data.getBytes());
return new String(Base64.encodeBase64(signature.sign()));
}
/**
* 验签
*
* @param srcData 原始字符串
* @param publicKey 公钥
* @param sign 签名
* @return 是否验签通过
*/
public static boolean verify(String srcData, PublicKey publicKey, String sign) throws Exception {
byte[] keyBytes = publicKey.getEncoded();
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey key = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance("MD5withRSA");
signature.initVerify(key);
signature.update(srcData.getBytes());
return signature.verify(Base64.decodeBase64(sign.getBytes()));
}
public static void main(String[] args) {
try {
// 生成密钥对
KeyPair keyPair = getKeyPair();
String privateKey = new String(Base64.encodeBase64(keyPair.getPrivate().getEncoded()));
String publicKey = new String(Base64.encodeBase64(keyPair.getPublic().getEncoded()));
System.out.println("私钥:" + privateKey);
System.out.println("公钥:" + publicKey);
// RSA加密
String data = "待加密的文字内容";
String encryptData = encrypt(data, getPublicKey(publicKey));
System.out.println("加密后内容:" + encryptData);
// RSA解密
String decryptData = decrypt(encryptData, getPrivateKey(privateKey));
System.out.println("解密后内容:" + decryptData);
// RSA签名
String sign = sign(data, getPrivateKey(privateKey));
// RSA验签
boolean result = verify(data, getPublicKey(publicKey), sign);
System.out.print("验签结果:" + result);
} catch (Exception e) {
e.printStackTrace();
System.out.print("加解密异常");
}
}
}
// 这部分代码转自https://www.cnblogs.com/pcheng/p/9629621.html
四、AES加密
4.1 AES简述
属于对称加密;对称加密算法中,加密与解密的密钥是相同的,密钥为接收方与发送方协商产生
AES标准支持三种不同的密钥长度:128位、192位和256位
AES加密以16个字节为一组进行分组加密,要求明文的长度一定是16个字节的整数倍,如果不够16个字节的倍数,需要按照填充模式进行填充
常见的填充模式包括NoPadding、ZeroPadding、PKCS#7
加密模式:ECB、CBC
CBC工作模式使用最广泛
每一块的加密依赖于前一块的密文,提供了良好的保密性和抗重播攻击能力
随机数作为IV参数,对于同一份明文,每次生成的密文都不同
4.2 代码示例
引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
实现代码
@Test
public static void testAes(String data) {
// 密钥,长度必须是16、24或32
byte[] key = "1234567898765432".getBytes();
// 初始化向量,CBC模式长度必须是16
byte[] iv = "1234567898765432".getBytes();
// 创建AES对象,指定密钥和初始化向量
SymmetricCrypto aes = new AES(Mode.CBC, Padding.PKCS5Padding, key, iv);
// 加密并进行Base转码
String encrypt = aes.encryptBase64(data);
System.out.println(encrypt);
// 解密为字符串
String decrypt = aes.decryptStr(encrypt);
System.out.println(decrypt);
}
五、商用密码算法
5.1 算法分类
SM1、SM2、SM3、SM4、SM9、ZUC等一系列商用密码算法构成了我国完整的密码算法体系
SM1 对称 电子政务、硬件等加密
SM2 非对称 数字签名、密钥交换
SM3 摘要算法 数字签名、完整性验证
SM4 对称 电子政务、无线局域网加密
SM9 非对称 数据加密、身份认证
5.2 SM4加密算法
5.2.1 SM4简述
SM4算法的特点是:
(1)属于对称密码算法,加解密的密钥相同;
(2)明密文和密钥均为128比特,分组长度为128比特;
(3)SM4密码算法加解密算法的轮数为32轮,每轮的轮结构相同,只是轮密钥使用相反
金融行业国内通用标准为SM4,对应国际标准SM4
有两种模式ECB和CBC
区别是前者只需要一个key,而后者不仅需要一个key还需要一个iv值
SM4的CBC模式和AES的CBC模式类似;安全性与AES-128基本一致,但是实现更简单、效率更高
SM4的CBC模式通过引入链式依赖来提高安全性。它将每个明文块与前一个密文块进行异或后再进行加密。在这种方法中,每个密文块都依赖于它前面的所有明文块。同时,为了保证每条消息的唯一性,在第一个块中需要使用初始化向量IV
5.2.2 代码示例
用工具类来实现CBC模式
引入依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
代码实现
public class TestSm4 {
/**
* 密钥 长度必须是16
*/
private static String SECRET_KEY = "";
/**
* IV是初始向量,它的作用是使相同的明文每次加密得到的密文都不同。 长度必须是16
*/
private static String IV= "";
/**
* 加密成base64/字节数组
*/
public static String encrypt(String plainTxt){
String cipherTxt = "";
SymmetricCrypto sm4Cbc = new SM4(Mode.CBC, Padding.PKCS5Padding, SECRET_KEY.getBytes(CharsetUtil.CHARSET_UTF_8), IV.getBytes(CharsetUtil.CHARSET_UTF_8));
byte[] encrypHex = sm4Cbc.encrypt(plainTxt);
cipherTxt = Base64.encode(encrypHex);
return cipherTxt;
}
/**
* 解密
*/
public static String decrypt(String cipherTxt){
String plainTxt = "";
try {
SymmetricCrypto sm4 = new SM4(Mode.CBC, Padding.PKCS5Padding, SECRET_KEY.getBytes(CharsetUtil.CHARSET_UTF_8), IV.getBytes(CharsetUtil.CHARSET_UTF_8));
byte[] cipherHex = Base64.decode(cipherTxt);
plainTxt = sm4.decryptStr(cipherHex, CharsetUtil.CHARSET_UTF_8);
} catch(Exception e) {
log.error(e.getMessage());
}
return plainTxt;
}
/**
* 测试
*/
public static void main(String[] argc){
String originTxt = "加密测试";
String cipherTxt = encrypt(originTxt);
System.out.println("加密后密文: " + cipherTxt);
String plainTxt = decrypt(cipherTxt);
System.out.println("解密后结果: " + plainTxt);
}
}
使用SmUtil实现
public void testSm4(String text) {
SymmetricCrypto sm4 = SmUtil.sm4();
// 加密后密文
String encryptHex = sm4.encryptHex(text);
// 解密后结果
String decryptStr = sm4.decryptStr(encryptHex, CharsetUtil.CHARSET_UTF_8);
}
5.2 SM2签名算法
5.2.1 SM2简述
SM2算法是一种非对称加密算法,SM2算法主要用于数字签名、密钥交换和数据加密。在使用SM2算法时,需要生成一个私钥,一个公钥。私钥用于签名和解密,公钥用于验证签名和加密。是一种更先进安全的算法
5.2.2 代码实现
引入依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
实现:
// 加密
public static String encrypt(String data, String publicKey,String privateKey) {
SM2 sm2 = SmUtil.sm2(publicKey,privateKey);
return Arrays.toString(sm2.encrypt(data, KeyType.PublicKey));
}
// 解密
public static String decrypt(String data, String publicKey,String privateKey) {
SM2 sm2 = SmUtil.sm2(publicKey,privateKey);
return sm2.decryptStr(data, KeyType.PrivateKey);
}
public static void main(String[] args) {
String data = "text";
// 公钥;公钥长度为64字节(512位)
String publicKey = "";
// 私钥;私钥长度为32字节(256位)
String privateKey = "";
// 加密
String encryptedData = encrypt(data, publicKey,privateKey);
// 解密
String decryptedData = decrypt(encryptedData,publicKey, privateKey);
}
5.3 SM3摘要算法
5.3.1 SM3简述
SM3算法是中国国家密码管理局发布的消息摘要算法,用于生成消息的哈希值
SM3算法采用Merkle-Damgård结构,消息分组长度为512位,摘要值长度为256位(即32字节)。其输入可以是任意长度数据,但加密结果始终是256位数据。
应用场景:
数字签名:使用SM3算法生成的哈希值作为签名的一部分,确保签名的真实性和完整性。
消息完整性验证:通过比较消息的哈希值来验证消息在传输过程中是否被篡改。
流程简述:
1、消息填充:
将输入消息填充至长度为512位的倍数。
2、消息分组:
将填充后的消息按512位(64字节)进行分组
3、消息扩展:
对每个512位的分组进行扩展,每个分组称为一个消息块,对每个消息块进行扩展,形成132个字(每个字为32位)的扩展消息
4、迭代压缩:
将132个消息字,通过64轮的迭代压缩计算,最终得到256位的哈希值。
在每一轮迭代中,都会使用8个32位的寄存器(A、B、C、D、E、F、G、H),这些寄存器在迭代开始时被初始化为固定的常数值。
通过一系列操作,更新这些寄存器的值。
在完成64轮迭代后,将8个寄存器的值进行异或运算,得到最终的256位哈希值。
5.3.2 代码实现
public static void main(String[] args) {
String input = "Hello";
String output = sm3Encrypt(input);
System.out.println("SM3 Result: " + output);
}
public static String sm3Encrypt(String input) {
try {
MessageDigest md = MessageDigest.getInstance("SM3");
byte[] digest = md.digest(input.getBytes());
return bytesToHex(digest);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
// 转为16进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
5.4 HMAC-SM3
5.4.1 HMAC-SM3简述
HMAC-SM3是一种基于SM3的带密钥的哈希算法认证技术,使用SM3算法生成哈希值,引入密钥来增强安全性
应用场景:
消息认证:通过比较发送方和接收方生成的消息认证码来验证消息的真实性和完整性。
数据完整性保护:在数据传输和存储过程中,使用HMAC-SM3算法来确保数据不被篡改。
5.4.2 代码实现
引入依赖
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.69</version> <!-- 使用最新版本 -->
</dependency>
代码实现
public static String hmacSM3(byte[] key, byte[] data) {
// 创建一个 SM3Digest 对象,用于进行 SM3 哈希运算
SM3Digest sm3Digest = new SM3Digest();
// 创建一个 HMac 对象,使用 SM3Digest 作为底层哈希算法
HMac hmac = new HMac(sm3Digest);
// 使用密钥初始化 HMac 对象
hmac.init(new KeyParameter(key));
// 更新 HMac 对象的数据
hmac.update(data, 0, data.length);
// 计算 HMAC-SM3 值,并将结果存储在 result 数组中
byte[] result = new byte[hmac.getMacSize()];
// 执行最终的哈希运算,并将结果填充到 result 数组中
hmac.doFinal(result, 0);
return bytesToHex(result);
}
public static void main(String[] args) {
// 示例密钥
byte[] key = "123456789".getBytes();
byte[] data = "Hello".getBytes();
// 计算HMAC-SM3值
String hmac = hmacSM3(key, data);
System.out.println("HMAC-SM3: " + hmac);
}
// 转为16进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
// 将每个字节转换为16进制字符串,并拼接到 StringBuilder 中
sb.append(String.format("%02x", b));
}
return sb.toString();
}