Я пытаюсь преобразовать этот код в pascal в С#. Нет ошибок компиляции, и метод отлично работает с короткой строкой (4 или 5 символов). Все методы и функции являются эквивалентами, но мой код генерирует это исключение:
System.OverflowException: значение было слишком большим или слишком маленьким для символа.
Это StackTrace
:
в System.Convert.ToChar (значение Int32) в ConsoleApplication3.Program.Encrypt(Boolean encrypt, String word, Int32 startKey, Int32 multKey, Int32 addKey) на c:\Users\TRS\Documents\Visual Studio 2013\Projects\ConsoleApplication3\ConsoleApplication3\Program.cs: строка 29.
Это код Pascal:
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;
И это мой эквивалентный код С#:
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;
}
}
}
Спасибо.
Первым шагом на пути решения этой проблемы является распознавание того, что шифрование работает с байтовыми массивами и возвращает массивы байтов. Здесь нет места для струн. Текстовые кодировки просто затушевывают арифметику шифрования. Если вам нужно зашифровать строку, вы должны сначала преобразовать ее в массив байтов, используя определенную текстовую кодировку.
Итак, позвольте повторно написать код Delphi с этой стратегией. Это выглядит так:
{$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;
Реплицировать это на С# достаточно просто. Нам просто нужно убедиться, что мы допускаем переполнение так же, как и код Delphi. Это предполагает использование unchecked
. Код работает следующим образом:
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;
}
Мои несколько беглых тестов показывают, что:
Ваш код приводил к ошибкам, потому что арифметика была проверена для переполнения. Ваша арифметика переполнила тип данных int
и, поскольку она выполняется в проверенном контексте, это переполнение приводит к ошибке. Документация содержит подробную информацию: http://msdn.microsoft.com/en-us/library/khy08726.aspx
unchecked
ключевое слово подавляет эту ошибку и позволяет переполнение. Для достижения такого же эффекта в коде Delphi используется {$Q-}
.
Как правильно указал Дэвид Кроуэлл, AnsiChar - 1 байт. Поэтому вам нужно изменить это:
encryptedWord += Convert.ToChar(word[i] ^ (startKey >> 8));
в нечто подобное:
encryptedWord += (char)((byte)(word[i] ^ (startKey >> 8)) & 0xFF);
Кроме того, ваш код на С# довольно неэффективен. Я бы сделал это так:
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;
}
}
Обновление Дэвид Хеффернан прав в своих двух комментариях:
Вздох. Еще один вопрос шифрования, который работает с строками, а не с байтовыми массивами.
а также
Основная проблема с этим кодом заключается в том, что он пытается массировать байтовые массивы в кодированные строки UTF-16. Я ценю, что код в вопросе делает то же самое, но это действительно суть дела.
Если word
содержит некоторый символ со значением> 255, то результат, созданный кодом С#, будет отличаться от того, что будет производить код Pascal. Чтобы решить эту проблему, вам необходимо работать с байтовыми массивами. Что-то вроде этого должно преуспеть:
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 и BytesToString отсюда: Как получить согласованное байтовое представление строк в С# без ручного указания кодировки?
Pascal AnsiChar - это восьмибитовое значение. Возможно, разрешено любое недополнение или переполнение.
Вероятно, вы захотите написать код С# для использования байтовых массивов вместо символов и строк, поскольку символы С# равны 16 бит.
Если код зависит от работы переполнения/недоработки, использование байтов исправит его.
Код Delphi явно отключает проверку переполнения с помощью {$Q-}
. Если вы убедитесь, что переполнение включено в Delphi, я ожидаю, что вы также получите ошибку переполнения.
Подобные вещи распространены с подпрограммами кодирования, где они написаны таким образом, что ожидаются наложения, но не имеют отношения к окончательному результату и поэтому игнорируются.
С# предоставляет непроверенную опцию, позволяющую явно отключить проверку переполнения.
Однако, если у вас есть другие тонкие ошибки, это может привести к неправильным результатам. Поэтому убедитесь, что вы все еще тщательно проверяете его. (См. Ответ Сергея для семантического несоответствия в вашем преобразовании, которое почти наверняка нарушит реализацию).
Convert.ToChar()
выдает исключение, когда значение, которое вы передаете, слишком велико для символа, тогда как в Pascal литье в AnsiChar
просто AnsiChar
значение. Также обратите внимание, что символ С# - 16 бит, тогда как в Pascal - 8 бит.
Вы можете избежать исключения и заставить свой код работать как код Pascal, маскируя верхние биты перед вызовом Convert.ToChar
в обеих строках, которые добавляются в encryptedWord
, например:
encryptedWord += Convert.ToChar((word[i] ^ (startKey >> 8)) & 0xff);
word[i] ^ (startKey >> 8)
будет оцениваться до 81867, что слишком велико для преобразования в char.
попробуй использовать
Char.Parse()
в то время как
Convert.ToChar()