Skip to content

Open log file with Read/Write access #152

Open
@MaxXor

Description

@MaxXor

I am trying to implement encrypted storing of log files. However this is currently not possible with the FileLifecycleHooks class as the underlyingStream parameter was opened with FileAccess.Write. I'm trying to get it to work with AES in CBC mode with padding, which requires re-reading of the last block because of the padding if an existing log file is opened.

Would it be possible to open the log files with read/write access in this library?

Below is a minimal example which describes the situation:

using Serilog;
using Serilog.Sinks.File;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace TestCryptoHooks
{
    public class Program
    {
        // Adapted from: https://stackoverflow.com/a/30864169

        public static string ReadStringFromFile(string fileName, byte[] key, byte[] iv)
        {
            string plainText;

            using (Rijndael algo = Rijndael.Create())
            {
                algo.Key = key;
                algo.IV = iv;
                algo.Mode = CipherMode.CBC;
                algo.Padding = PaddingMode.PKCS7;

                // Create the streams used for decryption.
                using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read))
                // Create a decrytor to perform the stream transform.
                using (ICryptoTransform decryptor = algo.CreateDecryptor())
                using (CryptoStream cs = new CryptoStream(file, decryptor, CryptoStreamMode.Read))
                using (StreamReader sr = new StreamReader(cs))
                {
                    // Read all data from the stream.
                    plainText = sr.ReadToEnd();
                }
            }

            return plainText;
        }

        public class CryptoHooks : FileLifecycleHooks
        {
            private byte[] _key;
            private byte[] _iv;

            public CryptoHooks(byte[] key, byte[] iv)
            {
                _key = key;
                _iv = iv;
            }

            public override Stream OnFileOpened(Stream underlyingStream, Encoding _)
            {
                var algo = Rijndael.Create(); 
                algo.Key = _key;
                algo.IV = _iv;
                algo.Mode = CipherMode.CBC;
                algo.Padding = PaddingMode.PKCS7;

                byte[] previous = null;
                int previousLength = 0;

                long length = underlyingStream.Length;

                if (length != 0)
                {
                    // The IV length is equal to the block length
                    byte[] block = new byte[_iv.Length];

                    if (length >= _iv.Length * 2)
                    {
                        // At least 2 blocks, take the penultimate block
                        // as the IV
                        underlyingStream.Position = length - _iv.Length * 2;
                        underlyingStream.Read(block, 0, block.Length);
                        algo.IV = block;
                    }
                    else
                    {
                        // A single block present, use the IV given
                        underlyingStream.Position = length - _iv.Length;
                        algo.IV = _iv;
                    }

                    // Read the last block
                    underlyingStream.Read(block, 0, block.Length);

                    // And reposition at the beginning of the last block
                    underlyingStream.Position = length - _iv.Length;

                    // We use a MemoryStream because the CryptoStream
                    // will close the Stream at the end
                    using (var ms = new MemoryStream(block))
                    // Create a decrytor to perform the stream transform.
                    using (ICryptoTransform decryptor = algo.CreateDecryptor())
                    using (CryptoStream cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read))
                    {
                        // Read all data from the stream. The decrypted last
                        // block can be long up to block length characters
                        // (so up to iv.Length) (this with AES + CBC)
                        previous = new byte[_iv.Length];
                        previousLength = cs.Read(previous, 0, previous.Length);
                    }
                }
                else
                {
                    // Use the IV given
                    algo.IV = _iv;
                }

                var outStream = new CryptoStream(underlyingStream, algo.CreateEncryptor(), CryptoStreamMode.Write);

                // Rewrite the last block, if present. We even skip
                // the case of block present but empty
                if (previousLength != 0)
                {
                    outStream.Write(previous, 0, previousLength);
                }

                return outStream;
            }
        }

        static void Main(string[] args)
        {
            var key = Encoding.UTF8.GetBytes("Simple key");
            var iv = Encoding.UTF8.GetBytes("Simple IV");

            Array.Resize(ref key, 128 / 8);
            Array.Resize(ref iv, 128 / 8);

            Log.Logger = new LoggerConfiguration()
                .WriteTo.Async(a => a.File("file.log", rollingInterval: RollingInterval.Day, retainedFileCountLimit: 31, hooks: new CryptoHooks(key, iv)))
                .CreateLogger();

            Log.Information("This will be written to disk on the worker thread");
            Log.CloseAndFlush();
            string plainText = ReadStringFromFile("file20200602.log", key, iv);
            Console.WriteLine(plainText);
            Console.ReadLine();
        }
    }
}

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions