赞
踩
啊,作者对上篇文章做出改进(其实是找gpt做的改进),于本篇记录一下。
首先引入了一个随机生成的盐(salt
)。盐是用来确保即使两次使用相同的密码,通过密钥派生函数(如PBKDF2)生成的密钥也会是不同的。主要是服务下面的PBKDF2密钥派生函数,让函数生成的密钥变化!
salt = os.urandom(16)
使用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次迭代。这样产生的密钥不仅与所提供的密码相关,还与盐值相关,增加了密钥的复杂性和随机性。
使用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}")
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- 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刚学,这个密钥改进花了一个多小时才完成(代码还是gpt写的),并且是在删除安全性检查依赖的基础上勉强完成的,以后一定会解决这个问题。身份认证是很重要的内容,作者不会因为它困难而不去实现,目标是请老师救救我!
下面是对Service服务层的代码处理:
在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 (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。这确保了数据在传输或存储过程中没有被篡改。
在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值进行比较。如果它们不匹配,这意味着数据可能已被篡改,因此会抛出一个异常。
首先,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);
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- 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;
- }
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
@RestController & @RequestMapping:标记这是一个RESTful Controller,处理的基本URL路径是/encryption
。
加密和解密的请求方法:我为加密和解密操作创建了两个HTTP POST方法,因为我们需要向服务器发送数据。
Request & Response Classes:为了清晰地定义输入和输出格式,我创建了四个内部类:EncryptRequest
, DecryptRequest
, EncryptResponse
, 和 DecryptResponse
。这些类代表请求的内容和响应的格式。
以下是如何使用Postman
来测试上述Spring Boot加密和解密Controller的步骤:
安装和启动 Postman:
配置请求:
POST
。http://localhost:8080/encryption/encrypt
(这假设你的应用运行在默认的8080端口上)。设置请求体:
Body
选项卡。raw
和 JSON
。- {
- "plainText": "HELLO",
- "password": "yourpassword"
- }
发送请求:
Send
按钮发送请求。测试解密:
http://localhost:8080/encryption/decrypt
。plainText
换成encryptedText
并输入从上一步获得的加密文本。 - {
- "encryptedText": "the_encrypted_text_here",
- "password": "yourpassword"
- }
Send
,你应该看到原文 "HELLO" 作为响应。查看响应:
作者使用的是Apipost7,挺好看的倒是,结果如下:
解密效果如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。