Java对日志文件进行加密

·
2025-11-01 17:22:42

最近碰到了一个新的需求,生产环境中Java程序部署的服务器会定期清理数据,需要将保存在程序所在服务器上的日志文件挂载到网盘上,但又不想让用户看到日志文件中的信息,因此需要对日志文件中的内容进行加密。

这里,并不是对日志文件中的敏感信息进行加密,而是对所有数据都进行加密。上网查了一圈资料之后,最终到了解决方案:自定义Appender,使用AES进行加密。下面贴出具体代码。

AES加密解密工具类

package com.lg.coding.util;

import java.io.*;

import java.security.Key;

import javax.crypto.Cipher;

import javax.crypto.CipherInputStream;

import javax.crypto.spec.IvParameterSpec;

import javax.crypto.spec.SecretKeySpec;

import java.util.Base64;

public class AESUtil {

private static final String ALGORITHM = "AES";

private static int offset = 16;

private static final String transformation = "AES/CBC/PKCS5Padding";

/**

* AES加密字符串

* @param password 密钥

* @param value 待加密字符串

*/

public static String encrypt(String password, String value) {

try {

Key key = generateKey(password);

//创建初始向量iv用于指定密钥偏移量

IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);

Cipher cipher = Cipher.getInstance(transformation);

cipher.init(Cipher.ENCRYPT_MODE, key, iv);

byte[] encryptedByteValue = cipher.doFinal(value.getBytes("utf-8"));

String encryptedValue64 = Base64.getEncoder().encodeToString(encryptedByteValue);

return encryptedValue64;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

/**

* AES解密字符串

* @param password 密钥

* @param value 待解密字符串

* @return

*/

public static String decrypt(String password, String value) {

try {

Key key = generateKey(password);

//创建初始向量iv用于指定密钥偏移量

IvParameterSpec iv = new IvParameterSpec(password.getBytes(), 0, offset);

Cipher cipher = Cipher.getInstance(transformation);

cipher.init(Cipher.DECRYPT_MODE, key, iv);

byte[] decryptedValue64 = Base64.getDecoder().decode(value);

byte[] decryptedByteValue = cipher.doFinal(decryptedValue64);

String decryptedValue = new String(decryptedByteValue,"utf-8");

return decryptedValue;

} catch (Exception e) {

e.printStackTrace();

}

return null;

}

/**

* AES解密文件

* @param password 密钥

* @param inputFilePath 待解密文件路径

* @param outputFilePath 输出文件路径

*/

public static void decryptFile(String password, String inputFilePath, String outputFilePath) {

InputStream inputStream = null;

BufferedReader bufferedReader = null;

BufferedWriter bufferedWriter = null;

try {

inputStream = new FileInputStream(inputFilePath);

bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

bufferedWriter = new BufferedWriter(new FileWriter(outputFilePath));

String s;

while ((s = bufferedReader.readLine()) != null) {

bufferedWriter.write(decrypt(password, s));

bufferedWriter.newLine();

bufferedWriter.flush();

}

} catch (FileNotFoundException e) {

System.out.println("找不到指定文件!");

e.printStackTrace();

} catch (IOException e) {

System.out.println("文件读取错误!");

e.printStackTrace();

} finally {

try {

inputStream.close();

bufferedReader.close();

bufferedWriter.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

/**

* 生成key

* @param password

* @return

* @throws Exception

*/

private static Key generateKey(String password) {

Key key = new SecretKeySpec(password.getBytes(),ALGORITHM);

return key;

}

}

在这里,加密操作和解密操作都是针对字符串进行的,在自定义Appender类中,重写subAppend方法,在执行输出文件操作之前对内容进行字符串加密;解密时,逐行读取文件内容后再进行字符串解密。

自定义Appender类

package com.lg.coding.util;

import ch.qos.logback.core.FileAppender;

import ch.qos.logback.core.Layout;

import ch.qos.logback.core.rolling.RollingPolicy;

import ch.qos.logback.core.rolling.RollingPolicyBase;

import ch.qos.logback.core.rolling.RolloverFailure;

import ch.qos.logback.core.rolling.TriggeringPolicy;

import ch.qos.logback.core.rolling.helper.CompressionMode;

import ch.qos.logback.core.rolling.helper.FileNamePattern;

import ch.qos.logback.core.spi.DeferredProcessingAware;

import ch.qos.logback.core.status.ErrorStatus;

import ch.qos.logback.core.util.ContextUtil;

import org.slf4j.event.LoggingEvent;

import java.io.File;

import java.io.IOException;

import java.util.Iterator;

import java.util.Map;

public class CustomRollingFileAppender extends FileAppender {

File currentlyActiveFile;

TriggeringPolicy triggeringPolicy;

RollingPolicy rollingPolicy;

private static String RFA_NO_TP_URL = "http://logback.qos.ch/codes.html#rfa_no_tp";

private static String RFA_NO_RP_URL = "http://logback.qos.ch/codes.html#rfa_no_rp";

private static String COLLISION_URL = "http://logback.qos.ch/codes.html#rfa_collision";

private static String RFA_LATE_FILE_URL = "http://logback.qos.ch/codes.html#rfa_file_after";

public CustomRollingFileAppender() {

}

public void start() {

if (this.triggeringPolicy == null) {

this.addWarn("No TriggeringPolicy was set for the RollingFileAppender named " + this.getName());

this.addWarn("For more information, please visit " + RFA_NO_TP_URL);

} else if (!this.triggeringPolicy.isStarted()) {

this.addWarn("TriggeringPolicy has not started. RollingFileAppender will not start");

} /*else if (this.checkForCollisionsInPreviousRollingFileAppenders()) {

this.addError("Collisions detected with FileAppender/RollingAppender instances defined earlier. Aborting.");

this.addError("For more information, please visit " + COLLISION_WITH_EARLIER_APPENDER_URL);

}*/ else {

if (!this.append) {

this.addWarn("Append mode is mandatory for RollingFileAppender. Defaulting to append=true.");

this.append = true;

}

if (this.rollingPolicy == null) {

this.addError("No RollingPolicy was set for the RollingFileAppender named " + this.getName());

this.addError("For more information, please visit " + RFA_NO_RP_URL);

} /*else if (this.checkForFileAndPatternCollisions()) {

this.addError("File property collides with fileNamePattern. Aborting.");

this.addError("For more information, please visit " + COLLISION_URL);

}*/ else {

if (this.isPrudent()) {

if (this.rawFileProperty() != null) {

this.addWarn("Setting \"File\" property to null on account of prudent mode");

this.setFile((String)null);

}

if (this.rollingPolicy.getCompressionMode() != CompressionMode.NONE) {

this.addError("Compression is not supported in prudent mode. Aborting");

return;

}

}

this.currentlyActiveFile = new File(this.getFile());

this.addInfo("Active log file name: " + this.getFile());

super.start();

}

}

}

/*private boolean checkForFileAndPatternCollisions() {

if (this.triggeringPolicy instanceof RollingPolicyBase) {

RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;

FileNamePattern fileNamePattern = base.fileNamePattern;

if (fileNamePattern != null && this.fileName != null) {

String regex = fileNamePattern.toRegex();

return this.fileName.matches(regex);

}

}

return false;

}

private boolean checkForCollisionsInPreviousRollingFileAppenders() {

boolean collisionResult = false;

if (this.triggeringPolicy instanceof RollingPolicyBase) {

RollingPolicyBase base = (RollingPolicyBase)this.triggeringPolicy;

FileNamePattern fileNamePattern = base.fileNamePattern;

boolean collisionsDetected = this.innerCheckForFileNamePatternCollisionInPreviousRFA(fileNamePattern);

if (collisionsDetected) {

collisionResult = true;

}

}

return collisionResult;

}*/

private boolean innerCheckForFileNamePatternCollisionInPreviousRFA(FileNamePattern fileNamePattern) {

boolean collisionsDetected = false;

Map map = (Map)this.context.getObject("RFA_FILENAME_PATTERN_COLLISION_MAP");

if (map == null) {

return collisionsDetected;

} else {

Iterator var4 = map.entrySet().iterator();

while(var4.hasNext()) {

Map.Entry entry = (Map.Entry)var4.next();

if (fileNamePattern.equals(entry.getValue())) {

this.addErrorForCollision("FileNamePattern", ((FileNamePattern)entry.getValue()).toString(), (String)entry.getKey());

collisionsDetected = true;

}

}

if (this.name != null) {

map.put(this.getName(), fileNamePattern);

}

return collisionsDetected;

}

}

public void stop() {

super.stop();

if (this.rollingPolicy != null) {

this.rollingPolicy.stop();

}

if (this.triggeringPolicy != null) {

this.triggeringPolicy.stop();

}

Map map = ContextUtil.getFilenamePatternCollisionMap(this.context);

if (map != null && this.getName() != null) {

map.remove(this.getName());

}

}

public void setFile(String file) {

if (file != null && (this.triggeringPolicy != null || this.rollingPolicy != null)) {

this.addError("File property must be set before any triggeringPolicy or rollingPolicy properties");

this.addError("For more information, please visit " + RFA_LATE_FILE_URL);

}

super.setFile(file);

}

public String getFile() {

return this.rollingPolicy.getActiveFileName();

}

public void rollover() {

this.lock.lock();

try {

this.closeOutputStream();

this.attemptRollover();

this.attemptOpenFile();

} finally {

this.lock.unlock();

}

}

private void attemptOpenFile() {

try {

this.currentlyActiveFile = new File(this.rollingPolicy.getActiveFileName());

this.openFile(this.rollingPolicy.getActiveFileName());

} catch (IOException var2) {

this.addError("setFile(" + this.fileName + ", false) call failed.", var2);

}

}

private void attemptRollover() {

try {

this.rollingPolicy.rollover();

} catch (RolloverFailure var2) {

this.addWarn("RolloverFailure occurred. Deferring roll-over.");

this.append = true;

}

}

protected void subAppend(E event) {

if (this.isStarted()) {

try {

if (event instanceof DeferredProcessingAware) {

((DeferredProcessingAware)event).prepareForDeferredProcessing();

}

byte[] byteArray = this.encoder.encode(event);

//加密前数据

String originalString = new String(byteArray, "UTF-8");

//加密后数据

String encryptedString = AESUtil.encrypt("Sanyuan123456789", originalString);

this.writeBytes((encryptedString + "\n").getBytes());

} catch (IOException var3) {

this.started = false;

this.addStatus(new ErrorStatus("IO failure in appender", this, var3));

}

}

}

private void writeBytes(byte[] byteArray) throws IOException {

if (byteArray != null && byteArray.length != 0) {

this.lock.lock();

try {

this.getOutputStream().write(byteArray);

if (this.isImmediateFlush()) {

this.getOutputStream().flush();

}

} finally {

this.lock.unlock();

}

}

}

public RollingPolicy getRollingPolicy() {

return this.rollingPolicy;

}

public TriggeringPolicy getTriggeringPolicy() {

return this.triggeringPolicy;

}

public void setRollingPolicy(RollingPolicy policy) {

this.rollingPolicy = policy;

if (this.rollingPolicy instanceof TriggeringPolicy) {

this.triggeringPolicy = (TriggeringPolicy)policy;

}

}

public void setTriggeringPolicy(TriggeringPolicy policy) {

this.triggeringPolicy = policy;

if (policy instanceof RollingPolicy) {

this.rollingPolicy = (RollingPolicy)policy;

}

}

}

这里是直接复制RollingFileAppender类的代码,对subAppend方法进行重写,在调用writeBytes()方法之前进行加密操作,将加密后的数据输出到本地。

本系统用的日志框架为SpringBoot内置的日志处理框架Logback。将logback-spring.xml文件中系统日志输出对应的Appender标签,class属性改为自定义Appender类的全路径:

utf8

${log.pattern}

${log.pattern}

${log.path}/coding-info.log

${log.path}/coding-info.%d{yyyy-MM-dd}.%i.log

60

10MB

${log.pattern}

utf8

ACCEPT

DENY

INFO

${log.path}/coding-error.log

coding/.%d{yyyy-MM-dd-HH:mm:ss}.gz

${log.path}/coding-error.%d{yyyy-MM-dd-HH}.log

60

${log.pattern}

utf8

ERROR

ACCEPT

DENY

${log.path}/coding-debug.log

${log.path}/coding-debug.%d{yyyy-MM-dd}.%i.log

60

10MB

${log.pattern}

utf8

ACCEPT

DENY

DEBUG

${log.path}/sys-user.log

${log.path}/sys-user.%d{yyyy-MM-dd}.log

60

${log.pattern}

此时,启动项目,查看系统本地日志文件:

可以看到,日志文件的内容已成功加密。

再写一个文件解密接口:

@ApiOperation(value = "aes文件解密测试")

@PostMapping("/aesFileDecrypteTest")

public void aesFileDecrypteTest(String fileInputPath, String fileOutputPath) {

String key = "Sanyuan123456789";

AESUtil.decryptFile(key, fileInputPath, fileOutputPath);

}

其中,fileInputPath和fileOutputPath两个参数分别为待解密文件所在路径和解密后的文件所在路径,接口调用中输入对应参数:

调用接口,可以看到,指定路径下生成了一个名为“解密日志.log”的文件, 打开文件,查看内容:

可以看到,内容已被成功解密。