⭐ If you would like to buy me a coffee, well thank you very much that is mega kind! : https://www.buymeacoffee.com/honeyvig Hire a web Developer and Designer to upgrade and boost your online presence with cutting edge Technologies
Showing posts with label Encryption. Show all posts
Showing posts with label Encryption. Show all posts

Sunday, April 24, 2022

RSA: Encrypt in .NET & Decrypt in Python

 

So… one of my current projects required the following actions: asymmetrically encrypt a string in .NET using a public key and decrypt it in a python script using a private key.

The problem that I’ve encountered was that, apparently, I couldn’t achieve compatibility between the two exposed classes: RSACryptoServiceProvider and PKCS1_v1_5. To be more specific, the python script couldn’t decrypt the ciphertext even though proper configurations were made and the provided keys were compatible. Additionally, separate encryption-decryption actions worked inside .NET and python but not in-between them.

I wasn’t able to find too much information about this specific problem in the RSAParameters documentation, hence this post.

Solution

Alright, the issue seems to be caused by a difference in endianness between the two classes, when the RSA parameters are provided. PKCS1_v1_5 uses little endian and RSACryptoServiceProvider prefers big endian. In my case, this made the encryption method use a different key than the one I though I specified. Nevertheless, it was more fun to debug because of PKCS which always ensured different ciphertexts.

I fixed this by base64-encoding the exponent and modulus in big-endian format (in python) and then loading them with RSACryptoServiceProvider.FromXmlString() (in .NET).

Working Example

I hardcoded the (N, E, D) parameters for a private key in python and exported the exponent and modulus to be used later for encryption.


# custom base64 encoding
def b64_enc(n, l):
    n = n.to_bytes(l, 'big')
    return base64.b64encode(n)

# fixed a set of keys for testing purposes
N = 26004126751443262055682011081007404548850063543219588539086190001742195632834884763548378850634989264309169823030784372770378521274048211537270851954737597964394738860810397764157069391719551179298507244962912383723776384386127059976543327113777072990654810746825378287761304202032439750301912045623786736128233730798303406858144431081065384988539277630625160727011582345942687126935423502995613920211095965452425548919926951203151483590222152446516520421379279591807660810550784744188433550335950652666201439521115515355539373928576162221297645781251953236644092963307595988040539993067709240004782161131243282208593
E = 65537
D = 844954574014654722486150458473919587206863455991060222377955072839922571984098861772377020041002939383041291761051853484512886782322743892284027026528735139923685801975918062144627908962369108081178131103781404720078456605432924519279933702927938064507063482999903002331319671303661755165294744970869186178561527578261522199503340027952798084625109041630166309505066404215223685733585467434168146932177924040219720383860880583466676764286302300281603021045351842170755190359364339936360197909582974922675680101321863304283607829144759777189360340512230537108705852116021758740440195445732631657876008160876867027543

# construct pair of keys
private_key = RSA.construct((N, E, D))
public_key = private_key.publickey()

# base64-encode parameters in big-endian format
EXP = b64_enc(public_key.e, 3)
MODULUS = b64_enc(public_key.n, 256)

print('EXP:', EXP, 'MODULUS:', MODULUS)

# Output:
# EXP: b'AQAB' MODULUS: b'zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ=='

In .NET (I used C#), there will be something like this:


using System;
using System.Security.Cryptography;
using System.Text;

public class RSACryptoApp
{
    // parameters from the python script (public key)
    private static readonly String EXP = "AQAB";
    private static readonly String MODULUS = "zf4LgceVPvjMLz/pp8exH58AeBrhjLe0k4FRmd59I0k4sH6oug6Z9RfY4FvEFcssBwH1cmWF5/Zen8xbRVRyUnzer6b6cKmlzHFYf0LlbovvYMkW5pdhRcTHK2ijByGtmVgU/CEKEQTy3elpU7ZsHE8D6T1M7L2gmGAxvgldUMRu4l8BPuRyht1a9dA9b6005atpdlkCSc3emXSfyBOBwNE0UicVTVncn9SBjP7bTBGgOKshYnYsqh4BD0I7AU3xdoAsZVWudECX/zVa7uUOk1ooVYjMEyfBngrEDXrmIkAlVruUuj/eWiYwT2vXqByQgDfDvat5IS4i3ywiHAWXUQ==";

    public static void Main(string[] args)
    {
       RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048);
       csp.FromXmlString("<RSAKeyValue><Exponent>" + EXP + "</Exponent><Modulus>" + MODULUS + "</Modulus></RSAKeyValue>");

       // encrypting a string for testing purposes
       byte[] plainText = Encoding.ASCII.GetBytes("Hello from .NET");
       byte[] cipherText = csp.Encrypt(plainText, false);

       Console.WriteLine("Encrypted: " + Convert.ToBase64String(cipherText));

       // Output:
       // Encrypted: F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w==
    }
}

Back to the python script:


cipher = PKCS1_v1_5.new(private_key)

random_generator = Random.new().read
sentinel = random_generator(20)

cipher_text = 'F/agXpfSrs7HSXZz+jVq5no/xyQDXuOiVAG/MOY7WzSlp14vMOTM8TshFiWtegB3+2BZCMOEPLQFFFbxusuCFOYGGJ8yRaV7q985z/UDJVXvbX5ANYqrirobR+c868mY4V33loAt2ZFNXwr+Ubk11my1aJgHmoBem/6yPfoRd9GrZaSQnbJRSa3EDtP+8pXETkF9B98E7KvElrsRTLXEXSBygmeKsyENo5DDcARW+lVVsQuP8wUEGnth9SX4oG8i++gmQKkrv0ep6yFrn05xZJKgpOfRiTTo/Bkh7FxNP2wo7utzhtYkNnvtXaJPWAvqXg93KmNPqg1IsN4P1Swb8w=='

plain_text = cipher.decrypt(base64.b64decode(cipher_text.encode('ASCII')), sentinel)
print('Decrypted:', plain_text.decode('ASCII'))

# Output:
# Decrypted: Hello from .NET

C# Prevent Decompilation by Decrypting Source at Runtime

 The point is: it is rather difficult to make .NET programs run with a key or license; since these can be reverted back to their sourcecode, anyone can alter it or just learn to create fake keys that will be seen as valid.

 

Possible Solution

One way to make an application a little bit more difficult to crack would be to deliver it as a program that decrypts instructions, compiles and runs them only when needed. This way, if someone finds out where the sourcecode is stored, it will still be encrypted and without a key (or license) it is unusable.

We’re kinda writing polymorphic stuff here - AVs won’t be happy; actually…only 2/57 don’t like it, we’re good.

1. Making the Compiler

We’re not really going to reinvent the wheel here - .NET seems to allow us to use the original compiler to produce an Assembly. Just as always, we start with a CodeDomProvider, add a bunch of settings using CompilerParameters and a few sourcecodes.


CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

CompilerParameters parameters = new CompilerParameters();

parameters.GenerateExecutable = true;
parameters.GenerateInMemory = true; // it's still going to generate a file somewhere in AppData (temp)
parameters.TreatWarningsAsErrors = false;
            
// I need these references because the program that I will 'secure'
// is that Form from the photo above that requires a password
parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
parameters.ReferencedAssemblies.Add("System.dll");
parameters.ReferencedAssemblies.Add("System.Drawing.dll");

// getContents() is a method that extracts & decrypts the sourcecodes 
// of the 'secured' application and returns everything as an array of Strings
// in order to be compiled

CompilerResults result = provider.CompileAssemblyFromSource(parameters, getContents());

If you look around, there’s also an article that provides a little bit more detail about how to compile code at runtime using CSharpCodeProvider and ICodeCompiler which are now considered obsolete, but the code is similar.

2. Running the Compiled Assembly

What we’re interested in is result.CompiledAssembly - in order to run it we have to create an instance of the method that serves as entrypoint and then invoking it.

Short note: if the assembly that you’re trying to run belongs to a Console Application and this program has the same project type, you might need to call FreeConsole() and then AllocConsole(). Without recreating the Console there seems to be no output from the compiled assembly.

This is how we can run the compiled code:


Assembly assembly = result.CompiledAssembly;

//taking the entrypoint
MethodInfo methodInfo = assembly.EntryPoint;

// creating an instance
object entryPointInstance = assembly.CreateInstance(methodInfo.Name);

// then invoking it with no arguments (hence the 'null')
methodInfo.Invoke(entryPointInstance, null);

3. Encrypting & Attaching Sourcecodes

This is one of the tough parts - we take the sourcecodes of the files that we want to secure and encrypt them (I use AES with Rijndael’s algorithm) then attach the results at the end of the executable that we’ve been working on at the previous steps.

Here, the content of the executable and each sourcecode are separated by a sequence of 3 FS (File Separator Character). It’s not the clean way to handle this…don’t use it in serious projects; but for this tutorial it should be fine.

FS = 28(dec) = 1C(hex);

The method that I use looks like this:


static void appendContents(String fileName)
{
    // fileName contains the name of the decrypter 
    FileStream fstream = new FileStream(fileName, FileMode.Append);
    
    // attaching the first 3 FS chars
    fstream.WriteByte(CHAR_FS);
    fstream.WriteByte(CHAR_FS);
    fstream.WriteByte(CHAR_FS);

    // grabbing any .cs file (anything that needs to be encrypted and attached)
    string[] sourceFiles = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.cs", SearchOption.AllDirectories);

    // taking each source file
    for (int i = 0; i < sourceFiles.Length; i++)
    {
        byte[] buffer = File.ReadAllBytes(sourceFiles[i]);
                

        // removing UTF8's byte order mark, if needed
        if (buffer.Length > 2 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0XBF)
        {
            // skipping the first 3 bytes
            byte[] newBuffer = new byte[buffer.Length - 3];
            Array.Copy(buffer, 3, newBuffer, 0, buffer.Length - 3);

            // encrypting with a test key
            newBuffer = EncryptMessage(newBuffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

            // writing...
            fstream.Write(newBuffer, 0, newBuffer.Length);
        }
        else
        {
            // same thing as above, but for texts without BOM
            buffer = EncryptMessage(buffer, "abcdabcdabcdabcdabcdabcdabcdabcd");
            fstream.Write(buffer, 0, buffer.Length);
        }

        // more separators!
        fstream.WriteByte(CHAR_FS);
        fstream.WriteByte(CHAR_FS);
        fstream.WriteByte(CHAR_FS);
    }
}

I’ll not add EncryptMessage()’s code here since it’s not related to the actual subject - you can find it below, in the complete sourcecode.

4. Extracting & Decrypting Sourcecodes

Procedure that runs before the whole compile & run thingy - we look for any sequence of 3 FS characters, skip the executable’s content, take the encrypted sourcecode and run it through the decryption method - the result is pure C# code that will be given to the compiler.

Remember to replace the "abcdabcdabc..." decryption key with what the user inputs in order to use the program (like a license) - line 31.


static String[] getContents()
{
    // reads all the bytes found in the running executable's file
    byte[] bytes = File.ReadAllBytes(Assembly.GetEntryAssembly().Location);

    int i = 0;
            
    List<String> sourceFiles = new List<String>();

    // skipping the original executable's data
    for (i = 0; i < bytes.Length - 2; i++)
    {
        // if there are 3 FS characters in a row
        // then there's a source file
        if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
        {
            i += 3;
            break;
        }
    }

    // here I should keep one sourcefile at a time
    List<Byte> sourceFileBuffer = new List<Byte>(4000);

    for (; i < bytes.Length - 2; i++)
    {
        // checking if I reached the end of a sourcefile
        if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
        {
            // TO DO: decrypt with the key given by the user of the program
            sourceFiles.Add(Encoding.Default.GetString(DecryptMessage(sourceFileBuffer.ToArray(), "abcdabcdabcdabcdabcdabcdabcdabcd")));
            sourceFileBuffer.Clear();
            i += 2;
        }
        else
            sourceFileBuffer.Add(bytes[i]);
    }

    // returning the array of sourcecodes
    return sourceFiles.ToArray();
}

Final Notes & Complete Sourcecode

Below you’ll find the sourcecode I ended up with while writing this article. It’s more like a fast way to explain an idea - it needs some “patching”.

In order to actually use it you should split this into 2 programs - one for encrypting and attaching and the other to do the decryption, compilation & execution. You send only the latter one to the user - so he won’t get the encryption key - this or switch to an asymmetric algorithm. Also don’t forget to remove the hardcoded decryption key and ask the user for his own.


using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;

namespace ConsoleApplication1
{
    class Program
    {
        const int CHAR_FS = 28;

        public static byte[] EncryptMessage(byte[] text, string key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;
            aes.Mode = CipherMode.CBC;

            aes.Key = Encoding.Default.GetBytes(key);
            aes.GenerateIV();

            string IV = Encoding.Default.GetString(aes.IV);

            ICryptoTransform AESEncrypt = aes.CreateEncryptor(aes.Key, aes.IV);
            byte[] buffer = text;

            return Encoding.Default.GetBytes(Encoding.Default.GetString(AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length)) + IV);
        }

        public static byte[] DecryptMessage(byte[] text, string key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;
            aes.Mode = CipherMode.CBC;

            aes.Key = Encoding.Default.GetBytes(key);

            byte[] IV = new byte[32];
            Array.Copy(text, text.Length - 32, IV, 0, 32);

            byte[] text2 = new byte[text.Length - 32];
            Array.Copy(text, text2, text2.Length);

            aes.IV = IV;

            ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV);

            return AESDecrypt.TransformFinalBlock(text2, 0, text2.Length);
        }

        static void appendContents(String fileName)
        {
            FileStream fstream = new FileStream(fileName, FileMode.Append);
            fstream.WriteByte(CHAR_FS);
            fstream.WriteByte(CHAR_FS);
            fstream.WriteByte(CHAR_FS);


            string[] sourceFiles = Directory.GetFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "*.cs", SearchOption.AllDirectories);


            for (int i = 0; i < sourceFiles.Length; i++)
            {
                byte[] buffer = File.ReadAllBytes(sourceFiles[i]);
                

                // removing UTF8's byte order mark...
                if (buffer.Length > 2 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0XBF)
                {
                    byte[] newBuffer = new byte[buffer.Length - 3];
                    Array.Copy(buffer, 3, newBuffer, 0, buffer.Length - 3);

                    newBuffer = EncryptMessage(newBuffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

                    fstream.Write(newBuffer, 0, newBuffer.Length);
                }
                else
                {
                    buffer = EncryptMessage(buffer, "abcdabcdabcdabcdabcdabcdabcdabcd");

                    fstream.Write(buffer, 0, buffer.Length);
                }

                fstream.WriteByte(CHAR_FS);
                fstream.WriteByte(CHAR_FS);
                fstream.WriteByte(CHAR_FS);
            }
        }
        static String[] getContents()
        {
            byte[] bytes = File.ReadAllBytes(Assembly.GetEntryAssembly().Location);

            int i = 0;
            
            List<String> sourceFiles = new List<String>();

            for (i = 0; i < bytes.Length - 2; i++)
            {
                if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
                {
                    i += 3;
                    break;
                }
            }

            List<Byte> sourceFileBuffer = new List<Byte>(4000);

            for (; i < bytes.Length - 2; i++)
            {
                if (bytes[i] == bytes[i + 1] && bytes[i + 1] == bytes[i + 2] && bytes[i + 2] == 28)
                {
                    sourceFiles.Add(Encoding.Default.GetString(DecryptMessage(sourceFileBuffer.ToArray(), "abcdabcdabcdabcdabcdabcdabcdabcd")));
                    sourceFileBuffer.Clear();
                    i += 2;
                }
                else
                    sourceFileBuffer.Add(bytes[i]);

            }
            return sourceFiles.ToArray();
        }

        static void Main(string[] args)
        {
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");

            CompilerParameters parameters = new CompilerParameters();

            parameters.GenerateExecutable = true;
            parameters.GenerateInMemory = true;
            parameters.TreatWarningsAsErrors = false;
            

            parameters.ReferencedAssemblies.Add("System.Windows.Forms.dll");
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("System.Drawing.dll");

            if (args.Length > 0)
            {
                appendContents(args[0]);
                return;
            }

            // source-files
            CompilerResults result = provider.CompileAssemblyFromSource(parameters, getContents());


            if (result.Errors.Count > 0)
            {
                foreach (CompilerError er in result.Errors)
                    Console.WriteLine(er.ToString());

                Console.ReadLine();
                return;
            }

            Assembly assembly = result.CompiledAssembly;
            MethodInfo methodInfo = assembly.EntryPoint;

            object entryPointInstance = assembly.CreateInstance(methodInfo.Name);
            methodInfo.Invoke(entryPointInstance, null);
        }
    }
}

 

Wednesday, April 20, 2022

C#/PHP Compatible Encryption (AES256)

 

Finding a way to encrypt messages in C# and decrypting them in PHP or vice versa seems to be a “challenge” for many users. I wrote this tutorial to provide some help with this: below, you can find how to encrypt / decrypt messages in C# / PHP using AES256 with CBC mode.

1.Basic Information

AES 256 with CBC mode requires 3 values: the message, a key (32 bytes long) and an initialization vector (IV). Note that you must use the same IV when encrypting / decrypting a message: otherwise the message is lost. Sending the IV with the message is perfectly safe but it always has to be a random value. Since it has a fixed size, I always place the IV at the end of the encrypted text.

The encrypted messages should be encoded using base64 before being sent.

base64([ENCRYPTED_TEXT][-[–IV-[-][INITIALIZATION_VECTOR])

Encryption steps:
  • encrypt the text
  • add the IV at the end
  • encode everything (base64)
Decryption steps:
  • decode the message
  • get & remove the IV
  • proceed to decrypt

Ok, enough talking, let’s see some code…

2.PHP Encryption/Decryption Code

PHP accepts keys that are not 32 bytes long and simply extends them to the correct length. Well…C# doesn’t, so you’ll have to use a key that is 32 bytes long.

Encryption

function encrypt($text, $pkey)
{
	$key = $pkey;  
	$IV = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_CBC), MCRYPT_RAND); 

	return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, $IV)."-[--IV-[-".$IV); 
}```

<font size="4">Decryption</font>

```php
function decrypt($text, $pkey)
{
	$key = $pkey;   
	$text = base64_decode($text); 
	$IV = substr($text, strrpos($text, "-[--IV-[-") + 9);
	$text = str_replace("-[--IV-[-".$IV, "", $text);

	return rtrim(mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $text, MCRYPT_MODE_CBC, $IV), "\0");
}```

## 3.C# Encryption/Decryption Code

As I said before, C# doesn't accept keys that aren't **32 bytes long** - it will throw an error. Also, many people get tricked here because of the **encoding** (most of the times you have to use **Encoding.Default**).

<font size="4">Encryption</font>

```csharp
public static string EncryptMessage(byte[] text, string key)
{
    RijndaelManaged aes = new RijndaelManaged();
    aes.KeySize = 256;  
    aes.BlockSize = 256;
    aes.Padding = PaddingMode.Zeros;
    aes.Mode = CipherMode.CBC;

    aes.Key = Encoding.Default.GetBytes(key);
    aes.GenerateIV();  

    string IV = ("-[--IV-[-" + Encoding.Default.GetString(aes.IV));

    ICryptoTransform AESEncrypt = aes.CreateEncryptor(aes.Key, aes.IV);
    byte[] buffer = text;

    return
Convert.ToBase64String(Encoding.Default.GetBytes(Encoding.Default.GetString(AESEncrypt.TransformFinalBlock(buffer, 0, buffer.Length)) + IV));

}
Decryption

public static string DecryptMessage(string text, string key)
{
    RijndaelManaged aes = new RijndaelManaged();
    aes.KeySize = 256;
    aes.BlockSize = 256;
    aes.Padding = PaddingMode.Zeros;
    aes.Mode = CipherMode.CBC;

    aes.Key = Encoding.Default.GetBytes(key);

    text = Encoding.Default.GetString(Convert.FromBase64String(text));

    string IV = text;
    IV = IV.Substring(IV.IndexOf("-[--IV-[-") + 9);
    text = text.Replace("-[--IV-[-" + IV, "");

    text = Convert.ToBase64String(Encoding.Default.GetBytes(text));
    aes.IV = Encoding.Default.GetBytes(IV);

    ICryptoTransform AESDecrypt = aes.CreateDecryptor(aes.Key, aes.IV);
    byte[] buffer = Convert.FromBase64String(text);

    return Encoding.Default.GetString(AESDecrypt.TransformFinalBlock(buffer, 0, buffer.Length));
}

Monday, April 18, 2022

C# Password Protected File

 

In this article…I’ll talk about how to protect a file with a password (any file type) - without using ZIP or any other archivers. The file will be encrypted and then ‘attached’ to an executable, which will be later used to decrypt it using the password provided.

Starting information

Initially there’ll be 2 files: the file we want to encrypt, and the decryptor (which is a C# executable). These files will be ‘merged’ together, and when needed, the decryptor will retrieve from its own file the content of the original file.

How to 'pack' the file (steps):

  1. get the content (bytes) of the file we want to protect by password
  2. encrypt it using a good algorithm (I’m using AES)
  3. put everything at the end of the generic decryptor *

* the executable file won’t be affected by what we add.

How to decrypt the file:

  1. the file is appended at the end of the decryptor, so we need to skip as many bytes as the original decryptor's size (when it has no file attached)
  2. load the file’s content into memory -> decrypt it with the password provided
  3. save the content as a new file

1. Creating the decryptor

This part explains how to write the decryptor application: the resulting executable is, what I like to call, the ‘original decryptor’. Its size is exactly 10240 bytes, so after we attach the encrypted file, we need to skip those bytes when we want to decrypt the content.

I made it as a Forms project only for the sake of design - anyway there’s only 1 method that does all the work so it can be easily modified.


using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace decryptor
{
    public partial class DecryptorWnd : Form
    {
        public DecryptorWnd()
        {
            InitializeComponent();
        }

        // hardcoded size of the original decryptor...lazy coder...
        int decryptorSize = 10240;  

        private void decryptButton_Click(object sender, EventArgs e)
        {
            // sending the current process' name and the password provided by the user
            decrypt(Process.GetCurrentProcess().ProcessName, passwordTextBox.Text);
        }

        private void decrypt(string file, string password)
        {
            byte[] key = new byte[32]; // 256 bits key
            Encoding.Default.GetBytes(password).CopyTo(key, 0); // padding with 0

            RijndaelManaged aes = new RijndaelManaged();
            aes.Mode = CipherMode.CBC;
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;

            using (FileStream outputStream = new FileStream("decrypted_" + file, FileMode.Create))
            {
                using (CryptoStream cryptoStream = new CryptoStream(outputStream, aes.CreateDecryptor(key, key), CryptoStreamMode.Write))
                {
                    // reading the content of the current process
                    byte[] buffer = File.ReadAllBytes(file + ".exe");  

                    // skip the original decryptor's size (we don't want to decrypt that!)
                    cryptoStream.Write(buffer, decryptorSize, buffer.Length - decryptorSize);  
                }
            }
        }
    }
}

2. Attaching the file to the decryptor

This requires another executable file that will handle encryption and file attachment. I used a Console Application for this - to be honest I never intended to publish this as a program, I was just testing - and for testing, the console was enough.


using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace FileAppender
{
    class Program
    {
        static MemoryStream mStream = new MemoryStream();

        public static void Main()
        {
            encrypt("SomeFile.txt", "myPassword");
        }

        private static void encrypt(string fileName, string password)
        {
            byte[] key = new byte[32];  // same key (256 bits)
            Encoding.Default.GetBytes(password).CopyTo(key, 0);  // padding with 0 once again

            RijndaelManaged aes = new RijndaelManaged();
            aes.Mode = CipherMode.CBC;
            aes.KeySize = 256;
            aes.BlockSize = 256;
            aes.Padding = PaddingMode.Zeros;

            using (CryptoStream cStream = new CryptoStream(mStream, aes.CreateEncryptor(key, key), CryptoStreamMode.Write))
            {
                // reading the content of the file that requires password protection
                byte[] buffer = File.ReadAllBytes(fileName);

                // encrypting & storing everything in a MemoryStream
                cStream.Write(buffer, 0, buffer.Length);
            }
            append(fileName); // time to append
        }

        private static void append(string file)
        {
            // reading the content of the original decryptor
            byte[] exeBuffer = File.ReadAllBytes("decryptor.exe");  

            // extracting the encrypted content from the MemoryStream
            byte[] appendBuffer = mStream.ToArray() ;

            // this buffer is the 'new' decryptor, that contains the new file
            byte[] finalBuffer = new byte[exeBuffer.Length + appendBuffer.Length];
            exeBuffer.CopyTo(finalBuffer, 0);
            appendBuffer.CopyTo(finalBuffer, exeBuffer.Length);

            // creating 'SomeFile.txt.exe'
            File.WriteAllBytes(file + ".exe", finalBuffer);
        }
    }
}

Note

This code is provided to give you an idea on how to write such a program. This means it’s not optimized, so it can be improved.

 

Sunday, April 17, 2022

C# Safe Encryption/Decryption using DPAPI

 

Data Protection API aka DPAPI is a neat service provided by Windows Operating Systems (newer than Windows 2000) that safely encrypts and decrypts user credentials, using the Triple-DES algorithm.

You have to supply the data as byte array in order to be encrypted / decrypted.

DPAPI uses the user’s key to encrypt / decrypt so anyone who has access to your account can see the original data unless you define an Entropy - see below what that is.

1.What is needed

Before starting, add a reference to System.Security.dll, and include this line in your project:


using System.Security.Cryptography;

For increased security, you can choose an Entropy (which is an additional byte array) to make the encryption safer - this way, users that have access to your Windows account must also know the Entropy used.


readonly byte[] entropy = { 1, 2, 3, 4, 5, 6 }; //the entropy

2.Encryption

One of the functions that come with DPAPI is Protect(), that has 3 arguments. It returns an encrypted version of the message you provide.


private string Encrypt(string text) 
{ 
    // first, convert the text to byte array 
    byte[] originalText = Encoding.Unicode.GetBytes(text); 
    
    // then use Protect() to encrypt your data 
    byte[] encryptedText = ProtectedData.Protect(originalText, entropy, DataProtectionScope.CurrentUser); 
    
    //and return the encrypted message 
    return Convert.ToBase64String(encryptedText); 
}

3.Decryption

Another function that comes with DPAPI is Unprotect(), has 3 parameters and returns the original message, when you supply the encrypted one.


private string Decrypt(string text) 
{ 
    // the encrypted text, converted to byte array 
    byte[] encryptedText = Convert.FromBase64String(text); 
    
    // calling Unprotect() that returns the original text 
    byte[] originalText = ProtectedData.Unprotect(encryptedText, entropy, DataProtectionScope.CurrentUser); 
    
    // finally, returning the result 
    return Encoding.Unicode.GetString(originalText); 
}

4.Errors?

These methods may throw up errors if you try to decrypt a text using a different user than the one you used for encryption.

You can solve this by using DataProtectionScope.LocalMachine instead of DataProtectionScope.CurrentUser, this way any user has the possibility to decrypt the message if he knows the Entropy.