信息安全第三周+

啊,作者对上篇文章做出改进(其实是找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;
        }
    }
}
  1. @RestController & @RequestMapping:标记这是一个RESTful Controller,处理的基本URL路径是/encryption

  2. 加密和解密的请求方法:我为加密和解密操作创建了两个HTTP POST方法,因为我们需要向服务器发送数据。

  3. Request & Response Classes:为了清晰地定义输入和输出格式,我创建了四个内部类:EncryptRequest, DecryptRequest, EncryptResponse, 和 DecryptResponse。这些类代表请求的内容和响应的格式。

测试操作

以下是如何使用Postman来测试上述Spring Boot加密和解密Controller的步骤:

  1. 安装和启动 Postman:

  2. 配置请求:

    • 在请求类型选择框中选择 POST
    • 在URL输入框中输入您的Spring Boot应用的URL,如 http://localhost:8080/encryption/encrypt(这假设你的应用运行在默认的8080端口上)。
  3. 设置请求体:

    • 选择 Body 选项卡。
    • 选择 rawJSON
    • 在输入框中输入请求体。例如:
      {
        "plainText": "HELLO",
        "password": "yourpassword"
      }
      
  4. 发送请求:

    • 点击 Send 按钮发送请求。
    • 在下方的响应部分,你应该看到返回的加密文本。
  5. 测试解密:

    • 修改URL为 http://localhost:8080/encryption/decrypt
    • 在请求体中,将plainText换成encryptedText并输入从上一步获得的加密文本。
      {
        "encryptedText": "the_encrypted_text_here",
        "password": "yourpassword"
      }
      
    • 点击 Send,你应该看到原文 "HELLO" 作为响应。
  6. 查看响应:

    • 下方会展示服务器的响应。你可以查看返回的数据、HTTP状态码、响应时间等信息。

作者使用的是Apipost7,挺好看的倒是,结果如下:

解密效果如下:

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Joy T

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值