Hashing Passwords in Logback output I have used http://hilite.me/ website to do the java-html conversion. Lets say we want to encrypt passwords in log files while printing. Thanks to my Collegue Paul Bentley for achieving this. Encoder.java public class Encoder { private static final int FF = 0xFF; private static final char PAD = '0'; private static final String DEFAULT_REPLACEMENT = "Encoding error"; /** * Hex encodes a string. * @param digestAlgorithm the encryption algorithm. * @param value the value to encode. * @param salt the salt for the encryption. * @param defaultReplacement * @return the value hex encoded. */ public static String hexEncode( final String digestAlgorithm, final String value, final String salt, final String defaultReplacement) { //System.out.println("hexEncode " + digestAlgorithm + " " + value + " " + salt + " " + defaultReplacement); final StringBuilder hexString = new StringBuilder(); try { final MessageDigest messageDigest = MessageDigest.getInstance(digestAlgorithm); for (final byte b : messageDigest.digest((salt + value).getBytes())) { final int x = b & FF; final String s = Integer.toHexString(x); hexString.append(s.length() == 1 ? PAD + s : s); } } catch (final Exception e) { hexString.append(defaultReplacement); } return hexString.toString(); } } SecureMessageConverter.java import ch.qos.logback.classic.pattern.ClassicConverter; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Context; /** * Logback converter to encrypt private fields such as passwords. */ public class SecureMessageConverter extends ClassicConverter { // Properties from logback.xml public static final String ALGORITHM = "SecureMessageConverter.ALGORITHM"; public static final String SALT_FORMAT = "SecureMessageConverter.SALT_FORMAT"; public static final String DEFAULT_REPLACEMENT = "SecureMessageConverter.DEFAULT_REPLACEMENT"; public static final String CANDIDATE_PATTERN = "SecureMessageConverter.CANDIDATE_PATTERN"; public static final String PATTERN = "SecureMessageConverter.PATTERN"; private static volatile List<Pattern> patterns = new ArrayList<Pattern>(); private static String algorithm; private static String saltFormat; private static String defaultReplacement; private static Pattern candidatePattern; private static final int FIRST_FIELD = 1; private static final int ENCRYPTED_FIELD = 2; private static final int LAST_FIELD = 3; /** * Initialises the SecureMessageConverter initialising the resources. Since resource initialisation is relatively expensive * this is done only once for all instances of the class. The Context is not available in a constructor so this must be done * after construction. Patterns are thread safe so can be shared by instances of this class. */ private void initialise() { try { synchronized (patterns) { if (patterns.isEmpty()) { final Context context = getContext(); algorithm = context.getProperty(ALGORITHM); // System.out.println("alogrithm=" + algorithm); saltFormat = context.getProperty(SALT_FORMAT); // System.out.println("saltFormat=" + saltFormat); defaultReplacement = context.getProperty(DEFAULT_REPLACEMENT); // System.out.println("defaultReplacement=" + defaultReplacement); candidatePattern = Pattern.compile(context.getProperty(CANDIDATE_PATTERN), Pattern.DOTALL); // System.out.println("candidatePattern=" + defaultReplacement); // Read all the patterns of the form PATTERN1, PATTERN2 .. PATTERNN int i = 1; String property = context.getProperty(PATTERN + i); while (property != null) { patterns.add(Pattern.compile(property, Pattern.DOTALL)); property = context.getProperty(PATTERN + i); // System.out.println("pattern" + i + "=" + property); i++; } } } } catch (final Exception e) { e.printStackTrace(); } } /** * Encrypts the passwords in some text. Since most lines of text are not expected to contain passwords a simple regular * expression is used to efficiently find candidates for encryption and the more complex and expensive regular expressions are * used only on lines that are identified as candidates. * @param event the event to log. * @return the text with and matching password fields encrypted. */ @Override public String convert(final ILoggingEvent event) { initialise(); String result = new String(event.getFormattedMessage()); final Matcher preMatcher = candidatePattern.matcher(result); if (preMatcher.find()) { for (final Pattern pattern : patterns) { final Matcher matcher = pattern.matcher(result); if (matcher.find() && matcher.groupCount() == LAST_FIELD) { result = compose(matcher); break; } } } return result; } /** * Composes a string from regular expression matching three fields. * @param matcher the Matcher from the regular expression. * @return the result with the field for encryption hex encoded. */ private String compose(final Matcher matcher) { final String salt = new SimpleDateFormat(saltFormat).format(new Date()); final String hex = Encoder.hexEncode(algorithm, matcher.group(ENCRYPTED_FIELD), salt, defaultReplacement); return matcher.group(FIRST_FIELD) + hex + matcher.group(LAST_FIELD); } } logback.xml <configuration debug="true" scan="true" scanPeriod="30 seconds"> <property name="SERVER_LOG_LOCATION" value="${com.sun.aas.instanceRoot}/logs" /> <property scope="context" name="SecureMessageConverter.ALGORITHM" value="MD5" /> <property scope="context" name="SecureMessageConverter.SALT_FORMAT" value="yyyy-MM-dd'T'HH:mm:ss" /> <property scope="context" name="SecureMessageConverter.DEFAULT_REPLACEMENT" value="h3ll0" /> <property scope="context" name="SecureMessageConverter.CANDIDATE_PATTERN" value="PASSWORD|credentials" /> <property scope="context" name="SecureMessageConverter.PATTERN1" value="(.*PASSWORD=")(.*)(" .*)" /> <property scope="context" name="SecureMessageConverter.PATTERN2" value="(.*<.*:credentials>)(.*)(</.*:credentials>.*)" /> <conversionRule conversionWord="msg" converterClass="uk.co.virginmedia.mw.logback.SecureMessageConverter" /> <appender name="STDOUT" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${SERVER_LOG_LOCATION}/server_services.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${SERVER_LOG_LOCATION}/server_services_%i.log</fileNamePattern> <minIndex>1</minIndex> <maxIndex>10</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>10MB</maxFileSize> </triggeringPolicy> <encoder> <pattern>%d{yyyy-mm-dd'T'HH:mm:ss.SSSZ}|%-5level|_ThreadName=%thread|SERVICE_NAME:%logger|CLASS_NAME:%class|METHOD_NAME:%method|LINE_NUMBER:%file:%line|MESSAGE:%msg%n</pattern> </encoder> </appender> <appender name="SIFT" class="ch.qos.logback.classic.sift.SiftingAppender"> <discriminator class="uk.co.virginmedia.mw.logback.LoggerNameBasedDiscriminator"/> <sift> <appender name="FILE-${loggerName}" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${SERVER_LOG_LOCATION}/${loggerName}/${loggerName}.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${SERVER_LOG_LOCATION}/${loggerName}/${loggerName}_%i.log</fileNamePattern> <minIndex>1</minIndex> <maxIndex>10</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>10MB</maxFileSize> </triggeringPolicy> <encoder> <pattern>%d{yyyy-mm-dd'T'HH:mm:ss.SSSZ}|%-5level|_ThreadName=%thread|SERVICE_NAME:%logger|CLASS_NAME:%class|METHOD_NAME:%method|LINE_NUMBER:%file:%line|MESSAGE:%msg%n</pattern> </encoder> </appender> </sift> </appender> <root level="debug"> <appender-ref ref="STDOUT" /> <appender-ref ref="SIFT" /> </root> </configuration> pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <scope>provided</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <mainClass>Encoder</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>
Thursday, 5 June 2014
Hashing Passwords in Logback output
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment