简体   繁体   中英

Writing equivalent code in Pascal to code C#

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.

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.

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.

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?

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.)

word[i] ^ (startKey >> 8)

的计算结果为81867,该值太高而无法转换为char。

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);

try to use

Char.Parse()

whereas

Convert.ToChar()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM