[java] SLF4J와 Log4j2의 추가 로그 파일 암호화

보안은 모든 소프트웨어 시스템에서 매우 중요한 요소입니다. 특히 로그 파일에는 보호해야 할 중요한 정보가 포함될 수 있습니다. SLF4J와 Log4j2를 사용하여 암호화 된 로그 파일을 생성하는 방법을 알아보겠습니다.

1. 의존성 추가

먼저, 프로젝트의 Maven 또는 Gradle 빌드 파일에 SLF4J와 Log4j2의 의존성을 추가해야 합니다. 다음은 Maven을 사용하는 경우의 예시입니다:

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.32</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>2.14.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.14.1</version>
    </dependency>
</dependencies>

2. 암호화된 로그 파일 구성

Log4j2는 XML 또는 프로그래밍 방식으로 로그 파일 구성을 설정할 수 있습니다. 암호화된 로그 파일을 생성하기 위해 프로그래밍 방식으로 설정하는 방법을 소개하겠습니다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Configurator;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
import org.apache.logging.log4j.core.config.plugins.util.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.util.PluginBuilderFactory;
import org.apache.logging.log4j.core.util.Builder;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.Serializable;
import java.util.Base64;

public class EncryptedFileAppenderConfigFactory extends ConfigurationFactory {

    @Override
    protected String[] getSupportedTypes() {
        return new String[]{"*"};
    }

    @Override
    public Configuration getConfiguration(ConfigurationSource source) {
        return getConfiguration(source, null);
    }

    @Override
    public Configuration getConfiguration(ConfigurationSource source, ClassLoader loader) {
        return new EncryptedFileAppenderConfig(source);
    }

    private static class EncryptedFileAppenderConfig extends ConfigurationBuilder<BuiltConfiguration> implements Serializable {
        private static final long serialVersionUID = 1L;

        static {
            ConfigurationBuilderFactory.addBuilder("EncryptedFileAppender", new BuilderFactory());
        }

        private EncryptedFileAppenderConfig(ConfigurationSource source) {
            super.setConfigurationSource(source);
        }

        private static class Builder extends AbstractConfigurationBuilder<Builder> implements org.apache.logging.log4j.core.util.Builder<BuiltConfiguration> {
            private String key;
            private String algorithm;

            @Override
            public BuiltConfiguration build() {
                try {
                    byte[] decodedKey = Base64.getDecoder().decode(key);
                    SecretKeySpec secretKey = new SecretKeySpec(decodedKey, algorithm);
                    Cipher cipher = Cipher.getInstance(algorithm);
                    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

                    add(encryptedFileAppender("EncryptedFile", "log/encrypted.log", getCipheredLayout(cipher)));
                    setLogger(levels(Level.INFO).add(newAppenderRef("EncryptedFile")).attribute("additivity", false));

                    return build(true);
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
            }

            @PluginBuilderAttribute("key")
            public Builder setKey(String key) {
                this.key = key;
                return this;
            }

            @PluginBuilderAttribute("algorithm")
            public Builder setAlgorithm(String algorithm) {
                this.algorithm = algorithm;
                return this;
            }
        }

        private static class BuilderFactory implements org.apache.logging.log4j.core.config.plugins.util.Builder<Builder> {
            @Override
            public Builder build() {
                return new Builder();
            }
        }
    }

    private static org.apache.logging.log4j.core.layout.PatternLayout getCipheredLayout(Cipher cipher) {
        return org.apache.logging.log4j.core.layout.PatternLayout.newBuilder()
                .withPattern("%d [%t] %-5level %logger{36} - %msg%n")
                .withCharset("UTF-8")
                .withConfiguration(configuration)
                .withFilter(new EncryptedFilter(cipher))
                .build();
    }

    private static org.apache.logging.log4j.core.appender.EncryptingFileAppender.Builder encryptedFileAppender(String name, String fileName, org.apache.logging.log4j.core.layout.PatternLayout layout) {
        return org.apache.logging.log4j.core.appender.EncryptingFileAppender.newBuilder()
                .withName(name)
                .withFileName(fileName)
                .withImmediateFlush(true)
                .withLayout(layout);
    }

    private static org.apache.logging.log4j.core.Filter levels(Level level) {
        return org.apache.logging.log4j.core.filter.LevelRangeFilter.createFilter(level, Level.ALL, true, false);
    }

    private static class EncryptedFilter implements org.apache.logging.log4j.core.Filter {

        private final Cipher cipher;

        public EncryptedFilter(Cipher cipher) {
            this.cipher = cipher;
        }

        @Override
        public Result filter(LogEvent event) {
            try {
                String message = event.getMessage().getFormattedMessage();
                byte[] encryptedMessage = cipher.doFinal(message.getBytes());
                String encodedMessage = Base64.getEncoder().encodeToString(encryptedMessage);
                LogEvent newEvent = new Log4jLogEvent.Builder()
                        .setLoggerName(event.getLoggerName())
                        .setLoggerFqcn(event.getLoggerFqcn())
                        .setLevel(event.getLevel())
                        .setMessage(new SimpleMessage(encodedMessage))
                        .setTimeMillis(event.getTimeMillis())
                        .build();
                return Result.ACCEPT_AND_PROCEED;
            } catch (Exception e) {
                e.printStackTrace();
                return Result.NEUTRAL;
            }
        }
    }
}

3. 로그 파일 암호화 설정

Log4j2 XML 구성 파일에서 다음과 같이 새로 작성한 Factory 클래스를 사용하여 로그 파일 암호화를 설정할 수 있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <EncryptedFileAppender name="EncryptedFileAppender" key="YOUR_ENCRYPTION_KEY" algorithm="AES">
            <PatternLayout pattern="%d [%t] %-5level %logger{36} - %msg%n"/>
        </EncryptedFileAppender>
    </Appenders>
    
    <Loggers>
        <Root level="info">
            <AppenderRef ref="EncryptedFileAppender"/>
        </Root>
    </Loggers>
</Configuration>

주의: YOUR_ENCRYPTION_KEY를 원하는 실제 암호화 키로 교체해야 합니다.

암호화된 로그 파일 설정이 완료되었습니다! 이제 프로그램을 실행하면 로그 파일이 암호화되어 생성됩니다.

참고 자료