Reputation: 545
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.
Upvotes: 1
Views: 1711
Reputation: 612954
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:
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.
Upvotes: 8
Reputation: 1938
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?
Upvotes: 1
Reputation: 14832
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.)
Upvotes: 1
Reputation: 2027
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);
Upvotes: 0
Reputation: 3813
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.
Upvotes: 1
Reputation: 8669
word[i] ^ (startKey >> 8)
will evaluate to 81867, which is too high of a value to convert to char.
Upvotes: 0