實驗二 電子公文傳輸系統安全-進展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();
}
}
密鑰更新:


本周計劃
- 等待驗收
- 繼續對密鑰算法進行完善
浙公網安備 33010602011771號