Encryption 创建加密日志文件

Encryption 创建加密日志文件,encryption,logging,Encryption,Logging,我正在创建一个客户端应用程序,它需要创建一个用户活动日志,但由于各种原因,该日志不能是人类可读的 目前,为了我的开发,我正在创建一个纯文本日志,该日志如下所示: 12/03/2009 08:34:21 -> User 'Bob' logged in 12/03/2009 08:34:28 -> Navigated to config page 12/03/2009 08:34:32 -> Option x changed to y 部署应用程序时,日志不能是纯文本,因此所有文

我正在创建一个客户端应用程序,它需要创建一个用户活动日志,但由于各种原因,该日志不能是人类可读的

目前,为了我的开发,我正在创建一个纯文本日志,该日志如下所示:

12/03/2009 08:34:21 -> User 'Bob' logged in
12/03/2009 08:34:28 -> Navigated to config page
12/03/2009 08:34:32 -> Option x changed to y
部署应用程序时,日志不能是纯文本,因此所有文本都必须加密。这似乎并不容易实现,因为我需要在添加每个条目时动态更新日志文件

我考虑的方法是创建一个二进制文件,单独加密每个日志条目,然后将其附加到二进制文件中,并在每个条目之间进行适当的划分


有没有人知道解决这个问题的常用方法,我相信一定有更好的解决办法

这不是我的事,我承认这一点,但是你不能单独加密每个条目,然后将其附加到日志文件中吗?如果您不加密时间戳,您可以很容易地找到您要查找的条目,并在需要时解密这些条目


我的观点主要是,将单个加密条目附加到文件并不一定需要是附加到二进制文件的二进制条目。使用(例如)gpg加密将产生可以附加到ascii文件的ascii编码。这能解决你的问题吗

假设您正在使用某种日志框架,例如,等等,那么您应该能够创建一个自定义的(或类似的)实现来加密每个条目,正如@wzzrd所建议的那样。

FWIW,有一次我需要一个加密的记录器,我使用了一个对称密钥(出于性能原因)来加密实际的日志条目

然后,对称的“日志文件密钥”在公钥下加密并存储在日志文件的开头,一个单独的日志读取器使用私钥解密“日志文件密钥”并读取条目


整个过程都是使用log4j和XML日志文件格式实现的(以便于读者解析),每次滚动日志文件时,都会生成一个新的“日志文件密钥”。

我想知道您编写的是哪种应用程序。病毒还是特洛伊木马?无论如何

单独加密每个条目,将其转换为某个字符串(例如Base64),然后将该字符串记录为“消息”

这允许您保持文件的部分可读性,并且只加密重要部分

请注意,这枚硬币还有另一面:如果你创建了一个完全加密的文件并向用户索要,她不知道你将从该文件中学到什么。因此,您应该尽可能少地加密(密码、IP地址、客户数据),以便法律部门能够验证留下的数据

更好的方法是为日志文件设置模糊器。它只是用“XXX”替换某些模式。你仍然可以看到发生了什么,当你需要一个特定的数据时,你可以要求它

[编辑]这个故事有更多你乍一看会想到的含义。这实际上意味着用户看不到文件中的内容。“用户”不一定包括“饼干”。破解者会专注于加密文件(因为它们可能更重要)。这就是老话的原因:一旦有人进入机器,就没有办法阻止他在机器上做任何事情。或者换一种说法:仅仅因为你不知道怎么做并不意味着其他人也不知道。如果你认为自己没有什么可隐瞒的,那你就没有考虑过自己

此外,还有责任问题。比如说,在你得到一份日志副本后,一些数据在互联网上泄露。既然用户不知道日志文件中有什么,你怎么能在法庭上证明你不是泄密者?老板们可以要求提供日志文件来监控他们的典当,要求对典当进行编码,这样农民就不会注意到典当,也不会抱怨典当了(或者苏,这个人渣!)


或者从一个完全不同的角度来看:如果没有日志文件,没有人可以滥用它。仅在紧急情况下启用调试如何?我已经将log4j配置为将最后200条日志消息保存在缓冲区中。如果记录了错误,我会将200条消息转储到日志中。理由:我真的不在乎白天会发生什么。我只喜欢虫子。使用JMX,当您需要更多详细信息时,可以简单地将调试级别设置为ERROR,并在运行时远程降低调试级别。

有关.Net,请参阅Microsoft应用程序块以了解日志和加密功能:


我会使用每个条目之间的适当界限将加密日志条目附加到平面文本文件中,以便解密工作。

单独加密每个日志条目将大大降低密文的安全性,特别是因为您使用的是非常可预测的明文

以下是您可以做的:

  • 使用对称加密(最好是AES)
  • 选择一个随机的主密钥
  • 选择一个安全窗口(5分钟、10分钟等)
  • 然后,在每个窗口的开头随机选择一个临时关键点(每5分钟、每10分钟等)

    使用临时密钥分别加密每个日志项,并附加到临时日志文件中

    当窗口关闭时(预定时间到了),使用临时密钥解密每个元素,使用主密钥解密主日志文件,合并文件,并使用主密钥加密

    然后,选择一个新的临时关键点并继续

    此外,每次旋转主日志文件时(每天、每周等)都要更改主密钥


    这应该能提供足够的安全性。

    我不清楚您的问题是在安全性上,还是在机具上

    一个简单的实现是连接流加密程序。流加密程序保持自己的状态,可以动态加密

    StreamEncryptor<AES_128> encryptor;
    encryptor.connectSink(new std::ofstream("app.log"));
    encryptor.write(line);
    encryptor.write(line2);
    ...
    
    流式加密机;
    encryptor.connectSink(新标准::of Stream(“app.log”);
    加密机。写入(行);
    encryptor.write
    
    import javax.crypto.*;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.*;
    import java.security.*;
    
    
    public class FlushableCipherOutputStream extends OutputStream
    {
        private static int HEADER_LENGTH = 16;
    
    
        private SecretKeySpec key;
        private RandomAccessFile seekableFile;
        private boolean flushGoesStraightToDisk;
        private Cipher cipher;
        private boolean needToRestoreCipherState;
    
        /** the buffer holding one byte of incoming data */
        private byte[] ibuffer = new byte[1];
    
        /** the buffer holding data ready to be written out */
        private byte[] obuffer;
    
    
    
        /** Each time you call 'flush()', the data will be written to the operating system level, immediately available
         * for other processes to read. However this is not the same as writing to disk, which might save you some
         * data if there's a sudden loss of power to the computer. To protect against that, set 'flushGoesStraightToDisk=true'.
         * Most people set that to 'false'. */
        public FlushableCipherOutputStream(String fnm, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk)
                throws IOException
        {
            this(new File(fnm), _key, append,_flushGoesStraightToDisk);
        }
    
        public FlushableCipherOutputStream(File file, SecretKeySpec _key, boolean append, boolean _flushGoesStraightToDisk)
                throws IOException
        {
            super();
    
            if (! append)
                file.delete();
            seekableFile = new RandomAccessFile(file,"rw");
            flushGoesStraightToDisk = _flushGoesStraightToDisk;
            key = _key;
    
            try {
                cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    
                byte[] iv = new byte[16];
                byte[] headerBytes = new byte[HEADER_LENGTH];
                long fileLen = seekableFile.length();
                if (fileLen % 16L != 0L) {
                    throw new IllegalArgumentException("Invalid file length (not a multiple of block size)");
                } else if (fileLen == 0L) {
                    // new file
    
                    // You can write a 16 byte file header here, including some file format number to represent the
                    // encryption format, in case you need to change the key or algorithm. E.g. "100" = v1.0.0
                    headerBytes[0] = 100;
                    seekableFile.write(headerBytes);
    
                    // Now appending the first IV
                    SecureRandom sr = new SecureRandom();
                    sr.nextBytes(iv);
                    seekableFile.write(iv);
                    cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
                } else if (fileLen <= 16 + HEADER_LENGTH) {
                    throw new IllegalArgumentException("Invalid file length (need 2 blocks for iv and data)");
                } else {
                    // file length is at least 2 blocks
                    needToRestoreCipherState = true;
                }
            } catch (InvalidKeyException e) {
                throw new IOException(e.getMessage());
            } catch (NoSuchAlgorithmException e) {
                throw new IOException(e.getMessage());
            } catch (NoSuchPaddingException e) {
                throw new IOException(e.getMessage());
            } catch (InvalidAlgorithmParameterException e) {
                throw new IOException(e.getMessage());
            }
        }
    
    
        /**
         * Writes one _byte_ to this output stream.
         */
        public void write(int b) throws IOException {
            if (needToRestoreCipherState)
                restoreStateOfCipher();
            ibuffer[0] = (byte) b;
            obuffer = cipher.update(ibuffer, 0, 1);
            if (obuffer != null) {
                seekableFile.write(obuffer);
                obuffer = null;
            }
        }
    
        /** Writes a byte array to this output stream. */
        public void write(byte data[]) throws IOException {
            write(data, 0, data.length);
        }
    
        /**
         * Writes <code>len</code> bytes from the specified byte array
         * starting at offset <code>off</code> to this output stream.
         *
         * @param      data     the data.
         * @param      off   the start offset in the data.
         * @param      len   the number of bytes to write.
         */
        public void write(byte data[], int off, int len) throws IOException
        {
            if (needToRestoreCipherState)
                restoreStateOfCipher();
            obuffer = cipher.update(data, off, len);
            if (obuffer != null) {
                seekableFile.write(obuffer);
                obuffer = null;
            }
        }
    
    
        /** The tricky stuff happens here. We finalise the cipher, write it out, but then rewind the
         * stream so that we can add more bytes without padding. */
        public void flush() throws IOException
        {
            try {
                if (needToRestoreCipherState)
                    return; // It must have already been flushed.
                byte[] obuffer = cipher.doFinal();
                if (obuffer != null) {
                    seekableFile.write(obuffer);
                    if (flushGoesStraightToDisk)
                        seekableFile.getFD().sync();
                    needToRestoreCipherState = true;
                }
            } catch (IllegalBlockSizeException e) {
                throw new IOException("Illegal block");
            } catch (BadPaddingException e) {
                throw new IOException("Bad padding");
            }
        }
    
        private void restoreStateOfCipher() throws IOException
        {
            try {
                // I wish there was a more direct way to snapshot a Cipher object, but it seems there's not.
                needToRestoreCipherState = false;
                byte[] iv = cipher.getIV(); // To help avoid garbage, re-use the old one if present.
                if (iv == null)
                    iv = new byte[16];
                seekableFile.seek(seekableFile.length() - 32);
                seekableFile.read(iv);
                byte[] lastBlockEnc = new byte[16];
                seekableFile.read(lastBlockEnc);
                cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
                byte[] lastBlock = cipher.doFinal(lastBlockEnc);
                seekableFile.seek(seekableFile.length() - 16);
                cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
                byte[] out = cipher.update(lastBlock);
                assert out == null || out.length == 0;
            } catch (Exception e) {
                throw new IOException("Unable to restore cipher state");
            }
        }
    
        public void close() throws IOException
        {
            flush();
            seekableFile.close();
        }
    }
    
    import org.junit.Test;
    import javax.crypto.Cipher;
    import javax.crypto.CipherInputStream;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;
    import java.io.*;
    import java.io.BufferedWriter;
    
    
    
    public class TestFlushableCipher {
        private static byte[] keyBytes = new byte[] {
                // Change these numbers, lest other StackOverflow readers can decrypt your files.
                -53, 93, 59, 108, -34, 17, -72, -33, 126, 93, -62, -50, 106, -44, 17, 55
        };
        private static SecretKeySpec key = new SecretKeySpec(keyBytes,"AES");
        private static int HEADER_LENGTH = 16;
    
    
        private static BufferedWriter flushableEncryptedBufferedWriter(File file, boolean append) throws Exception
        {
            FlushableCipherOutputStream fcos = new FlushableCipherOutputStream(file, key, append, false);
            return new BufferedWriter(new OutputStreamWriter(fcos, "UTF-8"));
        }
    
        private static InputStream readerEncryptedByteStream(File file) throws Exception
        {
            FileInputStream fin = new FileInputStream(file);
            byte[] iv = new byte[16];
            byte[] headerBytes = new byte[HEADER_LENGTH];
            if (fin.read(headerBytes) < HEADER_LENGTH)
                throw new IllegalArgumentException("Invalid file length (failed to read file header)");
            if (headerBytes[0] != 100)
                throw new IllegalArgumentException("The file header does not conform to our encrypted format.");
            if (fin.read(iv) < 16) {
                throw new IllegalArgumentException("Invalid file length (needs a full block for iv)");
            }
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
            return new CipherInputStream(fin,cipher);
        }
    
        private static BufferedReader readerEncrypted(File file) throws Exception
        {
            InputStream cis = readerEncryptedByteStream(file);
            return new BufferedReader(new InputStreamReader(cis));
        }
    
        @Test
        public void test() throws Exception {
            File zfilename = new File("c:\\WebEdvalData\\log.x");
    
            BufferedWriter cos = flushableEncryptedBufferedWriter(zfilename, false);
            cos.append("Sunny ");
            cos.append("and green.  \n");
            cos.close();
    
            int spaces=0;
            for (int i = 0; i<10; i++) {
                cos = flushableEncryptedBufferedWriter(zfilename, true);
                for (int j=0; j < 2; j++) {
                    cos.append("Karelia and Tapiola" + i);
                    for (int k=0; k < spaces; k++)
                        cos.append(" ");
                    spaces++;
                    cos.append("and other nice things.  \n");
                    cos.flush();
                    tail(zfilename);
                }
                cos.close();
            }
    
            BufferedReader cis = readerEncrypted(zfilename);
            String msg;
            while ((msg=cis.readLine()) != null) {
                System.out.println(msg);
            }
            cis.close();
        }
    
        private void tail(File filename) throws Exception
        {
            BufferedReader infile = readerEncrypted(filename);
            String last = null, secondLast = null;
            do {
                String msg = infile.readLine();
                if (msg == null)
                    break;
                if (! msg.startsWith("}")) {
                    secondLast = last;
                    last = msg;
                }
            } while (true);
            if (secondLast != null)
                System.out.println(secondLast);
            System.out.println(last);
            System.out.println();
        }
    }