1

I'm a beginner of C# programming. Recently I created a simple file encrypting program and a decrypting program. First I took a file into a FileStream and altered the each byte of the file according to a password. Here's what my code

Code of the Encrypter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace Encrypter
{
    class Program
    {
        static void Main(string[] args)
        {         
            //Actual code starts from here
            string file_path = @args[0];

            string[] splited_filepath = file_path.Split('\\');
            string file_name = splited_filepath[splited_filepath.GetLength(0) - 1];

            FileStream file;
            if (File.Exists(file_path))
            {
                file = new FileStream(file_path, FileMode.Open);
                if (!file.CanWrite)
                {
                    Console.WriteLine("The file exists, but not writable!");
                    return;
                    //goto exit;
                }
            }
            else
            {
                Console.WriteLine("The file does not exist!\n{0}", file_path);
                return;
                //goto exit;
            }

            Console.Write("Enter the password : ");
            string password = Console.ReadLine();

            if(password == "")
            {
                Console.WriteLine("Password Cannot be empty!");
                return;
            }

            try
            {
                while(file.Position < file.Length)
                { 
                    int b_input = file.ReadByte();
                    file.Position--;
                    file.WriteByte(encrypt_byte(b_input,password,file.Position));
                    file.Position++;
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
                //goto exit;
            }

            file.Close();
            string encrypted_file_name = base64_enc(file_name) + compute_hash(password, new MD5CryptoServiceProvider());
            string encrypted_file_path = create_encrypted_file_path(encrypted_file_name, splited_filepath);

            try
            {
                File.Move(file_path, encrypted_file_path);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }
            //exit:;
        }

        static string create_encrypted_file_path(string enc_filename, string[] splited_fp)
        {
            string output = "";
            for(int a = 0; a < splited_fp.GetLength(0) - 1; a++)
            {
                output += (splited_fp[a] + "\\");
            }
            output += enc_filename;
            return output;
        }

        //Encrypting method starts here
        static byte encrypt_byte(int input, string pass, long pos)
        {
            char[] pass_char = pass.ToArray();
            int char_pos = Convert.ToInt32(pos % pass_char.GetLength(0));
            byte output = Convert.ToByte(
                (input + Convert.ToInt32(pass_char[char_pos])) % 256
                );
            return output;
        }

        static string compute_hash(string input,HashAlgorithm algorithm)
        {
            return BitConverter.ToString(algorithm.ComputeHash(Encoding.UTF8.GetBytes(input)));
        }

        //base64 encoding method
        static string base64_enc(string input)
        {
            return Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
        }
    }
}

Code of the decrypter

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace Decrypter
{
    class Program
    {
        static void Main(string[] args)
        {
            //Actual code starts from here
            string file_path = args[0];

            string[] splited_filepath = file_path.Split('\\');
            string file_name = splited_filepath[splited_filepath.GetLength(0) - 1];

            string encrypted_filename = file_name.Substring(0, file_name.Length - 47);
            string hashed_password = file_name.Substring(file_name.Length - 47);

            FileStream file;
            if (File.Exists(file_path))
            {
                file = new FileStream(file_path, FileMode.Open);
                if (!file.CanWrite)
                {
                    Console.WriteLine("The file exists, but not writable!");
                    return;
                    //goto exit;
                }
            }
            else
            {
                Console.WriteLine("The file does not exist!\n{0}", file_path);
                Console.ReadLine();
                return;
                //goto exit;
            }

            Console.Write("Enter the password : ");
            string password = Console.ReadLine();
            if(password == "")
            {
                Console.WriteLine("Password cannot be empty!");
                return;
            }

            //Varify the password
            if(compute_hash(password,new MD5CryptoServiceProvider()) != hashed_password)
            {
                Console.WriteLine(compute_hash(password, new MD5CryptoServiceProvider()));
                Console.WriteLine(hashed_password);
                Console.WriteLine("Invalid password!");
                return;
            }

            try
            {
                while(file.Position < file.Length)
                {                
                    int b_input = file.ReadByte();
                    file.Position--;
                    file.WriteByte(decrypt_byte(b_input, password, file.Position));
                    file.Position++;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.ReadLine();
                return;
                //goto exit;
            }
            file.Close();
            string decrypted_filename = base64_dec(encrypted_filename);
            string decrypted_filepath = create_decrypted_file_path(decrypted_filename, splited_filepath);

            try
            {
                File.Move(file_path, decrypted_filepath);
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return;
            }

            //exit:;
        }

        //Decrypting method starts here
        static byte decrypt_byte(int input, string pass, long pos)
        {
            char[] pass_char = pass.ToArray();
            int char_pos = Convert.ToInt32(pos % pass_char.GetLength(0));
            byte output = Convert.ToByte(
                get_output_byte(input - Convert.ToInt32(pass_char[char_pos]))
                );
            return output;
        }

        //a simple method to get the numaric value of the output byte
        static int get_output_byte(int number)
        {
            if (number < 0) return 256 + number;
            else return number;
        }

        static string compute_hash(string input, HashAlgorithm algorithm)
        {
            return BitConverter.ToString(algorithm.ComputeHash(Encoding.UTF8.GetBytes(input)));
        }

        //base64 decoding method
        static string base64_dec(string input)
        {
            return Encoding.UTF8.GetString(Convert.FromBase64String(input));
        }

        static string create_decrypted_file_path(string enc_filename, string[] splited_fp)
        {
            string output = "";
            for (int a = 0; a < splited_fp.GetLength(0) - 1; a++)
            {
                output += (splited_fp[a] + "\\");
            }
            output += enc_filename;
            return output;
        }
    }
}

The problem is both of these programs are considerably slow and it takes hours to encrypt or decrypt large files. Any suggestions to speed up the process?

Buddhima Z
  • 21
  • 3
  • 1
    The obvious thing to do is to not be working a *single* byte at a time. For simple (but memory intensive) you may want to consider [`File.ReadAllBytes`](https://learn.microsoft.com/en-us/dotnet/api/system.io.file.readallbytes?view=netframework-4.7.2) and doing your actual work inside the in-memory array instead. – Damien_The_Unbeliever Feb 25 '19 at 11:06
  • There is a good answer here on tips how to improve the performance: https://stackoverflow.com/questions/12545533/performance-of-filestreams-write-vs-writebyte-on-an-ienumerablebyte – Erndob Feb 25 '19 at 11:07
  • @Damien_The_Unbeliever I tried that. It was much faster than this but it gave me OutOfMemory Exception when I tried to encrypt larger files – Buddhima Z Feb 25 '19 at 11:10
  • 1
    you could read just portions of files.... lets say read 10000 bytes, encrypt, continue... – Mat Feb 25 '19 at 11:15

1 Answers1

2

This is about the worst possible way I can think of to use a FileStream in a loop:

int b_input = file.ReadByte();
file.Position--;
file.WriteByte(encrypt_byte(b_input,password,file.Position));
file.Position++;

Okay, the first line isn't bad. The second line causes the stream to flush its write buffer and invalidate its read buffer. Then we write a new byte and adjust the position again, causing another flush and invalidation (and causes us to skip every other byte to boot, because the WriteByte already updated the file position).

Simple solution is to use File.ReadAllBytes and just operate in memory. However, as you yourself observed, this doesn't work well for large files.

Instead, you should work with a reasonably sized buffer (say var buffer = new byte[4096];) and then use file.Read to read the file in chunks1. It's better if you can also use a separate stream pointing at a second file for the write side so that you still benefit from buffers on both sides.

You have to maintain your own count of the position within the file rather than relying on the file's Position property, and your loop should terminate when Read returns 0 (but make sure you're always paying attention to that value and only process that much of the buffer).

Finally, close both streams, delete the old file and move/rename the new file to replace it.


Note that this sort of "encryption" is suitable for toy usage but shouldn't be seriously used for anything. You're already aware of the System.Security.Cryptography namespace. For any serious encryption work, you should be looking for existing classes in there, not rolling your own.


1Consider also using Async variants. Most of the time will still be spent doing I/O. Blocking a thread whilst that's happening doesn't add much value to the system.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • Thank you for your help. I appreciate it. And yes I'm not going to use this for serious encryption. I'm just new to programming and wrote this code for fun. – Buddhima Z Feb 26 '19 at 03:44