How to convert CryptAcquireContext to .NET 8 using System.Security.Cryptography methods

33 Views Asked by At

I am needing to decrypt a file that was encrypted using the advapi32.dll. I am needing to write a decryption method using .NET 8 and the in built System.Security.Cryptography methods using C# as this will need to used frequently. Unfortunately, we are not able to re-write the encryption logic as this is done by an external 3rd party and I would rather not have any reliance on the advapi32.dll moving forward.

This is the code we currently use for decrypting the file using advapi32.dll

#Region "Constants"
    Private Const lngALG_CLASS_DATA_ENCRYPT As Integer = 24576
    Private Const lngALG_CLASS_HASH As Integer = 32768
    Private Const lngALG_SID_MD5 As Integer = 3
    Private Const lngALG_SID_RC4 As Integer = 1
    Private Const lngALG_TYPE_ANY As Integer = 0
    Private Const lngALG_TYPE_STREAM As Integer = 2048
    Private Const lngCALG_MD5 As Integer = ((lngALG_CLASS_HASH Or lngALG_TYPE_ANY) Or lngALG_SID_MD5)
    Private Const lngCALG_RC4 As Integer = ((lngALG_CLASS_DATA_ENCRYPT Or lngALG_TYPE_STREAM) Or lngALG_SID_RC4)
    Private Const lngCRYPT_NEWKEYSET As Integer = 8
    Private Const lngENCRYPT_ALGORITHM As Integer = lngCALG_RC4
    Private Const lngPROV_RSA_FULL As Integer = 1
    Private Const strKEY_CONTAINER As String = "TestString"
    Private Const strSERVICE_PROVIDER As String = "Microsoft Base Cryptographic Provider v1.0"
#End Region

#Region "Declarations"
    Private Declare Function CryptAcquireContext Lib "advapi32.dll" Alias "CryptAcquireContextA" (ByRef phProv As Integer, ByVal pszContainer As String, ByVal pszProvider As String, ByVal dwProvType As Integer, ByVal dwFlags As Integer) As Integer
    Private Declare Function CryptCreateHash Lib "advapi32.dll" (ByVal hProv As Integer, ByVal Algid As Integer, ByVal hKey As Integer, ByVal dwFlags As Integer, ByRef phHash As Integer) As Integer
    Private Declare Function CryptHashData Lib "advapi32.dll" (ByVal hHash As Integer, ByVal pbData As String, ByVal dwDataLen As Integer, ByVal dwFlags As Integer) As Integer
    Private Declare Function CryptDeriveKey Lib "advapi32.dll" (ByVal hProv As Integer, ByVal Algid As Integer, ByVal hBaseData As Integer, ByVal dwFlags As Integer, ByRef phKey As Integer) As Integer
    Private Declare Function CryptDestroyHash Lib "advapi32.dll" (ByVal hHash As Integer) As Integer
    Private Declare Function CryptEncrypt Lib "advapi32.dll" (ByVal hKey As Integer, ByVal hHash As Integer, ByVal Final As Integer, ByVal dwFlags As Integer, ByVal pbData As String, ByRef pdwDataLen As Integer, ByVal dwBufLen As Integer) As Integer
    Private Declare Function CryptDestroyKey Lib "advapi32.dll" (ByVal hKey As Integer) As Integer
    Private Declare Function CryptReleaseContext Lib "advapi32.dll" (ByVal hProv As Integer, ByVal dwFlags As Integer) As Integer
    Private Declare Function CryptDecrypt Lib "advapi32.dll" (ByVal hKey As Integer, ByVal hHash As Integer, ByVal Final As Integer, ByVal dwFlags As Integer, ByVal pbData As String, ByRef pdwDataLen As Integer) As Integer
#End Region

    Public Shared Function Encrypt(ByVal strValue As String, ByVal strPassword As String) As String
        Return Encrypt_Or_Decrypt(strValue, strPassword, True)
    End Function

    Public Shared Function Decrypt(ByVal strValue As String, ByVal strPassword As String) As String
        Return Encrypt_Or_Decrypt(strValue, strPassword, False)
    End Function

    Private Shared Function Encrypt_Or_Decrypt(ByVal strValue As String, ByVal strKey As String, ByVal blnEncrypt As Boolean) As String
        Dim lngCryptProv As Integer
        Dim lngHash As Integer
        Dim lngKey As Integer
        Dim lngLength As Integer

        Try
            If CryptAcquireContext(lngCryptProv, strKEY_CONTAINER, strSERVICE_PROVIDER, lngPROV_RSA_FULL, lngCRYPT_NEWKEYSET) = 0 Then
                If CryptAcquireContext(lngCryptProv, strKEY_CONTAINER, strSERVICE_PROVIDER, lngPROV_RSA_FULL, 0) = 0 Then
                    Throw New Exception("Error during CryptAcquireContext for a new key container." & vbCrLf & "A container with this name probably already exists.")
                End If
            End If

            If CryptCreateHash(lngCryptProv, lngCALG_MD5, 0, 0, lngHash) = 0 Then
                Throw New Exception("Could not create a Hash Object (CryptCreateHash API)")
            End If

            If CryptHashData(lngHash, strKey, strKey.Length, 0) = 0 Then
                Throw New Exception("Could not calculate a Hash Value (CryptHashData API)")
            End If

            If CryptDeriveKey(lngCryptProv, lngENCRYPT_ALGORITHM, lngHash, 0, lngKey) = 0 Then
                Throw New Exception("Could not create a session key (CryptDeriveKey API)")
            End If

            lngLength = strValue.Length

            If blnEncrypt Then
                If CryptEncrypt(lngKey, 0, 1, 0, strValue, lngLength, lngLength) = 0 Then
                    Throw New Exception("Error during CryptEncrypt.")
                End If
            Else
                If CryptDecrypt(lngKey, 0, 1, 0, strValue, lngLength) = 0 Then
                    Throw New Exception("Error during CryptDecrypt.")
                End If
            End If

            Return strValue.Substring(0, lngLength)

        Finally
            ' Ignore errors here
            Try
                If lngKey <> 0 Then
                    CryptDestroyKey(lngKey)
                End If

                If lngHash <> 0 Then
                    CryptDestroyHash(lngHash)
                End If

                If lngCryptProv <> 0 Then
                    CryptReleaseContext(lngCryptProv, 0)
                End If
            Catch
            End Try
        End Try
    End Function

My understanding of the logic is that it uses MD5 to hash the password and then derive a key using RC4 algorithm which is then used to decrypt the file. I think I know how to has the password using the below logic but cannot see for certain that this is valid

using (var md5 = MD5.Create())
{
    var hash = md5.ComputeHash(System.Text.Encoding.ASCII.GetBytes(key));
}

At this point I am a bit stumped as cannot see anything in the System.Security.Cryptography namespace for RC4 to derive the key, or, how I then use it to decrypt the file

1

There are 1 best solutions below

0
Aaron Gibson On

this should cover it I think

Hash the password using MD5. RC4 is not supported in .NET. This provides CreateDecryptor and CreateEncryptor methods required for CryptoStream. Decrypt the file using the key and RC4. You just need to replace filePath and password with yours when calling the DecryptFile method.

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

public class FileDecryptor
{
    private const string strKEY_CONTAINER = "TestString";
    private const string strSERVICE_PROVIDER = "Microsoft Base Cryptographic Provider v1.0";

    public static string DecryptFile(string filePath, string password)
    {
        using (var md5 = MD5.Create())
        {
            byte[] hash = md5.ComputeHash(Encoding.ASCII.GetBytes(password));
            
            using (var rc4 = new RC4())
            {
                rc4.Key = hash;
                rc4.IV = new byte[16]; // RC4 doesn't use IV, but we need to set it for compatibility

                using (var inputFile = new FileStream(filePath, FileMode.Open))
                using (var outputFile = new MemoryStream())
                using (var cryptoStream = new CryptoStream(inputFile, rc4.CreateDecryptor(), CryptoStreamMode.Read))
                {
                    cryptoStream.CopyTo(outputFile);
                    return Encoding.UTF8.GetString(outputFile.ToArray());
                }
            }
        }
    }
}

// Implementation of RC4 algorithm
public class RC4 : SymmetricAlgorithm
{
    public RC4()
    {
        KeySize = 128;
        BlockSize = 64;
        FeedbackSize = 64;
        LegalBlockSizesValue = new KeySizes[] { new KeySizes(64, 64, 0) };
        LegalKeySizesValue = new KeySizes[] { new KeySizes(40, 128, 8) };
    }

    public override ICryptoTransform CreateDecryptor(byte[] rgbKey, byte[]? rgbIV)
    {
        return new RC4Transform(rgbKey);
    }

    public override ICryptoTransform CreateEncryptor(byte[] rgbKey, byte[]? rgbIV)
    {
        return new RC4Transform(rgbKey);
    }

    public override void GenerateIV()
    {
        IV = new byte[8];
    }

    public override void GenerateKey()
    {
        Key = new byte[16];
    }
}

internal class RC4Transform : ICryptoTransform
{
    private byte[] s;
    private int x;
    private int y;

    public RC4Transform(byte[] key)
    {
        s = new byte[256];
        for (int i = 0; i < 256; i++)
        {
            s[i] = (byte)i;
        }

        int j = 0;
        for (int i = 0; i < 256; i++)
        {
            j = (j + key[i % key.Length] + s[i]) & 255;
            Swap(s, i, j);
        }

        x = 0;
        y = 0;
    }

    public int InputBlockSize => 1;

    public int OutputBlockSize => 1;

    public bool CanTransformMultipleBlocks => true;

    public bool CanReuseTransform => true;

    public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset)
    {
        for (int i = 0; i < inputCount; i++)
        {
            outputBuffer[outputOffset + i] = (byte)(inputBuffer[inputOffset + i] ^ GenerateNextKeystreamByte());
        }
        return inputCount;
    }

    public byte[] TransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
    {
        byte[] outputBuffer = new byte[inputCount];
        TransformBlock(inputBuffer, inputOffset, inputCount, outputBuffer, 0);
        return outputBuffer;
    }

    public void Dispose()
    {
    }

    private byte GenerateNextKeystreamByte()
    {
        x = (x + 1) & 255;
        y = (y + s[x]) & 255;
        Swap(s, x, y);
        return s[(s[x] + s[y]) & 255];
    }

    private static void Swap(byte[] array, int i, int j)
    {
        byte temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
}