3

I've created a RollingFileAppender which encrypts the output to a log4j log file. Currently it uses AES/ECB/NoPadding, and it works fine.

Here 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's how we create the 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 by using

getCipher(false)

to create an appropriate decryption stream.

The issue is that our security team are haggling about key management. They don't like the use symetric key encryption, and would prefer us to use a key pair rather than a simple password that we have to manage in some way.

Does anyone know of a non-padding ECB encryption technique that would use a key pair, and would be appropriate for this kind of stream encryption and decryption?

Richard
  • 1,070
  • 9
  • 22
  • You could use [hybrid encryption](https://en.wikipedia.org/wiki/Hybrid_cryptosystem) with any combination of asymmetric and symmetric encryption algorithms, but you have other issues. Encrypted messages need some kind of header. How do you distinguish multiple messages in the same log file? – Artjom B. May 06 '17 at 17:45
  • You're writing to a config file? Really? – user207421 May 06 '17 at 18:09
  • @ArtjomB. Do you have any examples of hybrid encryption in Java. More specifically, could I get RC4 to work with asynchronous key pairs? – Richard May 06 '17 at 22:28
  • @Richard [Here](http://stackoverflow.com/q/42466378/1816580) is an example, but it's not streamified. I don't see why you want to use RC4. – Artjom B. May 07 '17 at 07:55
  • I don't quite follow the example. I want to use RC4 because it creates files that are the same size as unencrypted files. In other words there is no extra payload for the encryption. The log files this produces will amount to 60GB per day as it is. – Richard May 08 '17 at 19:18
  • Here is another SO question with a solution using BouncyCastle and PGP for public key encryption of a stream: http://stackoverflow.com/questions/3939447/how-to-encrypt-a-string-stream-with-bouncycastle-pgp-without-starting-with-a-fil – Michael May 08 '17 at 19:25
  • If pgp works in my aplication i'll get you to make your comment an answer and give you the bounty. – Richard May 09 '17 at 12:46
  • @Michael create an answer and I'll award it to you. Though Magnus's answer is more complete, I think it was you that first suggested PGP as a stream encryption mechanism. – Richard May 10 '17 at 09:27
  • So you basically create a new log file for each server restart (start-stop logging?) – ThomasRS May 10 '17 at 09:55
  • No. Log files are created on a rolling basis, each file being 500mb in size. – Richard May 10 '17 at 10:01
  • *"What algorithm to use when creating an encrypting log4j appender..."* - What problem are you trying to solve? Usually you implement [Tamper Evident logging](https://www.google.com/search?q=tamper+evident+logging) when you have elevated logging requirements. The Security Team should have a security architecture with requirements. What did the team say about the security requirements? Why did they not give you a detailed description with actionable items? – jww Jun 05 '17 at 14:28
  • Our security team are more like a sort of glorified email service for Fortify. They just really highlight problems. They say that we can't log sensitive data. Fair point for them, but our support teams may need it for debugging live or single user issues for which they have clearance. I don't want to try and identify what may or may not be sensitive in all our third party responses, or stack traces. We compromised on encryption. But now (again rightfully) they raise the issue of key management. I cannot effectively secure logs encrypted and decrypted with the same key. – Richard Jun 05 '17 at 22:11
  • @Richard - the application likely won't pass a security audit if its trying to encrypt sensitive information rather than not logging it in the first place. About all you should be logging is AAA items. If Support needs enhanced logs, then the customer gets a special build with enhanced logging enabled. Also, you usually don't use PGP in that fashion. You use a symmetric cipher, and the key for the cipher is on record with a Key Management server. That's speaking from experience. I used to perform the audits in US Financial; and I've been audited in US Federal and US DoD. – jww Jun 06 '17 at 00:53
  • @Richard - If the app does not run in the Enterprise, then you can get away with nearly anything. There's lots of security models out there. Inside-out is probably the most popular. Security controls are added (like encrypting a log after the fact), you see what is protected, and then that becomes the model. SSL and TLS use that model. Another favorite is removing threats from the model because they are too inconvenient to deal with (like user phishing). Web broswers use this technique. Once they remove the threats they don't want to deal with, they claim the app and the platform is secure. – jww Jun 06 '17 at 00:59
  • This is an enterprise system dealing with hundreds of thousands of individuals a day. The issue is the structure of our company, which is very RUP. Our security, ops, dev, test.... etc. teams are siloed. Our business requires that ultimately live defects are debuggable, but will not allow dev to attach to the live servers. Ironically we are forced to produce an unencrypted log containing sensitive data so that it can be read by a system detecting hackers. I realise that others do things a certain way, but I believe my proposed system is better for our business, if I can just make it work. – Richard Jun 06 '17 at 09:39

2 Answers2

0

The comments suggesting using hybrid encryption and PGP are right.
PGP is the defacto standard for hybrid encryption of files, and it is a much more robust solution that ECB mode AES.

Due to the nature of PGP it is going to work slightly differently to your existing solution.

PGP messages have a header and a footer, so you will want each file to be individually encrypted (you wont just be able to decrypt individual blocks like you can with plain ECB mode encryption).

It looks like you are using log4j 1.2, I have created a working implementation of a PGP encrypting RollingFileAppender.

To use the example, generate an armour encoded PGP public key, run the main class then decrypt the file with any PGP tool (I used GnuPG for creating the keys and decrypting).

The example was built against log4j:log4j:1.2.17 and org.bouncycastle:bcpg-jdk15on:1.56

Magnus
  • 7,952
  • 2
  • 26
  • 52
  • Thank you that's great. I don't think I can use PGP because it doesn't allow us to decrypt a file which is still being written to. Also the file size after PGP is much larger than the unencrypted files. I think what I need is a way of using RC4 with some sort of clever key pair system. And after a lot of research I don't believe it's possible. Instead I have resolved to use a simple cipher to to convert the key into something else. That ciphered key is then used to encrypt and decrypt the logs. And then I just obfuscate the jar to hide the ciphering. It's not secure, but it's a step forward – Richard May 10 '17 at 09:24
  • The size of the file should be more or less the same if you disable the ascii "Armour" encoding(there is some fixed size overhead for the header which becomes meaningless as the file size increases, bouncycastle also has a compression option). It should be technically possible to decrypt the file as it is being written, but standard tooling probably wont work, might be able to write some code to do it though. I would avoid RC4, there is really no reason to use it for anything these days, if you really want a stream cipher you can use AES in CFB mode. – Magnus May 10 '17 at 10:05
  • But aes is symetric key, so i still have the problem of hiding the key. – Richard May 10 '17 at 10:07
  • Yes, but so is RC4, you will have the same problem either way. Neither will solve the asymmetric encryption issue. PGP is a scheme that uses an asymmetric cipher to encrypt a symmetric cipher key. You can make your own home grown scheme to do the same thing, but I don't really see the point. – Magnus May 10 '17 at 10:12
  • Our application security do not want any encryption key in the code or a config file. They don't want our sysadmins to be able to see sensitive customer data in the logs. We (dev) could obfuscate individually sensitive fields but that is an endless task. We elected to encrypt the logs and provide search and decryption tools for our support teams. Only the support teams should be able to decrypt the logs. If we use a symmetric key, it is hard to hide the key from dev and ops. If we use an asymetric key there seems to be no available stream ciphers that meet our requirements for stream encrytion – Richard May 10 '17 at 10:26
  • This idea of using an asymetric cipher to encrypt a symetric key, is that 'hybrid' encryption? – Richard May 10 '17 at 10:28
  • Yes, I tried to explain that in my answer, it seems like your only real issue with my PGP solution is that you don't currently have a method to decrypt files as they are written. The reason why no one uses asymmetric ciphers to directly encrypt data, is because they are slow and can only work on small pieces of data at a time(RSA can only encrypt data the size of its key, eg 2048 bits) – Magnus May 10 '17 at 10:33
  • I think I'm leaning towards RC4 with a home-baked hybrid key encryption mechanism. We can get support to generate a key pair. I'll put the public key in our code and use it to encrypt the symetric key which can then be stored in the code in plain text. When they want to decrypt they can supply their private key, and I'll use that to re-encrypt the symetric key and begin decryption. Any ideas or comments on this? – Richard May 10 '17 at 10:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/143867/discussion-between-magnus-and-richard). – Magnus May 10 '17 at 11:01
  • I decided to award the bounty to Magnus because he explained the problem and solutions to me in detail, and took the time and effort. – Richard May 10 '17 at 12:07
  • Forgive my ignorance... What is the problem you are solving with PGP? The problem also seems to be related to Key Management and Key Hierarchies. Its not clear to me how PGP solves anything at this point. – jww Jun 05 '17 at 14:25
  • @jww My understanding of the problem, is that they have an application generating a log file, they don't want anyone with access to the system/file to be able to read the data within it. PGP allows them to encrypt the file so that only the holder of the private key can decrypt its contents. So i demonstrated a practical approach using the logging framework in the question. – Magnus Jun 06 '17 at 00:38
0

You are pretty close to what you want to achieve. Using Log4j 1.2 (because 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 produce an AES CipherOutputStream for the log appender.

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´ll need several helper functions for reading the private key from a file or from a Java key store which can be found here.

The test file TestEncryptingRollingFileAppender shows how to write an encrypted log and read it back.

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());

    }
}
Serg M Ten
  • 5,568
  • 4
  • 25
  • 48