What algorithm to use when creating encryption log4j appender
I created a RollingFileAppender that encrypts the output to the log4j log file. It currently uses AES / ECB / NoPadding and it works great.
This is how we create the cipher
public static Cipher getCipher(boolean encrypt) throws Exception {
//https://en.wikipedia.org/wiki/Stream_cipher
byte[] key = ("sometestkey").getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit
Key k = new SecretKeySpec(key,"AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
if (encrypt) {
cipher.init(Cipher.ENCRYPT_MODE, k);
} else {
cipher.init(Cipher.DECRYPT_MODE, k);
}
return cipher;
}
Here we create an appender:
public class EncryptingRollingFileAppender extends RollingFileAppender {
private CipherOutputStream s;
private Cipher cipher;
public EncryptingRollingFileAppender() {super();}
public EncryptingRollingFileAppender(Layout layout, String filename, boolean append) throws IOException {super(layout, filename, append);}
public EncryptingRollingFileAppender(Layout layout, String filename) throws IOException {super(layout, filename);}
@Override
protected OutputStreamWriter createWriter(OutputStream outputStream) {
if (cipher==null) {
try {
cipher = DecryptionTools.getCipher(true);
s = new CipherOutputStream(outputStream, cipher);
} catch (Throwable t) {
throw new RuntimeException("failed to initialise encrypting file appender",t);
}
}
OutputStreamWriter out = super.createWriter(s);
return out;
}
}
We can decrypt the file using
getCipher (false)
to create the corresponding decryption stream.
The problem is that our security team is haggling over key management. They don't like using a symmetric encryption key and would rather use a key pair than a simple password that we need to manage in some way.
Does anyone know of a no-padding ECB encryption method that will use a key pair and is suitable for this kind of stream encryption and decryption?
source to share
The comments suggesting the use of hybrid encryption and PGP are correct.
PGP is the defacto standard for hybrid file encryption, and it is a much more secure solution that ECES AES mode provides.
Due to the nature of PGP, it will work slightly differently than your existing solution.
PGP messages have a header and a footer, so you want each file to be individually encrypted (you just won't be able to decrypt individual blocks like you can with simple ECB mode encryption).
It looks like you are using log4j 1.2.I created a working implementation of PGP encryption RollingFileAppender
.
To use this example, generate an armor-encoded PGP public key, run the main class, then decrypt the file using any PGP tool (I used GnuPG to generate keys and decrypt).
An example was built against log4j:log4j:1.2.17
andorg.bouncycastle:bcpg-jdk15on:1.56
source to share
You are very close to where you want to achieve. Using Log4j 1.2 (since you cannot subclass RollingFileAppender directly in Log4j 2), you can generate a password on the fly for each log file, encrypt the password with RSA, and store next to it. Then use the password to create an AES CipherOutputStream for the log recorder.
public class EncryptingRollingFileAppender extends RollingFileAppender {
private CipherOutputStream s;
private Cipher cipher;
private byte[] secretKey;
public EncryptingRollingFileAppender(Layout layout, String filename, boolean append) throws IOException {
super(layout, filename, append);
writeKeyFile(filename);
}
public EncryptingRollingFileAppender(Layout layout, String filename) throws IOException {
super(layout, filename);
writeKeyFile(filename);
}
private void writeKeyFile(final String logfilename) throws IOException {
final int dot = logfilename.lastIndexOf('.');
final String keyfilename = (dot == -1 ? logfilename : logfilename.substring(0, dot)) + ".key";
try (FileOutputStream out = new FileOutputStream(keyfilename)) {
out.write(DecryptionTools.encryptPasswordBase64(secretKey).getBytes(ISO_8859_1));
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | KeyStoreException
| CertificateException e) {
}
}
@Override
protected OutputStreamWriter createWriter(OutputStream outputStream) {
System.out.println("createWriter()");
if (cipher == null) {
secretKey = DecryptionTools.generateRandomKey(16).getBytes(ISO_8859_1);
try {
cipher = DecryptionTools.getCipher(true, secretKey);
} catch (InvalidKeyException e) {
System.out.println("InvalidKeyException");
}
s = new CipherOutputStream(outputStream, cipher);
}
OutputStreamWriter out = super.createWriter(s);
return out;
}
}
You will need some helper functions to read the private key from a file or from the Java keystore, which can be found here .
The test file TestEncryptingRollingFileAppender
shows you how to write an encrypted log and read it.
import static com.acme.DecryptionTools.getCipher;
import static com.acme.DecryptionTools.decryptPasswordBase64;
public class TestEncryptingRollingFileAppender {
@SuppressWarnings("deprecation")
@Test
public void testAppender() throws IOException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, KeyStoreException, CertificateException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException {
final File logfile = File.createTempFile("testlog_", ".log");
final String logfilename = logfile.getAbsolutePath();
final Logger lggr = LogManager.getLogger(TestEncryptingRollingFileAppender.class);
final EncryptingRollingFileAppender appender = new EncryptingRollingFileAppender(new SimpleLayout(), logfilename, true);
appender.append(new LoggingEvent(lggr.getClass().getName(), lggr, Priority.INFO, "Test Log Line #1", null));
appender.append(new LoggingEvent(lggr.getClass().getName(), lggr, Priority.INFO, "Test Log Line #1", null));
final int dot = logfilename.lastIndexOf('.');
byte[] key = decryptPasswordBase64(new String(Files.readAllBytes(Paths.get(logfilename.substring(0, dot)+".key"))));
StringBuilder logContent = new StringBuilder();
try (FileInputStream instrm = new FileInputStream(logfilename);
CipherInputStream cistrm = new CipherInputStream(instrm, getCipher(false, key))) {
int c;
while ((c=cistrm.read())!=-1)
logContent.append((char) c);
}
assertEquals("INFO - Test Log Line #1\r\nINFO - Test Log Line #1", logContent.toString());
}
}
source to share