实验二 电子公文传输系统安全-进展2
任务详情
- 上周任务完成情况(代码链接,所写文档等)
- 本周计划
上周任务完成情况
package cn.edu.nuc.article.util;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
public class SM3SaltHelper {
static {
// 在类加载时添加BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
}
public static void main(String[] args) {
// 原始数据
byte[] data = "Hello, World!".getBytes();
// 生成随机的盐值
byte[] salt = generateSalt();
// 将原始数据与盐值拼接
byte[] dataWithSalt = concatBytes(data, salt);
// 计算SM3哈希值
byte[] hash = calculateHash(dataWithSalt);
// 将盐值和哈希值转换为十六进制字符串
String saltHex = bytesToHex(salt);
String hashHex = bytesToHex(hash);
System.out.println("Salt: " + saltHex);
System.out.println("Hash: " + hashHex);
}
public static String encrypt(String paramStr, byte[] salt) {
// 原始数据
byte[] data = paramStr.getBytes();
// 将原始数据与盐值拼接
byte[] dataWithSalt = concatBytes(data, salt);
// 计算SM3哈希值
byte[] hash = calculateHash(dataWithSalt);
// 将哈希值转换为十六进制字符串
return bytesToHex(hash);
}
public static String entryptSM3Password(String plainPassword) {
byte[] bytesSalt = generateSalt();
String sm3Password = encrypt(plainPassword, bytesSalt);
return bytesToHex(bytesSalt) + sm3Password;
}
public static byte[] generateSalt() {
byte[] salt = new byte[16]; // 使用16字节的盐值
new SecureRandom().nextBytes(salt);
return salt;
}
private static byte[] concatBytes(byte[] a, byte[] b) {
byte[] result = Arrays.copyOf(a, a.length + b.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
private static byte[] calculateHash(byte[] input) {
SM3Digest digest = new SM3Digest();
digest.update(input, 0, input.length);
byte[] result = new byte[digest.getDigestSize()];
digest.doFinal(result, 0);
return result;
}
public static String bytesToHex(byte[] bytes) {
return Hex.toHexString(bytes);
}
public static byte[] HexTobytes(String hexStr) {
return Hex.decode(hexStr);
}
}
用户数据库如下:
同时我还设置了定期更新用户密码,以达到定期更新用户盐值的效果,我个人测试需要,故每个用户更新的密码都设置为相同,在实际应用过程中,应当注意区分
@Component
public static class PasswordUpdateTask {
private final Logger logger = LoggerFactory.getLogger(PasswordUpdateTask.class);
@Autowired
private UserService userService;
@Value("${user.default.password}")//此处需注意不同用户应当根据其用户名等信息生成密码,并进行保存
private String defaultPassword;
@Scheduled(fixedRate = 10000) // 每10秒执行一次,单位为毫秒,可修改,实际工作中应当6个月为一个周期
public void updatePasswords() {
List<User> users = userService.findAllUsers();
for (User user : users) {
byte[] salt = SM3SaltHelper.generateSalt();
String encryptedPassword = SM3SaltHelper.encrypt(defaultPassword, salt);
user.setPassword(encryptedPassword);
user.setSalt(SM3SaltHelper.bytesToHex(salt));
boolean isUpdated = userService.updateUser1(user);
if (isUpdated) {
logger.info("用户 {} 密码成功更新。新密码: {}", user.getLoginname(), encryptedPassword);
} else {
logger.error("更新用户 {} 失败.", user.getLoginname());
}
}
}
}
- SM4
采用sm4对存储的公文进行加密处理,密钥随机生成,乱序存储在数据库中。其中sm4采用cbc模式,iv随机生成,跟随密文一起存储。解密的时候读取密文并分离密文和iv,然后解密。
package cn.edu.nuc.article.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class SM4Tools {
private static final String name = "SM4"; // 算法名字
private static final String transformation = "SM4/CBC/PKCS5Padding"; // 加密模式以及短块填充方式
// private static final String Default_iv="0123456789abcdef"; // 加密使用的初始向量
/**
* 加载指定文件,对其进行加密,并将加密结果写入指定输出文件中
* @param inputFile 要加密的输入文件路径
* @param outputFile 加密后的输出文件路径
* @param key 加密所需的密钥
* @throws Exception 如果文件读取、加密或写入时出现错误,则抛出异常
*/
public static void encodeFile(String inputFile, String outputFile, String key) throws Exception {
byte[] inputBytes = Files.readAllBytes(Paths.get(inputFile));
byte[] encodeByte = encode(inputBytes, key.getBytes(StandardCharsets.UTF_8));
Files.write(Paths.get(outputFile), encodeByte);
System.out.println("File encoded successfully.");
}
/**
* 使用指定的加密算法和密钥对给定的字节数组进行加密
* @param inputByte 要加密的字节数组
* @param key 加密所需的密钥
* @return 加密后的字节数组
* @throws Exception 如果加密时发生错误,则抛出异常
*/
public static byte[] encode(byte[] inputByte, byte[] key) throws Exception {
Cipher c = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, name);
// 生成随机IV
byte[] iv = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
c.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
// 加密数据
byte[] encryptedData = c.doFinal(inputByte);
// 将IV和密文一起返回
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
return combined;
}
public static void decodeFile(String inputFilePath, String outputFilePath, String key) throws Exception {
byte[] inputBytes = Files.readAllBytes(Paths.get(inputFilePath));
byte[] decodeBytes = decode(inputBytes, key.getBytes(StandardCharsets.UTF_8));
Files.write(Paths.get(outputFilePath), decodeBytes);
System.out.println("File decode successfully.");
}
public static byte[] decode(byte[] inputBytes, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
SecretKeySpec secretKeySpec = new SecretKeySpec(key, name);
// 从输入数据中提取IV和密文
byte[] iv = Arrays.copyOfRange(inputBytes, 0, 16);
byte[] encryptedData = Arrays.copyOfRange(inputBytes, 16, inputBytes.length);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
return cipher.doFinal(encryptedData);
}
public static String generateRandomString(int length) {
Random random = new Random();
StringBuffer string = new StringBuffer();
for (int i = 0; i < length; i++) {
int randomChar = random.nextInt(91);
if (randomChar >= 48 && randomChar <= 57 ||
randomChar >= 65 && randomChar <= 90 ||
randomChar >= 97 && randomChar <= 122) {
string.append((char) randomChar);
} else {
i--;
}
}
return string.toString();
}
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
String inputFile = "D:\\data\\test01.docx";
String enFile = "D:\\data\\test01Encode.docx";
String deFile = "D:\\data\\test01Decode.docx";
String key = generateRandomString(16);
System.out.println(key);
encodeFile(inputFile, enFile, key);
decodeFile(enFile, deFile, key);
}
}
上传后的公文加密存储
同时我还设置了定期更新密钥,代码如下:
//密钥定期更新
@Component
public static class KeyManagementTask {
@Autowired
private AttachmentService attachmentService;
@Scheduled(fixedRate = 259200000) //259200000// 每3天执行一次,单位为毫秒
public void updateKeys() {
try {
// 2. 获取所有已加密的文件
List<Attachment> attachments = attachmentService.findAllEncryptedAttachments();
for (Attachment attachment : attachments) {
String oldKey = new StringBuffer(attachment.getEncodekey()).reverse().toString();
System.out.println(oldKey);
String filePath = Constants.PROJECT_ROOT_DIRECTORY + attachment.getFileid();
// 3. 使用旧密钥解密文件
byte[] encryptedData = Files.readAllBytes(Paths.get(filePath));
byte[] decryptedData = SM4Tools.decode(encryptedData, oldKey.getBytes(StandardCharsets.UTF_8));
// 1. 生成新密钥
String newKey = SM4Tools.generateRandomString(16);
// 4. 使用新密钥重新加密文件
byte[] reEncryptedData = SM4Tools.encode(decryptedData, newKey.getBytes(StandardCharsets.UTF_8));
Files.write(Paths.get(filePath), reEncryptedData);
// 5. 更新数据库中的密钥信息
attachment.setEncodekey(new StringBuffer(newKey).reverse().toString());
attachmentService.updateAttachment(attachment);
// 6. 记录密钥更新日志
logKeyUpdate(attachment.getAttachmentid(), oldKey, newKey);
}
// 7. 销毁旧密钥(这里假设旧密钥已经不再被使用)
// 具体销毁方式根据实际需求实现
} catch (Exception e) {
e.printStackTrace();
}
}
密钥更新:
本周计划
- 等待验收
- 继续对密钥算法进行完善