保护/加密 log4j 文件
Secure / Encrypt log4j files
我有问题; 安全性 要求我有 java swing 应用程序,该应用程序具有使用 log4j 生成的日志文件,用于跟踪情况下的支持问题
一个错误。
我必须加密/cypher/secure文件,这样客户端就无法打开它们并查看它们(至少不是人类可读的方式),同时支持技术团队拿走这些文件,他们将知道如何阅读(解密)它们。
我进行了大量搜索,并尝试了我发现的最佳选择,即通过扩展 SkeletonAppender
构建自定义附加程序。
现在知道我有 log4j 工作得很好,就像下面的配置一样,但是我创建了新的 class 来加密它,但即使通过简单的设置我也无法让它工作不要创建文件,所以我可以继续加密部分。
任何帮助,链接都很好。
工作...版本
<appender name="cache" class="com.MyAppender">
<param name="Threshold" value="ALL" />
<param name="ImmediateFlush" value="true" />
<param name="File" value="${home}/logs/cache.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ALL" />
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%-5p %d{MMM-dd-yyyy HH:mm:ss,SSS} %c{1} - %m%n" />
</layout>
</appender>
不工作...版本
<appender name="cache" class="com.MyAppender">
<param name="Threshold" value="ALL" />
<param name="ImmediateFlush" value="true" />
<param name="File" value="${home}/logs/cache.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ALL" />
<param name="Encoding" value="UTF-8" />
<rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
<param name="FileNamePattern"
value="${home}/logs/cache.%d{yyyy-MM-dd-HH}.gz" />
<param name="ActiveFileName" value="${home}/logs/cache.log" />
</rollingPolicy>
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern"
value="%-5p %d{MMM-dd-yyyy HH:mm:ss,SSS} %c{1} - %m%n" />
</layout>
</appender>
简单的class测试
package com.MyAppender;
import org.apache.log4j.spi.LoggingEvent;
public class MyAppender extends org.apache.log4j.RollingFileAppender {
private String file;
private boolean initialized = false;
private String baseFileName = null;
// private static final Log log = LogFactory.getLog(MyAppender.class);
/**
*
* write to ActivityLog
*
* @param event
* logging event invoked.
*
*/
@Override
protected void subAppend(LoggingEvent event) {
if (!initialized) {
createNewFile();
}
synchronized (this) {
super.subAppend(event);
}
}
/**
*
* create a new ActivityLog File
*
*/
public void createNewFile() {
try {
baseFileName = file;
super.setFile(baseFileName);
super.activateOptions();
initialized = true;
} catch (Exception e) {
// log.error("*#*Error in configuration of log4j params,unable to create ActivityLog file");
}
}
/**
*
* invokes File Appender's activateOptions() which controls the creation of
* log files.
*
*/
@Override
public void activateOptions() {
super.setFile(file);
super.activateOptions();
}
/**
*
* Close and rename the current ActivityLog file and reset counter and
* timestamp.
*
*/
public void rollOver() {
closeFile();
initialized = false;
}
@Override
public void setFile(String file) {
this.file = file;
}
}
然后我打算在
Cipher OutputStream
该问题的一个可能解决方法是将日志写入支持加密的嵌入式数据库,例如H2 natively supports encryption and SQLite 具有开源加密扩展 - 这样您就可以只使用 JDBCAppender
并让数据库负责加密,而不必担心自定义附加程序。
从 this question 开始,SQLite 配置看起来像
<appender name="jdbcAppender" class="org.apache.log4j.jdbc.JDBCAppender">
<param name="URL" value="jdbc:sqlite:D:/download/mapLogic/sf_log.db" />
<param name="user" value="" />
<param name="password" value="" />
<param name="driver" value="org.sqlite.JDBC" />
<param name="sql"
value="INSERT INTO Log(Message,Priority,Logger,Date) VALUES ('%m','%p','%c','%d{ABSOLUTE}')" />
</appender>
你的日志 table 看起来像
CREATE TABLE Log (
LogId INTEGER PRIMARY KEY,
Date DATETIME NOT NULL,
Level VARCHAR(50) NOT NULL,
Logger VARCHAR(255) NOT NULL,
Message TEXT DEFAULT NULL
);
可以找到关于 JDBCAppender
的文档 here
有一个 official encryption extension for SQLite as well as at least one 第三方开源扩展;我从来没有加密过 SQLite,但如果我必须这样做,那么我会使用官方扩展,除非我 运行 遇到问题。
如果您 运行 在客户端上使用此程序,那么理想情况下,您将能够在启动时让程序 phone 回家以获取数据库加密密钥,这样密钥就永远不会存在于客户端的磁盘驱动器上(忽略它进入交换文件的可能性)——客户端仍然可以使用调试器或其他任何东西来尝试从内存中获取密钥,但大概他们对解密日志没有足够的兴趣去那么多的麻烦。如果您必须将密钥存储在客户端,那么您至少可以在使用之前通过多次散列来混淆它,例如在程序中对 base_key 进行硬编码,然后在启动时通过 运行 base_key 通过 SHA512 (或其他)多次创建 actual_key ;客户仍然可以通过使用调试器弄清楚你在做什么,但他们希望再次不想惹麻烦。
我觉得你看起来像这样。虽然我不建议使用它。如果您将密钥与代码一起发送,则有很多反编译器可以反编译 jar/class 文件并获得 "key"。否则你应该使用 PKI 和所有..?我在这里没有研究那个选项。
扩展 RollingFileAppender class
public class EncryptedRollingFileAppender extends RollingFileAppender
因为 RollingFileAppender class 不是最终的 class 所以你可以这样做。
覆盖方法
public synchronized void setFile(String fileName, boolean append,
boolean bufferedIO, int bufferSize) throws IOException
像这样。
LogLog.debug("setFile called: " + fileName + ", " + append);
// It does not make sense to have immediate flush and bufferedIO.
if (bufferedIO) {
setImmediateFlush(false);
}
reset();
OutputStream ostream = null;
try {
//
// attempt to create file
//
// ostream = new FileOutputStream(fileName, append);
ostream = this.createEncryptedOutputStream(fileName, append);
} catch (FileNotFoundException ex) {
//
// if parent directory does not exist then
// attempt to create it and try to create file
// see bug 9150
//
String parentName = new File(fileName).getParent();
if (parentName != null) {
File parentDir = new File(parentName);
if (!parentDir.exists() && parentDir.mkdirs()) {
// ostream = new FileOutputStream(fileName, append);
try {
ostream = this.createEncryptedOutputStream(fileName, append);
} catch (Exception e) {
e.printStackTrace();
}
} else {
throw ex;
}
} else {
throw ex;
}
} catch (Exception e) {
throw new FileNotFoundException();
}
Writer fw = createWriter(ostream);
if (bufferedIO) {
fw = new BufferedWriter(fw, bufferSize);
}
this.setQWForFiles(fw);
this.fileName = fileName;
this.fileAppend = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
writeHeader();
LogLog.debug("setFile ended");
if (append) {
File f = new File(fileName);
((CountingQuietWriter) qw).setCount(f.length());
}
大部分代码是从基础 class 复制而来的。
加密私有方法应该是这样的
private OutputStream createEncryptedOutputStream(String filename, boolean append) throws FileNotFoundException,
NoSuchAlgorithmException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException {
CipherOutputStream cstream = null;
try {
byte[] keyBytes = "1234123412341234".getBytes(); //example
final byte[] ivBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; //example
final SecretKey key = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec IV = new IvParameterSpec(ivBytes);
final Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, IV);
cstream = new CipherOutputStream(new FileOutputStream(filename, append), cipher);
} catch (FileNotFoundException e) {
throw e;
}
return (cstream);
}
您不会失去 RollingFileAppender 的任何功能。您也可以对其他附加程序进行类似的编码。
免责声明:- 如果您正在使用这些密钥,请不要将其@production。同样,您要将密钥与罐子一起运送。一个聪明的黑客可以破解 easily.Fine 代码的调子,因为它是 'logging'。我没有测试过这段代码。
选项 1:使用自定义 SocketAppender
作为 Zim-Zam 关于使用支持 JDBC 的 appender 的答案的替代方案(顺便说一句,如果您走这条路,请记住启用安全传输),您还可以查看使用 SocketAppender
并推出您自己的加密方法。
选项 2:使用 Flume 和 FlumeAppender
参考log4j documentation on appenders,看看使用支持事件加密的FlumeAppender
:
示例 FlumeAppender 配置配置了主要代理和辅助代理,压缩正文,使用 RFC5424Layout 格式化正文,并将加密事件保存到磁盘。这个样本"compresses the body, formats the body using the RFC5424Layout, and persists encrypted events to disk:"
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<Flume name="eventLogger" compress="true" type="persistent" dataDir="./logData">
<Agent host="192.168.10.101" port="8800"/>
<RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
<Property name="keyProvider">MySecretProvider</Property>
</Flume>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="eventLogger"/>
</Root>
</Loggers>
</Configuration>
有趣的读物
这并没有直接回答你的问题,但也很有趣:Creating an encrypted log file
我有问题; 安全性 要求我有 java swing 应用程序,该应用程序具有使用 log4j 生成的日志文件,用于跟踪情况下的支持问题 一个错误。
我必须加密/cypher/secure文件,这样客户端就无法打开它们并查看它们(至少不是人类可读的方式),同时支持技术团队拿走这些文件,他们将知道如何阅读(解密)它们。
我进行了大量搜索,并尝试了我发现的最佳选择,即通过扩展 SkeletonAppender
构建自定义附加程序。
现在知道我有 log4j 工作得很好,就像下面的配置一样,但是我创建了新的 class 来加密它,但即使通过简单的设置我也无法让它工作不要创建文件,所以我可以继续加密部分。
任何帮助,链接都很好。
工作...版本
<appender name="cache" class="com.MyAppender">
<param name="Threshold" value="ALL" />
<param name="ImmediateFlush" value="true" />
<param name="File" value="${home}/logs/cache.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ALL" />
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern" value="%-5p %d{MMM-dd-yyyy HH:mm:ss,SSS} %c{1} - %m%n" />
</layout>
</appender>
不工作...版本
<appender name="cache" class="com.MyAppender">
<param name="Threshold" value="ALL" />
<param name="ImmediateFlush" value="true" />
<param name="File" value="${home}/logs/cache.log"/>
<param name="Append" value="true"/>
<param name="Threshold" value="ALL" />
<param name="Encoding" value="UTF-8" />
<rollingPolicy class="org.apache.log4j.rolling.TimeBasedRollingPolicy">
<param name="FileNamePattern"
value="${home}/logs/cache.%d{yyyy-MM-dd-HH}.gz" />
<param name="ActiveFileName" value="${home}/logs/cache.log" />
</rollingPolicy>
<layout class="org.apache.log4j.EnhancedPatternLayout">
<param name="ConversionPattern"
value="%-5p %d{MMM-dd-yyyy HH:mm:ss,SSS} %c{1} - %m%n" />
</layout>
</appender>
简单的class测试
package com.MyAppender;
import org.apache.log4j.spi.LoggingEvent;
public class MyAppender extends org.apache.log4j.RollingFileAppender {
private String file;
private boolean initialized = false;
private String baseFileName = null;
// private static final Log log = LogFactory.getLog(MyAppender.class);
/**
*
* write to ActivityLog
*
* @param event
* logging event invoked.
*
*/
@Override
protected void subAppend(LoggingEvent event) {
if (!initialized) {
createNewFile();
}
synchronized (this) {
super.subAppend(event);
}
}
/**
*
* create a new ActivityLog File
*
*/
public void createNewFile() {
try {
baseFileName = file;
super.setFile(baseFileName);
super.activateOptions();
initialized = true;
} catch (Exception e) {
// log.error("*#*Error in configuration of log4j params,unable to create ActivityLog file");
}
}
/**
*
* invokes File Appender's activateOptions() which controls the creation of
* log files.
*
*/
@Override
public void activateOptions() {
super.setFile(file);
super.activateOptions();
}
/**
*
* Close and rename the current ActivityLog file and reset counter and
* timestamp.
*
*/
public void rollOver() {
closeFile();
initialized = false;
}
@Override
public void setFile(String file) {
this.file = file;
}
}
然后我打算在 Cipher OutputStream
该问题的一个可能解决方法是将日志写入支持加密的嵌入式数据库,例如H2 natively supports encryption and SQLite 具有开源加密扩展 - 这样您就可以只使用 JDBCAppender
并让数据库负责加密,而不必担心自定义附加程序。
从 this question 开始,SQLite 配置看起来像
<appender name="jdbcAppender" class="org.apache.log4j.jdbc.JDBCAppender">
<param name="URL" value="jdbc:sqlite:D:/download/mapLogic/sf_log.db" />
<param name="user" value="" />
<param name="password" value="" />
<param name="driver" value="org.sqlite.JDBC" />
<param name="sql"
value="INSERT INTO Log(Message,Priority,Logger,Date) VALUES ('%m','%p','%c','%d{ABSOLUTE}')" />
</appender>
你的日志 table 看起来像
CREATE TABLE Log (
LogId INTEGER PRIMARY KEY,
Date DATETIME NOT NULL,
Level VARCHAR(50) NOT NULL,
Logger VARCHAR(255) NOT NULL,
Message TEXT DEFAULT NULL
);
可以找到关于 JDBCAppender
的文档 here
有一个 official encryption extension for SQLite as well as at least one 第三方开源扩展;我从来没有加密过 SQLite,但如果我必须这样做,那么我会使用官方扩展,除非我 运行 遇到问题。
如果您 运行 在客户端上使用此程序,那么理想情况下,您将能够在启动时让程序 phone 回家以获取数据库加密密钥,这样密钥就永远不会存在于客户端的磁盘驱动器上(忽略它进入交换文件的可能性)——客户端仍然可以使用调试器或其他任何东西来尝试从内存中获取密钥,但大概他们对解密日志没有足够的兴趣去那么多的麻烦。如果您必须将密钥存储在客户端,那么您至少可以在使用之前通过多次散列来混淆它,例如在程序中对 base_key 进行硬编码,然后在启动时通过 运行 base_key 通过 SHA512 (或其他)多次创建 actual_key ;客户仍然可以通过使用调试器弄清楚你在做什么,但他们希望再次不想惹麻烦。
我觉得你看起来像这样。虽然我不建议使用它。如果您将密钥与代码一起发送,则有很多反编译器可以反编译 jar/class 文件并获得 "key"。否则你应该使用 PKI 和所有..?我在这里没有研究那个选项。
扩展 RollingFileAppender class
public class EncryptedRollingFileAppender extends RollingFileAppender
因为 RollingFileAppender class 不是最终的 class 所以你可以这样做。
覆盖方法
public synchronized void setFile(String fileName, boolean append,
boolean bufferedIO, int bufferSize) throws IOException
像这样。
LogLog.debug("setFile called: " + fileName + ", " + append);
// It does not make sense to have immediate flush and bufferedIO.
if (bufferedIO) {
setImmediateFlush(false);
}
reset();
OutputStream ostream = null;
try {
//
// attempt to create file
//
// ostream = new FileOutputStream(fileName, append);
ostream = this.createEncryptedOutputStream(fileName, append);
} catch (FileNotFoundException ex) {
//
// if parent directory does not exist then
// attempt to create it and try to create file
// see bug 9150
//
String parentName = new File(fileName).getParent();
if (parentName != null) {
File parentDir = new File(parentName);
if (!parentDir.exists() && parentDir.mkdirs()) {
// ostream = new FileOutputStream(fileName, append);
try {
ostream = this.createEncryptedOutputStream(fileName, append);
} catch (Exception e) {
e.printStackTrace();
}
} else {
throw ex;
}
} else {
throw ex;
}
} catch (Exception e) {
throw new FileNotFoundException();
}
Writer fw = createWriter(ostream);
if (bufferedIO) {
fw = new BufferedWriter(fw, bufferSize);
}
this.setQWForFiles(fw);
this.fileName = fileName;
this.fileAppend = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
writeHeader();
LogLog.debug("setFile ended");
if (append) {
File f = new File(fileName);
((CountingQuietWriter) qw).setCount(f.length());
}
大部分代码是从基础 class 复制而来的。
加密私有方法应该是这样的
private OutputStream createEncryptedOutputStream(String filename, boolean append) throws FileNotFoundException,
NoSuchAlgorithmException,
NoSuchPaddingException,
InvalidKeyException,
InvalidAlgorithmParameterException {
CipherOutputStream cstream = null;
try {
byte[] keyBytes = "1234123412341234".getBytes(); //example
final byte[] ivBytes = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; //example
final SecretKey key = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec IV = new IvParameterSpec(ivBytes);
final Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, IV);
cstream = new CipherOutputStream(new FileOutputStream(filename, append), cipher);
} catch (FileNotFoundException e) {
throw e;
}
return (cstream);
}
您不会失去 RollingFileAppender 的任何功能。您也可以对其他附加程序进行类似的编码。
免责声明:- 如果您正在使用这些密钥,请不要将其@production。同样,您要将密钥与罐子一起运送。一个聪明的黑客可以破解 easily.Fine 代码的调子,因为它是 'logging'。我没有测试过这段代码。
选项 1:使用自定义 SocketAppender
作为 Zim-Zam 关于使用支持 JDBC 的 appender 的答案的替代方案(顺便说一句,如果您走这条路,请记住启用安全传输),您还可以查看使用 SocketAppender
并推出您自己的加密方法。
选项 2:使用 Flume 和 FlumeAppender
参考log4j documentation on appenders,看看使用支持事件加密的FlumeAppender
:
示例 FlumeAppender 配置配置了主要代理和辅助代理,压缩正文,使用 RFC5424Layout 格式化正文,并将加密事件保存到磁盘。这个样本"compresses the body, formats the body using the RFC5424Layout, and persists encrypted events to disk:"
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="warn" name="MyApp" packages="">
<Appenders>
<Flume name="eventLogger" compress="true" type="persistent" dataDir="./logData">
<Agent host="192.168.10.101" port="8800"/>
<RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
<Property name="keyProvider">MySecretProvider</Property>
</Flume>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="eventLogger"/>
</Root>
</Loggers>
</Configuration>
有趣的读物
这并没有直接回答你的问题,但也很有趣:Creating an encrypted log file