JSP网站文件上传AES加密及用户本地解密实现咨询
我来帮你梳理一下整个实现流程,从后端上传加密到用户本地解密,一步一步来:
实现JSP网站文件AES加密上传与本地解密方案
一、后端上传时的AES加密逻辑(JSP/Servlet环境)
首先咱们完善AES加密工具类,优先用AES-GCM模式——它自带完整性校验,比传统CBC模式更安全。同时要实现随机salt、iv生成,以及从密码推导密钥的逻辑,再结合Servlet处理文件上传流程。
1. 完整的AES文件加密工具类
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.security.SecureRandom; import java.security.spec.KeySpec; import java.util.Base64; public class AESFileEncryption { // AES-GCM核心参数:256位密钥,128位校验标签 private static final int AES_KEY_SIZE = 256; private static final int GCM_TAG_LENGTH = 128; // PBKDF2密钥推导参数:迭代次数、salt长度 private static final int PBKDF2_ITERATIONS = 65536; private static final int SALT_LENGTH = 16; // GCM推荐IV长度(96位=12字节) private static final int IV_LENGTH = 12; // 生成随机salt public static byte[] generateSalt() { byte[] salt = new byte[SALT_LENGTH]; new SecureRandom().nextBytes(salt); return salt; } // 生成随机IV public static byte[] generateIV() { byte[] iv = new byte[IV_LENGTH]; new SecureRandom().nextBytes(iv); return iv; } // 从密码+salt推导AES密钥(用PBKDF2增强安全性) public static SecretKey generateKey(String password, byte[] salt) throws Exception { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, AES_KEY_SIZE); SecretKey tmp = factory.generateSecret(spec); return new SecretKeySpec(tmp.getEncoded(), "AES"); } // 加密文件:输入原文件路径、输出加密文件路径、密码、salt、iv public static void encryptFile(String inputFilePath, String outputFilePath, String password, byte[] salt, byte[] iv) throws Exception { SecretKey key = generateKey(password, salt); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.ENCRYPT_MODE, key, spec); try (FileInputStream in = new FileInputStream(inputFilePath); FileOutputStream out = new FileOutputStream(outputFilePath)) { // 分批读取原文件并加密写入 byte[] buffer = new byte[8192]; int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { byte[] encrypted = cipher.update(buffer, 0, bytesRead); if (encrypted != null) out.write(encrypted); } // 写入GCM校验标签 byte[] tag = cipher.doFinal(); out.write(tag); } } }
2. 处理文件上传的Servlet
这个Servlet负责接收用户上传的文件、生成加密所需的密码/salt/iv、加密文件后存储,最后把密钥信息发邮件给用户:
import javax.servlet.ServletException; import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Part; import java.io.File; import java.io.IOException; import java.util.Base64; import java.util.UUID; @WebServlet("/upload") @MultipartConfig(fileSizeThreshold = 1024 * 1024 * 2, // 2MB缓存阈值 maxFileSize = 1024 * 1024 * 50, // 单文件最大50MB maxRequestSize = 1024 * 1024 * 100) // 请求总大小最大100MB public class FileUploadServlet extends HttpServlet { // 服务器存储加密文件的目录(需确保有读写权限) private static final String UPLOAD_DIR = "encrypted_files"; @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String uploadPath = getServletContext().getRealPath("") + File.separator + UPLOAD_DIR; File uploadDir = new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdir(); try { // 获取上传文件和用户邮箱 Part filePart = request.getPart("file"); String fileName = filePart.getSubmittedFileName(); String userEmail = request.getParameter("email"); // 临时存储原文件 String tempFilePath = uploadPath + File.separator + "temp_" + fileName; filePart.write(tempFilePath); // 生成加密所需的随机信息 String password = UUID.randomUUID().toString().substring(0, 8); // 生成8位随机密码 byte[] salt = AESFileEncryption.generateSalt(); byte[] iv = AESFileEncryption.generateIV(); // 生成加密后文件名(避免重复) String encryptedFileName = UUID.randomUUID().toString() + "_encrypted"; String encryptedFilePath = uploadPath + File.separator + encryptedFileName; // 执行加密并删除临时原文件 AESFileEncryption.encryptFile(tempFilePath, encryptedFilePath, password, salt, iv); new File(tempFilePath).delete(); // 发送加密信息到用户邮箱 sendEncryptionEmail(userEmail, password, Base64.getEncoder().encodeToString(salt), Base64.getEncoder().encodeToString(iv), encryptedFileName); // 返回成功提示 response.getWriter().println("文件上传加密成功!密码、Salt和IV已发送至你的邮箱,请妥善保存!"); } catch (Exception e) { e.printStackTrace(); response.getWriter().println("上传加密失败:" + e.getMessage()); } } // 邮件发送方法(需配置你的SMTP服务器信息) private void sendEncryptionEmail(String toEmail, String password, String saltBase64, String ivBase64, String encryptedFileName) throws Exception { String fromEmail = "你的邮箱@example.com"; String emailPwd = "你的邮箱授权码"; String smtpHost = "smtp.example.com"; int smtpPort = 587; javax.mail.Session session = javax.mail.Session.getInstance(System.getProperties(), new javax.mail.Authenticator() { protected javax.mail.PasswordAuthentication getPasswordAuthentication() { return new javax.mail.PasswordAuthentication(fromEmail, emailPwd); } }); try (javax.mail.Message message = new javax.mail.internet.MimeMessage(session)) { message.setFrom(new javax.mail.internet.InternetAddress(fromEmail)); message.setRecipients(javax.mail.Message.RecipientType.TO, javax.mail.internet.InternetAddress.parse(toEmail)); message.setSubject("你的文件加密密钥信息"); String content = "您好,你的文件已完成加密上传!\n\n" + "解密所需信息:\n" + "密码:" + password + "\n" + "Salt(Base64格式):" + saltBase64 + "\n" + "IV(Base64格式):" + ivBase64 + "\n" + "加密文件下载链接:http://你的域名.com/download?file=" + encryptedFileName; message.setText(content); javax.mail.Transport.send(message); } } }
3. 上传表单JSP页面
写一个简单的前端页面让用户上传文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件加密上传</title> </head> <body> <h2>上传文件并加密</h2> <form action="upload" method="post" enctype="multipart/form-data"> <div> <label>接收密钥的邮箱:</label> <input type="email" name="email" required> </div> <div style="margin: 15px 0;"> <label>选择要上传的文件:</label> <input type="file" name="file" required> </div> <button type="submit">上传并加密</button> </form> </body> </html>
二、用户本地的AES解密逻辑
用户下载加密文件后,需要用本地工具解密。咱们写一个Java类,用户可以直接运行(或打包成jar包),输入邮件里的密钥信息即可解密。
本地解密工具类
import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; import java.io.*; import java.security.spec.KeySpec; import java.util.Base64; public class AESFileDecryption { private static final int AES_KEY_SIZE = 256; private static final int GCM_TAG_LENGTH = 128; private static final int PBKDF2_ITERATIONS = 65536; // 从密码+Base64格式的salt推导密钥 public static SecretKey generateKey(String password, String saltBase64) throws Exception { byte[] salt = Base64.getDecoder().decode(saltBase64); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, PBKDF2_ITERATIONS, AES_KEY_SIZE); SecretKey tmp = factory.generateSecret(spec); return new SecretKeySpec(tmp.getEncoded(), "AES"); } // 解密文件:输入加密文件路径、输出原文件路径、密码、saltBase64、ivBase64 public static void decryptFile(String encryptedFilePath, String outputFilePath, String password, String saltBase64, String ivBase64) throws Exception { SecretKey key = generateKey(password, saltBase64); byte[] iv = Base64.getDecoder().decode(ivBase64); Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv); cipher.init(Cipher.DECRYPT_MODE, key, spec); try (FileInputStream in = new FileInputStream(encryptedFilePath); FileOutputStream out = new FileOutputStream(outputFilePath)) { byte[] encryptedData = in.readAllBytes(); byte[] decryptedData = cipher.doFinal(encryptedData); out.write(decryptedData); } } // 命令行运行入口:java AESFileDecryption <加密文件路径> <输出文件路径> <密码> <Salt(Base64)> <IV(Base64)> public static void main(String[] args) { if (args.length != 5) { System.out.println("使用方法:java AESFileDecryption <加密文件路径> <输出文件路径> <密码> <Salt(Base64)> <IV(Base64)>"); return; } try { decryptFile(args[0], args[1], args[2], args[3], args[4]); System.out.println("解密成功!原文件已保存至:" + args[1]); } catch (Exception e) { System.err.println("解密失败:" + e.getMessage()); e.printStackTrace(); } } }
三、关键注意事项
- 安全性:
- 永远不要在服务器存储密码、salt、iv,这些信息只发送给用户,确保只有用户能解密文件。
- 密码要随机生成,建议长度不少于8位,包含混合字符。
- 每次上传都要生成新的salt和iv,绝对不能固定。
- 环境配置:
- JDK8及以下版本需要安装JCE无限强度权限政策文件,才能使用256位AES密钥;JDK9+默认支持。
- 确保服务器上传目录有读写权限,加密文件做好备份。
- 用户体验:
- 可以把解密工具打包成可执行jar包,用户无需安装JDK就能运行。
- 邮件里附上解密工具下载链接和详细操作步骤。
内容的提问来源于stack exchange,提问作者Shubhu Gholu




