views:

380

answers:

4

Hi all,
we have a project written in Delphi that we want to convert to C#. Problem is that we have some passwords and settings that are encrypted and written into the registry. When we need a specified password we get it from the registry and decrypt it so we can use it. For the conversion into C# we have to do it the same way so that the application can also be used by users that have the old version and want to upgrade it.
Here is the code we use to encrypt/decrypt strings in Delphi:

unit uCrypt;

interface

function EncryptString(strPlaintext, strPassword : String) : String;
function DecryptString(strEncryptedText, strPassword : String) : String;

implementation

uses
  DCPcrypt2, DCPblockciphers, DCPdes, DCPmd5;
const
  CRYPT_KEY = '1q2w3e4r5t6z7u8';

function EncryptString(strPlaintext) : String;
var
  cipher           : TDCP_3des;
  strEncryptedText : String;
begin
  if strPlaintext <> '' then
  begin
    try
      cipher := TDCP_3des.Create(nil);
      try
        cipher.InitStr(CRYPT_KEY, TDCP_md5);
        strEncryptedText := cipher.EncryptString(strPlaintext);
      finally
        cipher.Free;
      end;
    except
      strEncryptedText := '';
    end;
  end;

  Result := strEncryptedText;
end;

function DecryptString(strEncryptedText) : String;
var
  cipher           : TDCP_3des;
  strDecryptedText : String;
begin
  if strEncryptedText <> '' then
  begin
    try
      cipher := TDCP_3des.Create(nil);
      try
        cipher.InitStr(CRYPT_KEY, TDCP_md5);
        strDecryptedText := cipher.DecryptString(strEncryptedText);
      finally
        cipher.Free;
      end;
    except
      strDecryptedText := '';
    end;
  end;

  Result := strDecryptedText;
end;
end.

So for example when we want to encrypt the string asdf1234 we get the result WcOb/iKo4g8=.
We now want to decrypt that string in C#. Here is what we tried to do:

public static void Main(string[] args)
{
    string Encrypted = "WcOb/iKo4g8=";
    string Password = "1q2w3e4r5t6z7u8";
    string DecryptedString = DecryptString(Encrypted, Password);
}

public static string DecryptString(string Message, string Passphrase)
{
    byte[] Results;
    System.Text.UTF8Encoding UTF8 = new System.Text.UTF8Encoding();

    // Step 1. We hash the passphrase using MD5
    // We use the MD5 hash generator as the result is a 128 bit byte array
    // which is a valid length for the TripleDES encoder we use below

    MD5CryptoServiceProvider HashProvider = new MD5CryptoServiceProvider();
    byte[] TDESKey = HashProvider.ComputeHash(UTF8.GetBytes(Passphrase));

    // Step 2. Create a new TripleDESCryptoServiceProvider object
    TripleDESCryptoServiceProvider TDESAlgorithm = new TripleDESCryptoServiceProvider();

    // Step 3. Setup the decoder
    TDESAlgorithm.Key = TDESKey;
    TDESAlgorithm.Mode = CipherMode.ECB;
    TDESAlgorithm.Padding = PaddingMode.None;

    // Step 4. Convert the input string to a byte[]
    byte[] DataToDecrypt = Convert.FromBase64String(Message);

    // Step 5. Attempt to decrypt the string
    try
    {
        ICryptoTransform Decryptor = TDESAlgorithm.CreateDecryptor();
        Results = Decryptor.TransformFinalBlock(DataToDecrypt, 0, DataToDecrypt.Length);
    }
    finally
    {
        // Clear the TripleDes and Hashprovider services of any sensitive information
        TDESAlgorithm.Clear();
        HashProvider.Clear();
    }

    // Step 6. Return the decrypted string in UTF8 format
    return UTF8.GetString(Results);
}

Well the result differs from the expected result. After we call DecryptString() we expect to get asdf1234but we get something else.
Does anyone have an idea of how to decrypt that correctly?
Thanks in advance
Simon

EDIT:
Ok, thank you all for your suggestions. We couldn't find out how to do it all in C# so we decided to take our fall back version, using a Delphi DLL with P/Invoke, as it was suggested.

+1  A: 

I would recommend a different approach. You should P/Invoke the original code to read the passwords back the first time, and then re save using the .net code. This way you can avoid the issues with the different encryption routines in the two platforms.

Preet Sangha
This is our fallback. We already do have a Delphi DLL that we can P/Invoke. But it would be better to have it all in the same C# code.
Simon Linder
Is this not a migration issue? Once you've migrated all your customers then the delphi DLL could be dropped.
Preet Sangha
Be cannot assure when and if all the customers are migrated.
Simon Linder
A: 

Looking through the DCP source code it looks like InitStr() initializes the 3DES cipher in CBC-mode with either IV=EncryptECB(0000000000000000) or IV=EncryptECB(FFFFFFFFFFFFFFFF) (depending on an IFDEF).

Using those I still cannot reproduce the values from the Delphi-code, however.

I would suggest you debug through the code on each side and note exactly how the strings are converted to bytes and which value the code assigns to the derived key and the IV.

Rasmus Faber
Unfortunately I'm not the Delphi developer and he is not here today, but I will check it tomorrow.
Simon Linder
@Simon: any news on this?
Jeroen Pluimers
@Rasmus: Couldn't find anything while debugging, so we decided to take a Delphi DLL and use P/Invoke.
Simon Linder
+1  A: 

Something differs between your Delphi and C# implementations - somewhere.

I suggest you run the decryption against test data in both languages and output your intermediates at each step of the process: see where they diverge. In particular, you need to check out the derived 3DES key and the byte array passed as input to the cipher.

crazyscot
Unfortunately I'm not the Delphi developer and he is not here today, but I will check it tomorrow.
Simon Linder
A: 

DES is a block cipher. And padding is required to perform encryption and decryption operations. If the source data to encrypt is not a multiple of 64 bits in length, padding is requred for encryption. If you don't pad data, you will get unexpected results depending on what padding scheme is default here and there.

So, if you can, you should reencrypt all passwords in Delphi having them padded before encryption. Popular padding scheme is to append 0x80 to the data and append as many 0x00 as needed to make data size a multiple of 8 bytes. You will then be able to strip padding after decryption.

FractalizeR
Can't do. The new C# implementation of the application will replace the old Delphi implementation that cannot be changed anymore.
Simon Linder
You just need to reencrypt old passwords. And then replace Delphi implementation. If you have some users base, you can ask them to change passwords once you moved to C#. If that's impossible, try to pad data in .NET with zeroes to 8 bytes before decrypt/encrypt operation.
FractalizeR