啊,作者对上篇文章做出改进(其实是找gpt做的改进),于本篇记录一下。
Python密钥生成
盐(Salt)
首先引入了一个随机生成的盐(salt
)。盐是用来确保即使两次使用相同的密码,通过密钥派生函数(如PBKDF2)生成的密钥也会是不同的。主要是服务下面的PBKDF2密钥派生函数,让函数生成的密钥变化!
salt = os.urandom(16)
PBKDF2
使用PBKDF2HMAC,它是一个基于密码的密钥派生函数,通常用于从用户密码生成密钥。它使用HMAC作为伪随机函数,并可以进行多次迭代,增加暴力破解的难度。
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=backend
)
key = kdf.derive(b"Some password")
在这里,使用了PBKDF2与SHA-256哈希函数,设置了100,000次迭代。这样产生的密钥不仅与所提供的密码相关,还与盐值相关,增加了密钥的复杂性和随机性。
生成随机 nonce
-
使用
os.urandom(16)
代替了原来的随机函数来生成nonce。os.urandom
被广泛认为是一个安全的随机数生成器,适用于密码学应用。
nonce = os.urandom(16)
数据的完整性和真实性
-
HMAC: 为了确保加密数据(密文)的完整性和真实性,添加一个HMAC(Hash-based Message Authentication Code)。HMAC是一种特定类型的消息认证码(MAC),涉及到哈希函数和秘密加密密钥。简单地说,当您发送加密的消息时,您也可以发送HMAC,接收方可以使用同样的密钥来验证消息的完整性。
h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(ciphertext)
tag = h.finalize()
之后,在解密前,首先验证了密文的HMAC。只有在HMAC验证通过后,我们才会尝试解密密文,这可以防止潜在的篡改攻击。
h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(ciphertext)
h.verify(tag)
这些改进旨在确保密钥的强度、数据的完整性和真实性,并采用了一些被广泛接受的加密最佳实践。然而,请注意,在实际的生产环境中,可能还需要考虑其他的安全因素和实践。
完整代码如下:
import os
import random
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import ciphers, hmac, hashes
from cryptography.hazmat.primitives.ciphers import Cipher
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.asymmetric import padding
# 密钥生成 (这只是一个简化的例子,真实情境中你需要考虑如何安全存储密钥)
salt = os.urandom(16) # 加盐可以帮助防止两次生成的密钥相同
backend = default_backend()
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=backend
)
key = kdf.derive(b"Some password")
# 生成随机 nonce
nonce = os.urandom(16)
# 创建AES Cipher实例并设置为CTR模式
cipher = Cipher(ciphers.algorithms.AES(key), ciphers.modes.CTR(nonce), backend=default_backend())
# 创建一个加密器对象
encryptor = cipher.encryptor()
# 加密"HELLO"字符串
ciphertext = encryptor.update(b'HELLO') + encryptor.finalize()
print("Ciphertext:", ciphertext)
# 生成HMAC以确保数据完整性和真实性
h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(ciphertext)
tag = h.finalize()
decryptor = cipher.decryptor()
try:
# 在解密之前验证HMAC
h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(ciphertext)
h.verify(tag)
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
print("Decrypted Data:", decrypted_data)
except Exception as e:
print(f"Decryption failed: {e}")
作者个人问题解决
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=backend
)
作者首先不明白既然用的是PBKDF2,那为什么PBKDF2后要加上HMAC呢?为什么使用了HMAC作为伪随机函数,却没有在参数中体现?
原来,这段代码使用了Python的一个库:cryptography.hazmat.primitives.kdf.pbkdf2
中的 PBKDF2HMAC
类。这个类用于实现 PBKDF2 密钥派生函数,其中 HMAC 作为其伪随机函数。
Springboot密钥生成
妈的,作者Springboot刚学,这个密钥改进花了一个多小时才完成(代码还是gpt写的),并且是在删除安全性检查依赖的基础上勉强完成的,以后一定会解决这个问题。身份认证是很重要的内容,作者不会因为它困难而不去实现,目标是请老师救救我!
下面是对Service服务层的代码处理:
密钥衍生 (Key Derivation)
在Python代码中,我们使用了PBKDF2HMAC来从密码中衍生出一个密钥。这是一个广泛应用于密码加密的技术,它通过多次对密码应用哈希函数来生成密钥。
在Java代码中:
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, 256);
SecretKey tmp = javax.crypto.SecretKeyFactory.getInstance(PBKDF2_ALGORITHM).generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), ALGORITHM);
这段代码做的事情和Python代码相同:使用PBKDF2WithHmacSHA256算法从给定的密码和salt中衍生出一个256位的密钥。
生成HMAC
HMAC (Hash-based Message Authentication Code) 是一个用于数据完整性和真实性验证的工具。简单地说,它可以用来确保加密后的数据在传输或存储过程中没有被篡改。
Python代码中:
h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend())
h.update(ciphertext)
tag = h.finalize()
这段代码计算了密文的HMAC值。
在Java代码中,这一步是这样完成的:
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(secretKey);
mac.update(encryptedBytes);
byte[] hmac = mac.doFinal();
我们使用了Java的Mac类来计算HMAC。上述代码段将加密后的字节数据传递给HMAC函数,并使用之前衍生出的密钥进行初始化,然后计算HMAC。便于后续进行完整性验证。
合并数据
为了便于存储和传输,通常会将多个数据片段(例如 salt, iv, hmac 和加密文本)合并成一个字节数组。
Python代码中,这部分没有显示如何做,但在Java代码中,我们使用了System.arraycopy
函数来完成这一步:
byte[] combined = new byte[salt.length + iv.length + hmac.length + encryptedBytes.length];
此数组长度的计算是基于所有组件(salt, iv, hmac, 加密文本)的长度。
HMAC 验证
当解密时,一个重要的步骤是验证HMAC。这确保了数据在传输或存储过程中没有被篡改。
在Java代码中:
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(secretKey);
mac.update(encryptedBytes);
byte[] computedHmac = mac.doFinal();
if (!java.util.Arrays.equals(hmac, computedHmac)) {
throw new IllegalArgumentException("Invalid HMAC, data might be tampered");
}
上述代码首先计算存储的加密数据的HMAC值,然后将其与传入的HMAC值进行比较。如果它们不匹配,这意味着数据可能已被篡改,因此会抛出一个异常。
完整代码演示
EnhancedEncryptionService
首先,Java中的javax.crypto
库并不直接提供PBKDF2的支持。但是,Java的安全包java.security
提供了PBKDF2的支持,我们可以使用这个。
以下是对先前的示例进行改进,使用PBKDF2进行密钥衍生,并添加HMAC验证来确保数据完整性:
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Base64;
@Service
public class EnhancedEncryptionService {
private static final String ALGORITHM = "AES";
private static final String MODE = "AES/CTR/NoPadding";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final String PBKDF2_ALGORITHM = "PBKDF2WithHmacSHA256";
private static final int SALT_LENGTH = 16;
private static final int ITERATIONS = 100000;
public String encrypt(String plainText, String password) throws Exception {
// 生成salt和密钥
SecureRandom secureRandom = new SecureRandom();
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
SecretKey secretKey = deriveKey(password, salt);
// 加密
byte[] iv = new byte[SALT_LENGTH];
secureRandom.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance(MODE);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
// 生成HMAC
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(secretKey);
mac.update(encryptedBytes);
byte[] hmac = mac.doFinal();
// 合并 salt, iv, hmac 和 加密文本 以便存储和后续解密
byte[] combined = new byte[salt.length + iv.length + hmac.length + encryptedBytes.length];
System.arraycopy(salt, 0, combined, 0, salt.length);
System.arraycopy(iv, 0, combined, salt.length, iv.length);
System.arraycopy(hmac, 0, combined, salt.length + iv.length, hmac.length);
System.arraycopy(encryptedBytes, 0, combined, salt.length + iv.length + hmac.length, encryptedBytes.length);
return Base64.getEncoder().encodeToString(combined);
}
public String decrypt(String encryptedText, String password) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedText);
// 从组合的数据中提取 salt, iv, hmac 和 加密的文本
byte[] salt = new byte[SALT_LENGTH];
byte[] iv = new byte[SALT_LENGTH];
byte[] hmac = new byte[32];
byte[] encryptedBytes = new byte[combined.length - SALT_LENGTH - SALT_LENGTH - hmac.length];
System.arraycopy(combined, 0, salt, 0, salt.length);
System.arraycopy(combined, salt.length, iv, 0, iv.length);
System.arraycopy(combined, salt.length + iv.length, hmac, 0, hmac.length);
System.arraycopy(combined, salt.length + iv.length + hmac.length, encryptedBytes, 0, encryptedBytes.length);
SecretKey secretKey = deriveKey(password, salt);
// 验证 HMAC
Mac mac = Mac.getInstance(HMAC_ALGORITHM);
mac.init(secretKey);
mac.update(encryptedBytes);
byte[] computedHmac = mac.doFinal();
if (!java.util.Arrays.equals(hmac, computedHmac)) {
throw new IllegalArgumentException("Invalid HMAC, data might be tampered");
}
// 解密
Cipher cipher = Cipher.getInstance(MODE);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
private SecretKey deriveKey(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, 256); // AES-256
SecretKey tmp = javax.crypto.SecretKeyFactory.getInstance(PBKDF2_ALGORITHM).generateSecret(spec);
return new SecretKeySpec(tmp.getEncoded(), ALGORITHM);
}
}
CryptoController
package com.example.dataencryption.controller;
import com.example.dataencryption.service.EnhancedEncryptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/encryption")
public class CryptoController {
private final EnhancedEncryptionService encryptionService;
@Autowired
public CryptoController(EnhancedEncryptionService encryptionService) {
this.encryptionService = encryptionService;
}
@PostMapping("/encrypt")
public EncryptResponse encrypt(@RequestBody EncryptRequest request) throws Exception {
String encryptedText = encryptionService.encrypt(request.getPlainText(), request.getPassword());
return new EncryptResponse(encryptedText);
}
@PostMapping("/decrypt")
public DecryptResponse decrypt(@RequestBody DecryptRequest request) throws Exception {
String decryptedText = encryptionService.decrypt(request.getEncryptedText(), request.getPassword());
return new DecryptResponse(decryptedText);
}
static class EncryptRequest {
private String plainText;
private String password;
// getters and setters
public String getPlainText() {
return plainText;
}
public void setPlainText(String plainText) {
this.plainText = plainText;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
static class DecryptRequest {
private String encryptedText;
private String password;
// getters and setters
public String getEncryptedText() {
return encryptedText;
}
public void setEncryptedText(String encryptedText) {
this.encryptedText = encryptedText;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
static class EncryptResponse {
private String encryptedText;
public EncryptResponse(String encryptedText) {
this.encryptedText = encryptedText;
}
// getter
public String getEncryptedText() {
return encryptedText;
}
}
static class DecryptResponse {
private String decryptedText;
public DecryptResponse(String decryptedText) {
this.decryptedText = decryptedText;
}
// getter
public String getDecryptedText() {
return decryptedText;
}
}
}
-
@RestController & @RequestMapping:标记这是一个RESTful Controller,处理的基本URL路径是
/encryption
。 -
加密和解密的请求方法:我为加密和解密操作创建了两个HTTP POST方法,因为我们需要向服务器发送数据。
-
Request & Response Classes:为了清晰地定义输入和输出格式,我创建了四个内部类:
EncryptRequest
,DecryptRequest
,EncryptResponse
, 和DecryptResponse
。这些类代表请求的内容和响应的格式。
测试操作
以下是如何使用Postman
来测试上述Spring Boot加密和解密Controller的步骤:
-
安装和启动 Postman:
- 如果你还没有安装, 可以从 Postman 的官方网站 下载并安装。
- 启动Postman。
-
配置请求:
- 在请求类型选择框中选择
POST
。 - 在URL输入框中输入您的Spring Boot应用的URL,如
http://localhost:8080/encryption/encrypt
(这假设你的应用运行在默认的8080端口上)。
- 在请求类型选择框中选择
-
设置请求体:
- 选择
Body
选项卡。 - 选择
raw
和JSON
。 - 在输入框中输入请求体。例如:
{ "plainText": "HELLO", "password": "yourpassword" }
- 选择
-
发送请求:
- 点击
Send
按钮发送请求。 - 在下方的响应部分,你应该看到返回的加密文本。
- 点击
-
测试解密:
- 修改URL为
http://localhost:8080/encryption/decrypt
。 - 在请求体中,将
plainText
换成encryptedText
并输入从上一步获得的加密文本。{ "encryptedText": "the_encrypted_text_here", "password": "yourpassword" }
- 点击
Send
,你应该看到原文 "HELLO" 作为响应。
- 修改URL为
-
查看响应:
- 下方会展示服务器的响应。你可以查看返回的数据、HTTP状态码、响应时间等信息。
作者使用的是Apipost7,挺好看的倒是,结果如下:
解密效果如下: