WylieYYYY
WylieYYYY

Reputation: 3

AES Encryption failed on last line of file when using same FileStream as CryptoStream

I am trying to create an AES encryption method where the same file is read and write at the same time. Other codes I have read all create a new file, I don't want to have new file. Other encrypted line matched my reference output from separate output, except my method generated an incorrect last line as plain text plus additional bytes.

I have tried replacing FileStream argument of CryptoStream and remove fs.Seek() at line 58, the program runs correctly and generated new .aes encrypted file for my reference output above.

static void Main(string[] args)
{
    AESCryptFile(@"C:\Users\user\Downloads\test.txt",
        new byte[] { 0x13, 0x11, 0x7F, 0x08, 0x45, 0x2E, 0x96, 0x33 },
        new byte[] { 0x13, 0x11, 0x7F, 0x08, 0x45, 0x2E, 0x96, 0x33 }, false);
    Console.WriteLine("Done");
    Console.ReadLine();
}

public async static void AESCryptFile
    (string path, byte[] key, byte[] iv, bool encrypt)
{
    // validation
    if (path == null || !File.Exists(path))
        throw new FileNotFoundException("Path does not exist");
    if (key == null || key.Length == 0)
        throw new ArgumentNullException("Password is null");
    if (iv == null || iv.Length < 8)
        throw new ArgumentException("IV is null or under 8 bytes long");

    // in and out stream for files
    FileStream fs = new FileStream
        (path, FileMode.Open, FileAccess.ReadWrite, FileShare.None);

    // initialize aes with safe hash and mode
    RijndaelManaged algo = new RijndaelManaged();
    Rfc2898DeriveBytes hashed = new Rfc2898DeriveBytes(key, iv, 25000);
    algo.Key = hashed.GetBytes(32);
    algo.IV = hashed.GetBytes(16);
    algo.Padding = PaddingMode.PKCS7;
    algo.Mode = CipherMode.CFB;

    // mediator stream for cryptography
    CryptoStream cs = new CryptoStream(fs,
        encrypt ? algo.CreateEncryptor() : algo.CreateDecryptor(),
        encrypt ? CryptoStreamMode.Write : CryptoStreamMode.Read);

    // main file transfer and crypto
    await Task.Run(
        () => readCryptWrite(new FileInfo(path).Length, fs, cs, encrypt));
}

private static void readCryptWrite(long fileSize, FileStream fs,
    CryptoStream cs, bool encrypt)
{
    // 1 MB of buffer zone allocation
    byte[] buffer = new byte[1048576];
    int nextBlockSize;
    long processedSize = 0;
    // loop while there are more to read
    while ((nextBlockSize = encrypt ? fs.Read(buffer, 0, buffer.Length) :
        cs.Read(buffer, 0, buffer.Length)) > 0)
    {
        // set pointer back to last written space
        fs.Seek(processedSize, SeekOrigin.Begin);
        // write out to file
        if (encrypt) cs.Write(buffer, 0, nextBlockSize);
        else fs.Write(buffer, 0, nextBlockSize);
        // set progress
        processedSize = fs.Position;
        SetProgress?.Invoke((int)((double)processedSize / fileSize * 100));
        if (nextBlockSize != buffer.Length) break;
    }
    // close streams
    cs.Clear();
    cs.Close();
    fs.Close();
}

The input I used: Long English file

Upvotes: 0

Views: 481

Answers (1)

canton7
canton7

Reputation: 42235

This was the sort of thing I was trying to describe in the comments, and it works.

I haven't bothered to do Rfc2898DeriveBytes on the key, check the existence of the file, etc. It does however randomly generate an IV, and writes it to the end of the file (turned out to be easier than writing it to the start).

private static void EncryptFile(string path, byte[] key)
{
    byte[] iv = new byte[16];
    // private static readonly RNGCryptoServiceProvider rngcsp = new RNGCryptoServiceProvider();
    rngcsp.GetBytes(iv);

    using (var fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
    {
        byte[] buffer = new byte[1024];
        long readPos = 0;
        long writePos = 0;
        long readLength = fs.Length;

        using (var aes = new RijndaelManaged()
        {
            Key = key,
            IV = iv,
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CFB,
        })
        using (var cs = new CryptoStream(fs, aes.CreateEncryptor(), CryptoStreamMode.Write))
        {
            while (readPos < readLength)
            {
                fs.Position = readPos;
                int bytesRead = fs.Read(buffer, 0, buffer.Length);
                readPos = fs.Position;

                fs.Position = writePos;
                cs.Write(buffer, 0, bytesRead);
                writePos = fs.Position;
            }

            // In older versions of .NET, CryptoStream doesn't have a ctor
            // with 'leaveOpen', so we have to do this instead.
            cs.FlushFinalBlock();

            // Write the IV to the end of the file
            fs.Write(iv, 0, iv.Length);
        }
    }
}

And to decrypt:

private static void DecryptFile(string path, byte[] key)
{
    using (var fs = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
    {
        byte[] buffer = new byte[1024];
        byte[] iv = new byte[16];
        long readPos = 0;
        long writePos = 0;
        long readLength = fs.Length - iv.Length;

        // IV is the last 16 bytes
        if (fs.Length < iv.Length)
            throw new IOException("File is too short");
        fs.Position = readLength;
        fs.Read(iv, 0, iv.Length);
        fs.SetLength(readLength);

        using (var aes = new RijndaelManaged()
        {
            Key = key,
            IV = iv,
            Padding = PaddingMode.PKCS7,
            Mode = CipherMode.CFB,
        })
        using (var cs = new CryptoStream(fs, aes.CreateDecryptor(), CryptoStreamMode.Read))
        {
            while (readPos < readLength)
            {
                fs.Position = readPos;
                int bytesRead = cs.Read(buffer, 0, buffer.Length);
                readPos = fs.Position;

                fs.Position = writePos;
                fs.Write(buffer, 0, bytesRead);
                writePos = fs.Position;
            }

            // Trim the padding
            fs.SetLength(writePos);
        }
    }
}

Upvotes: 1

Related Questions