1

I'm trying to convert this code in pascal to c#. No compilation errors and the method works fine with short string (4 or 5 characters). All methods and functions are equivalents, but my code throw this exception :

System.OverflowException: Value was either too large or too small for a character .

This is the StackTrace:

in System.Convert.ToChar(Int32 value) in ConsoleApplication3.Program.Encrypt(Boolean encrypt, String word, Int32 startKey, Int32 multKey, Int32 addKey) on c:\Users\TRS\Documents\Visual Studio 2013\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs:line 29 .

This is the Pascal code:

function TGenericsF.Encrypter(Encrypt: WordBool; Source: AnsiString;
  StartKey, MultKey, AddKey: Integer): AnsiString;
 {$R-} {$Q-}
 var Counter: LongInt;
   S: AnsiString;
   Ret: AnsiString;
   begin
     S := Source;
     Ret := '';
     for Counter := 1 to Length(S) do
     begin
       if Encrypt then
        begin
         Ret := Ret + AnsiChar(Ord(S[Counter]) xor (StartKey shr 8));
         StartKey := (Ord(Ret[Counter]) + StartKey) * MultKey + AddKey;
        end
     else
       begin
         Ret := Ret + AnsiChar(Ord(S[Counter]) xor (StartKey shr 8));
         StartKey := (Ord(S[Counter]) + StartKey) * MultKey + AddKey;
       end;
    end;
  Result := Ret;
end;

And this is my equivalent C# code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication3
{
   class Program
   {
    static void Main(string[] args)
    {
        string username = Encrypt(true, "Administrator", 8, 12, 16);
        Console.WriteLine(username);
        Console.ReadKey();
    }

    public static string Encrypt(bool encrypt, string word, int startKey, int multKey, int addKey)
    {
         string encryptedWord = string.Empty;

         for (int i = 0; i < word.Length; i++)
         {
             if(encrypt)
             {

                 encryptedWord += Convert.ToChar(word[i] ^ (startKey >> 8));
                 startKey = (((int)encryptedWord[i]) + startKey) * multKey + addKey;

             }
             else
             {
                 encryptedWord += Convert.ToChar(word[i] ^ (startKey >> 8));
                 startKey = (((int)word[i]) + startKey) * multKey + addKey;
             }
         }

        return encryptedWord;
    }
}
}

Thanks so much.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Júlio Murta
  • 555
  • 2
  • 15
  • 1
    where does the error occur? can you narrow down the scope of your question with an example of variable values and the line of erroneous code? – Sayse May 22 '14 at 14:23
  • Use some breakpoints to see what values `word[i] ^ (startKey >> 8)` is evaluating to. – Bmo May 22 '14 at 14:26
  • Hi Sayse. The error occur in these lines `encryptedWord += Convert.ToChar(word[i] ^ (startKey >> 8));` when the argument word has more than 5 character. But i don't know why in pascal the code works fine and in c# the code throw an exception. =( – Júlio Murta May 22 '14 at 14:27
  • Can't you use the built in .Net cryptographic libraries? – Tarik May 23 '14 at 14:28

7 Answers7

8

The first step towards tackling this problem is to recognise that encryption operates on byte arrays, and returns byte arrays. There is no place for strings here. Text encodings simply obscure the arithmetic of encryption. If you need to encrypt a string you must first convert it to a byte array using some well-defined text encoding.

So, let's re-write the Delphi code with that strategy. It looks like this:

{$R-} {$Q-}
function EncryptDecrypt(Encrypt: Boolean; const Source: TBytes;
  StartKey, MultKey, AddKey: Integer): TBytes;
var
  i: Integer;
begin
  SetLength(Result, Length(Source));
  for i := low(Source) to high(Source) do
  begin
    if Encrypt then
    begin
      Result[i] := Source[i] xor (StartKey shr 8);
      StartKey := (Result[i] + StartKey) * MultKey + AddKey;
    end
    else
    begin
      Result[i] := Source[i] xor (StartKey shr 8);
      StartKey := (Source[i] + StartKey) * MultKey + AddKey;
    end;
  end;
end;

Replicating this in C# is easy enough. We just need to make sure that we allow overflow in the same way as does the Delphi code. That involves using unchecked. The code runs like this:

public static byte[] EncryptDecrypt(bool Encrypt, byte[] Source,
    int StartKey, int MultKey, int AddKey)
{
    byte[] Dest = new byte[Source.Length];
    for (int i = 0; i < Source.Length; i++)
    {
        if (Encrypt)
        {
            unchecked
            {
                Dest[i] = (byte) (Source[i] ^ (StartKey >> 8));
                StartKey = (Dest[i] + StartKey) * MultKey + AddKey;
            }
        }
        else
        {
            unchecked
            {
                Dest[i] = (byte) (Source[i] ^ (StartKey >> 8));
                StartKey = (Source[i] + StartKey) * MultKey + AddKey;
            }
        }
    }
    return Dest;
}

My somewhat cursory tests indicate that:

  1. The byte array Delphi code behaves identically to the Delphi code in the question.
  2. The C# code behaves identically to the Delphi code.
  3. Both versions of the code code can faithfully decrypt encrypted arrays.

Your code was leading to errors because the arithmetic was checked for overflows. Your arithmetic overflowed the int data type and because it executes in a checked context, that overflow leads to an error. The documentation has details: http://msdn.microsoft.com/en-us/library/khy08726.aspx

The unchecked keyword suppresses that error and allows the overflow to occur. The Delphi code uses {$Q-} to achieve the same effect.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
1

Pascal's AnsiChar is an eight-bit value. Any underflow or overflow is probably allowed.

You probably want to write the C# code to use byte arrays instead of characters and strings, as C# characters are 16 bits.

If the code depends on overflows/underflows working, using bytes will fix it.

David Crowell
  • 3,711
  • 21
  • 28
1

As David Crowell correctly pointed out, AnsiChar is 1 byte value. So you need to change this:

encryptedWord += Convert.ToChar(word[i] ^ (startKey >> 8));

into something like this:

encryptedWord += (char)((byte)(word[i] ^ (startKey >> 8)) & 0xFF);

Also, your code in C# is quite inefficient. I would do it like this:

public static string Encrypt(bool encrypt, string word, int startKey, int multKey, int addKey)
{
    try
    {
        StringBuilder encryptedWord = new StringBuilder(word.Length);
        for (int i = 0; i < word.Length; i++)
        {
            encryptedWord.Append((char)((byte)(word[i] ^ (startKey >> 8)) & 0xFF));
            if(encrypt)
                startKey = (((int)encryptedWord[i]) + startKey) * multKey + addKey;
            else
                startKey = (((int)word[i]) + startKey) * multKey + addKey;
        }
        return encryptedWord.ToString();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }    
}

Update David Heffernan is right in his both comments:

Sigh. Yet another encryption question that operates on strings rather than byte arrays.

and

The fundamental problem with this code is that it tries to stuff byte arrays into UTF-16 encoded strings. I appreciate that the code in the question does the same, but this really is the crux of the matter.

If word contains some char with value > 255, then the result produced by C# code will differ from what Pascal code will produce. To solve this problem, you need to work on byte arrays. Something like this should do fine:

private static byte[] StringToBytes(string str)
{
    byte[] bytes = new byte[str.Length * sizeof(char)];
    System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

private static string BytesToString(byte[] bytes)
{
    char[] chars = new char[bytes.Length / sizeof(char)];
    System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
    return new string(chars);
}

public static string Encrypt(bool encrypt, string word, int startKey, int multKey, int addKey)
{
    try
    {
        byte[] source = StringToBytes(word);
        byte[] result = new byte[source.Length];
        for (int i = 0; i < source.Length; ++i)
        {
            result[i] = (byte)((word[i] ^ (startKey >> 8)) & 0xFF);
            if (encrypt)
                startKey = ((result[i]) + startKey) * multKey + addKey;
            else
                startKey = ((word[i]) + startKey) * multKey + addKey;
        }
        return BytesToString(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;
    }
}

StringToBytes and BytesToString are from here: How do I get a consistent byte representation of strings in C# without manually specifying an encoding?

Community
  • 1
  • 1
Sergey Krusch
  • 1,928
  • 16
  • 17
  • 1
    Correction: AnsiChar is 8 **bits** = **1 byte**. – Disillusioned May 22 '14 at 14:56
  • The fundamental problem with this code is that it tries to stuff byte arrays into UTF-16 encoded strings. I appreciate that the code in the question does the same, but this really is the crux of the matter. – David Heffernan May 22 '14 at 15:23
  • Hm... Yes, David, you are right. Trimming 8 bits doesn't solve the problem. The code in C# is not equivalent of the code in Pascal still (if `word` will contain any char with value > 255). – Sergey Krusch May 22 '14 at 15:30
  • @SergeyKrusch: Operating on chars with value > 255 is out of scope here, since the original code uses `AnsiString` which is made of 8-bit characters. – Tara McGrew May 22 '14 at 15:43
  • Actually, you are wrong. You can convert virtually anything to AnsiString in Delphi. So, you can treat AnsiString as byte array. – Sergey Krusch May 22 '14 at 15:46
  • @SergeyKrusch, it used to be that way, but since D2009 the AnsiString has a codepage payload, and does implicit conversions. – LU RD May 22 '14 at 15:49
  • Yes, but you can still put anything into AnsiString even in D2009 as far as I remember. Anyway, I can't say better than David Heffernan did. So, I will vote for his answer :D – Sergey Krusch May 22 '14 at 16:00
  • I appreciate your edit but it doesn't go far enough. You might get away with encryption using AnsiString in Delphi. Maybe. But I would never attempt to do so. But in C# string cannot be used. That's UTF-16 encoded. You simply have to use `byte[]`. – David Heffernan May 22 '14 at 16:11
  • Yes. But I don't see any point in editing my answer further since it will turn into code that you have already wrote in yours :) – Sergey Krusch May 22 '14 at 16:15
1

The Delphi code is explicitly turning overflow checks off with {$Q-}. If you make sure that overflows are enabled in Delphi, I expect you'll get an overflow error there as well.

This sort of thing is common with encoding routines where they are written in such a way that overlflows are expected but irrelevant to the final outcome and therefore ignored.

C# provides the unchecked option to explicitly disable overflow checking.

However, if you have any other subtle errors this might not produce correct results. So ensure you still test it thoroughly. (See Sergey's answer for a semantic discrepancy in your conversion that will almost certainly break the implementation.)

Community
  • 1
  • 1
Disillusioned
  • 14,635
  • 3
  • 43
  • 77
  • 1
    Even disabling overflow checking will not solve the problem since AnsiChar is one byte value. – Sergey Krusch May 22 '14 at 14:49
  • @Sergey Yes, as I pointed out: there may be other subtle errors and thorough testing is required. (I have no desire to try understand whether the algorithm/implementation is sound.) You've done a good job of addressing the semantic flaw in his conversion. But if a value is overflowing in 16 bits, it will also overflow in 8. – Disillusioned May 22 '14 at 15:10
0
word[i] ^ (startKey >> 8)

will evaluate to 81867, which is too high of a value to convert to char.

Marcus
  • 8,230
  • 11
  • 61
  • 88
0

Convert.ToChar() throws an exception when the value you pass it is too large for a char, whereas in Pascal, casting to AnsiChar just truncates the value. Also note that a C# character is 16 bits, whereas in Pascal it's 8 bits.

You can avoid the exception and make your code work like the Pascal code by masking off the top bits before calling Convert.ToChar in both of the lines that append to encryptedWord, like so:

encryptedWord += Convert.ToChar((word[i] ^ (startKey >> 8)) & 0xff);
Tara McGrew
  • 1,987
  • 19
  • 28
-3

try to use

Char.Parse()

whereas

Convert.ToChar()
angel
  • 322
  • 1
  • 7