Как AES-128 зашифровать строку с помощью пароля в Delphi и расшифровать в С#?

Я хотел бы, чтобы AES-128 зашифровал строку в Delphi с помощью пароля. Я хотел бы загрузить это на свой сервер и иметь возможность расшифровать тот же пароль на С#.

В Delphi я использую TurboPower LockBox 3:

function EncryptText_AES_128(input: string; password: string): string;
var
  Codec: TCodec;
  CipherText: AnsiString;
begin
  Codec := TCodec.Create(nil);
  try
    Codec.CryptoLibrary := TCryptographicLibrary.Create(Codec);
    //
    Codec.StreamCipherId := BlockCipher_ProgID;
    Codec.BlockCipherId := Format(AES_ProgId, [128]);
    Codec.ChainModeId := CBC_ProgId;
    //
    Codec.Password := Password;
    Codec.EncryptString(input, CipherText);
    //
    Result := string(CipherText);
  finally
    Codec.Free;
  end;
end;

Как расшифровать результирующую строку на C#? Я могу изменить код Delphi. В производстве пока ничего нет. Я даже не зациклен на использовании LockBox. Но я хотел бы не помещать это в DLL для P/Invoke.

(Мой пример показывает, что моя зашифрованная последовательность сама по себе является строкой. Это не требование для меня. Поток байтов в порядке.)


person Troy    schedule 08.02.2012    source источник
comment
Не совсем понятно, о чем ваш вопрос.   -  person Bill    schedule 08.02.2012
comment
Спасибо, Билл. Я отредактировал вопрос, чтобы сделать его более понятным.   -  person Troy    schedule 08.02.2012
comment
Delphi и LockBox, похоже, имеют мало общего с вашим вопросом о С#. Сосредоточьтесь на AES и всех связанных деталях на языке криптологии.   -  person menjaraz    schedule 08.02.2012
comment
Но мой вопрос: как я могу зашифровать AES-строку в Delphi, а затем расшифровать ее в C#?. Как Delph не имеет отношения к моему вопросу? Я предлагаю свой код в качестве примера того, как я хотел бы шифровать в Delphi. Но я не зациклен на использовании Delphi. Если кто-то может показать, как вы можете выполнить работу другим способом, я весь в ушах.   -  person Troy    schedule 08.02.2012


Ответы (5)


Наконец-то я нашел совместимое решение между Delphi и C# для AES-128. Это также работает на Wine. Вот мой код Delphi:

unit TntLXCryptoUtils;

interface

function AES128_Encrypt(Value, Password: string): string;
function AES128_Decrypt(Value, Password: string): string;

implementation

uses
  SysUtils, Windows, IdCoderMIME, TntLXUtils;

//-------------------------------------------------------------------------------------------------------------------------
//    Base64 Encode/Decode
//-------------------------------------------------------------------------------------------------------------------------

function Base64_Encode(Value: TBytes): string;
var
  Encoder: TIdEncoderMIME;
begin
  Encoder := TIdEncoderMIME.Create(nil);
  try
    Result := Encoder.EncodeBytes(Value);
  finally
    Encoder.Free;
  end;
end;

function Base64_Decode(Value: string): TBytes;
var
  Encoder: TIdDecoderMIME;
begin
  Encoder := TIdDecoderMIME.Create(nil);
  try
    Result := Encoder.DecodeBytes(Value);
  finally
    Encoder.Free;
  end;
end;

//-------------------------------------------------------------------------------------------------------------------------
//    WinCrypt.h
//-------------------------------------------------------------------------------------------------------------------------

type
  HCRYPTPROV  = Cardinal;
  HCRYPTKEY   = Cardinal;
  ALG_ID      = Cardinal;
  HCRYPTHASH  = Cardinal;

const
  _lib_ADVAPI32    = 'ADVAPI32.dll';
  CALG_SHA_256     = 32780;
  CALG_AES_128     = 26126;
  CRYPT_NEWKEYSET  = $00000008;
  PROV_RSA_AES     = 24;
  KP_MODE          = 4;
  CRYPT_MODE_CBC   = 1;

function CryptAcquireContext(var Prov: HCRYPTPROV; Container: PChar; Provider: PChar; ProvType: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptAcquireContextW';
function CryptDeriveKey(Prov: HCRYPTPROV; Algid: ALG_ID; BaseData: HCRYPTHASH; Flags: LongWord; var Key: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDeriveKey';
function CryptSetKeyParam(hKey: HCRYPTKEY; dwParam: LongInt; pbData: PBYTE; dwFlags: LongInt): LongBool stdcall; stdcall; external _lib_ADVAPI32 name 'CryptSetKeyParam';
function CryptEncrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt; BufLen: LongInt): LongBool;stdcall;external _lib_ADVAPI32 name 'CryptEncrypt';
function CryptDecrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDecrypt';
function CryptCreateHash(Prov: HCRYPTPROV; Algid: ALG_ID; Key: HCRYPTKEY; Flags: LongWord; var Hash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptCreateHash';
function CryptHashData(Hash: HCRYPTHASH; Data: PChar; DataLen: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptHashData';
function CryptReleaseContext(hProv: HCRYPTPROV; dwFlags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptReleaseContext';
function CryptDestroyHash(hHash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyHash';
function CryptDestroyKey(hKey: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyKey';

//-------------------------------------------------------------------------------------------------------------------------

{$WARN SYMBOL_PLATFORM OFF}

function __CryptAcquireContext(ProviderType: Integer): HCRYPTPROV;
begin
  if (not CryptAcquireContext(Result, nil, nil, ProviderType, 0)) then
  begin
    if HRESULT(GetLastError) = NTE_BAD_KEYSET then
      Win32Check(CryptAcquireContext(Result, nil, nil, ProviderType, CRYPT_NEWKEYSET))
    else
      RaiseLastOSError;
  end;
end;

function __AES128_DeriveKeyFromPassword(m_hProv: HCRYPTPROV; Password: string): HCRYPTKEY;
var
  hHash: HCRYPTHASH;
  Mode: DWORD;
begin
  Win32Check(CryptCreateHash(m_hProv, CALG_SHA_256, 0, 0, hHash));
  try
    Win32Check(CryptHashData(hHash, PChar(Password), Length(Password) * SizeOf(Char), 0));
    Win32Check(CryptDeriveKey(m_hProv, CALG_AES_128, hHash, 0, Result));
    // Wine uses a different default mode of CRYPT_MODE_EBC
    Mode := CRYPT_MODE_CBC;
    Win32Check(CryptSetKeyParam(Result, KP_MODE, Pointer(@Mode), 0));
  finally
    CryptDestroyHash(hHash);
  end;
end;

function AES128_Encrypt(Value, Password: string): string;
var
  hCProv: HCRYPTPROV;
  hKey: HCRYPTKEY;
  lul_datalen: Integer;
  lul_buflen: Integer;
  Buffer: TBytes;
begin
  Assert(Password <> '');
  if (Value = '') then
    Result := ''
  else begin
    hCProv := __CryptAcquireContext(PROV_RSA_AES);
    try
      hKey := __AES128_DeriveKeyFromPassword(hCProv, Password);
      try
        // allocate buffer space
        lul_datalen := Length(Value) * SizeOf(Char);
        Buffer := TEncoding.Unicode.GetBytes(Value + '        ');
        lul_buflen := Length(Buffer);
        // encrypt to buffer
        Win32Check(CryptEncrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen, lul_buflen));
        SetLength(Buffer, lul_datalen);
        // base 64 result
        Result := Base64_Encode(Buffer);
      finally
        CryptDestroyKey(hKey);
      end;
    finally
      CryptReleaseContext(hCProv, 0);
    end;
  end;
end;

function AES128_Decrypt(Value, Password: string): string;
var
  hCProv: HCRYPTPROV;
  hKey: HCRYPTKEY;
  lul_datalen: Integer;
  Buffer: TBytes;
begin
  Assert(Password <> '');
  if Value = '' then
    Result := ''
  else begin
    hCProv := __CryptAcquireContext(PROV_RSA_AES);
    try
      hKey := __AES128_DeriveKeyFromPassword(hCProv, Password);
      try
        // decode base64
        Buffer := Base64_Decode(Value);
        // allocate buffer space
        lul_datalen := Length(Buffer);
        // decrypt buffer to to string
        Win32Check(CryptDecrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen));
        Result := TEncoding.Unicode.GetString(Buffer, 0, lul_datalen);
      finally
        CryptDestroyKey(hKey);
      end;
    finally
      CryptReleaseContext(hCProv, 0);
    end;
  end;
end;

end.

И вот мой код С#:

public class TntCryptoUtils
{
    private static ICryptoTransform __Get_AES128_Transform(string password, bool AsDecryptor)
    {
        const int KEY_SIZE = 16;
        var sha256CryptoServiceProvider = new SHA256CryptoServiceProvider();
        var hash = sha256CryptoServiceProvider.ComputeHash(Encoding.Unicode.GetBytes(password));
        var key = new byte[KEY_SIZE];
        var iv = new byte[KEY_SIZE];
        Buffer.BlockCopy(hash, 0, key, 0, KEY_SIZE);
        //Buffer.BlockCopy(hash, KEY_SIZE, iv, 0, KEY_SIZE); // On the Windows side, the IV is always 0 (zero)
        //
        if (AsDecryptor)
            return new AesCryptoServiceProvider().CreateDecryptor(key, iv);
        else
            return new AesCryptoServiceProvider().CreateEncryptor(key, iv);
    }

    public static string AES128_Encrypt(string Value, string Password)
    {
        byte[] Buffer = Encoding.Unicode.GetBytes(Value);
        //
        using (ICryptoTransform transform = __Get_AES128_Transform(Password, false))
        {
            byte[] encyptedBlob = transform.TransformFinalBlock(Buffer, 0, Buffer.Length);
            return Convert.ToBase64String(encyptedBlob);
        }
    }

    public static string AES128_Decrypt(string Value, string Password)
    {
        byte[] Buffer = Convert.FromBase64String(Value);
        //
        using (ICryptoTransform transform = __Get_AES128_Transform(Password, true))
        {
            byte[] decyptedBlob = transform.TransformFinalBlock(Buffer, 0, Buffer.Length);
            return Encoding.Unicode.GetString(decyptedBlob);
        }
    }
}
person Troy    schedule 15.02.2012
comment
У меня нет времени просматривать весь код, но я вижу, что вы используете статическую IV со всеми нулями. Должно быть довольно легко использовать случайный IV и добавлять его к зашифрованному тексту перед его кодированием в базе 64. Также обратите внимание, что IV имеет размер не KEY_SIZE байтов, а BlockSize в байтах (в этом примере они, к счастью, равны). В большинстве случаев вы можете запросить у экземпляра алгоритма размер блока. Наконец, вы можете использовать UTF8 вместо UNICODE, тем более, что впоследствии невозможно сжать зашифрованный текст (у вас, вероятно, будет в 2 раза меньше зашифрованного текста, о котором нужно беспокоиться). - person Maarten Bodewes; 16.02.2012

Вопреки любой приманке для троллей, которую вы можете прочитать, LockBox 3 на самом деле является криптографической библиотекой хорошего качества. Соответствие стандартам LB3 безупречно. Где у вас могут возникнуть проблемы с совместимостью с другими языками и библиотеками, так это в отношении параметров, выходящих за рамки стандарта. Если вы используете Lockbox на стороне Delphi, вам просто нужно убедиться, что эти параметры обрабатываются таким же образом на стороне другого языка. Если это невозможно, то следует выбрать другую библиотеку. Ниже я рассмотрю каждый из этих вариантов.

В альтернативных решениях (OpenSSL, CryptoAPI и Eldos) нет ничего плохого. Некоторые из них могут быть черными ящиками. Это может быть проблемой для некоторых людей.

  1. Преобразование пароля в ключ. AES-128 использует 16-байтовый ключ. Кроме того, стандартный механизм генерации ключа из «данных ключа» или «данных пароля» изначально основан на 16-байтовом входном семени. Для совместимости безопаснее сгенерировать двоичный ключ из строкового пароля на стороне Delphi и просто передать двоичный ключ на другую сторону, а не передавать строковый пароль. Это связано с тем, что алгоритм преобразования строкового пароля в двоичный 16-байтовый ключ не соответствует стандарту AES. Тем не менее, вы можете сделать это в любом случае. Когда lockbox получает строковый пароль для инициализации кодека AES-128, он рассматривает полезную нагрузку строки как массив байтов. Если полезная нагрузка ровно 16 байт, то отлично, ее можно передать напрямую алгоритму генерации ключа AES, который указан в стандарте. Если полезная нагрузка строки не равна точно 16 байтам, то полезная нагрузка будет хеширована с помощью SHA-1 для получения 20-байтового хэш-вывода. Младшие 16 байт этого хэша затем передаются стандартной функции генерации ключей AES. Итак, ваши варианты обеспечения интероперабельности в отношении инициализации ключа:

    1.1. Переносите двоичные ключи вместо строковых паролей.

    1.2. Если вариант 1.2 слишком неудобен, то перенесите пароль, но имитируйте тот же алгоритм «пароль-ключ» на другой стороне.

    1.3. Если 1 и 2 по какой-то причине не работают, попробуйте ограничить пароли ровно 16 байтами (8 символов UTF-8 или 16 кодовых точек UTF-16). Это должно быть довольно безопасно, если реализация другого языка наполовину прилична.

  2. UTF-16 против паролей ansi-string/UTF-8 Это не столько вариант, сколько ловушка для молодых игроков. Мы, программисты, склонны думать о "строках" как о "строках". Но это не так. В Delphi 2010 полезная нагрузка строк хранится в кодировке UTF-16LE с размером кодовой единицы 2 байта. Но в других языках, таких как PHP и python, в режиме по умолчанию строки представляют собой однобайтовые кодовые единицы, либо UTF-8, либо что-то, основанное на базе кодовых страниц MS Windows (которую MS называет "ansistring"). Стоит помнить, что кодировка UTF-16 «mypassword» отличается от кодировки UTF-8 «mypassword».

  3. IV установка. Стандарт AES не касается вопроса о том, как настроить вектор инициализации кодека (IV). Размер IV такой же, как размер базового блока. Для AES это 128 бит или 16 байт. При шифровании lockbox создает одноразовый номер размером 16 байт. Этот одноразовый номер становится значением IV и передается в открытом виде в заголовке зашифрованного сообщения. Прочтите документацию по методу/политике другой стороны для инициализации IV. Ваши варианты:

    3.1 Если другая сторона добавляет IV к зашифрованному тексту, то вы милы.

    3.2 В противном случае на другой стороне при расшифровке прочитать первые 16 байт шифртекста самостоятельно, а остаток передать чужому кодеку. Перед расшифровкой сообщите вам иностранный кодек, что такое IV (при условии, что его API способен на это).

  4. Квантование блока Размер блока AES составляет 16 байт. Когда открытое текстовое сообщение не является точно кратным 16 байтам, необходимо что-то сделать, чтобы сделать его кратным целому числу. Эта процедура называется блочным квантованием и не рассматривается в стандарте, но оставлена ​​на усмотрение реализации. Многие реализации будут использовать блочное заполнение. Стандартной схемы заполнения блоков нет, и есть из чего выбирать. LockBox не использует заполнение блока для CBC (другие режимы могут быть другим случаем). Если открытый текст представляет собой целое число блоков, квантизация не требуется и не выполняется, в противном случае используется стандартный перехват шифротекста. Если размер открытого текста очень мал (от 1 до 15 байт), кража зашифрованного текста невозможна, и вместо этого используется схема заполнения. Чтобы обеспечить интероперабельность в отношении блочного квантования, вы можете:

    4.1 Проверьте свою документацию на наличие стороннего кодека в отношении блочного квантования (это может относиться к заголовку «заполнение сообщений»). Если чужой кодек использует кражу зашифрованного текста, то вы милы (только убедитесь, что нет коротких сообщений).

    4.2 В противном случае вы можете сделать свой собственный отступ. Что касается защищенного хранилища, то оно ничего не делает с сообщениями, которые уже находятся в целых блоках. Вполне вероятно, что у иностранного кодека такая же политика, но опять же вам нужно проверить документацию на иностранный кодек.

person Sean B. Durkin    schedule 09.02.2012
comment
Отличный ответ. Я также только что столкнулся с ситуацией, когда мне нужно зашифровать что-то, что может расшифровать другой инструмент. Мы можем выбрать инструмент дешифрования, но он должен быть чем-то стандартным и легко доступным, поэтому мы выбираем OpenSSL. Я изучаю, может ли LB3 зашифровать что-то в AES, что может расшифровать OpenSSL. Этот ответ дает мне много знаний, которых у меня раньше не было. - person Jon Robertson; 09.02.2012
comment
Большое спасибо, Шон, за действительно конструктивный ответ. Это как бы подтверждает мое подозрение, что реальная проблема заключается в преобразовании пароля в 128-битный ключ последовательно между платформами. Ваша информация помогает мне в моем путешествии по выяснению этого. В этом шифровании так много всего, что трудно не разобрать. Я хочу, чтобы было простое решение для всего этого! Спасибо, что помогли мне стать на правильный путь. Теперь, если бы кто-нибудь мог просто указать мне пример кода, демонстрирующий это! ;) - person Troy; 09.02.2012
comment
РЖУ НЕ МОГУ! Вы являетесь автором этой библиотеки, поэтому неудивительно, что вы считаете ее хорошего качества. Пользователи могут думать совсем иначе. Более того, вы украли имя и даже пытались сменить лицензию, когда не имели на это права - я бы не стал доверять никакому защитному коду от того, кому наплевать на чужие права. - person ; 09.02.2012
comment
Шон, твоя информация великолепна. Но после часа прополки кровавых подробностей я сдался. Наверное, я простой смертный, когда дело доходит до криптографии. Я решил установить Delphi XE2 и скомпилировать dll x64 с моими двумя функциями (шифровать/дешифровать строку с паролем). Тогда я подумал, может быть, есть простая dll, которая имеет эти 2 простые функции, хорошего качества и имеет 32-битную и 64-битную версии. Вы знаете о таком? - person Troy; 09.02.2012
comment
ldsandon, если вы не считаете LockBox 3 хорошего качества, можете конкретно сказать, в чем проблема? Пока что он у меня на первом месте. Я заметил, что у них есть модульные тесты. Возможно, проблемы, с которыми вы столкнулись, были решены. - person Troy; 09.02.2012
comment
Вы также можете выполнить поиск здесь и найти то, что люди нашли о Lockbox 3. Возможно, проблемы были решены, но я переключился на библиотеки, которые не доставили мне проблем. Мне приходится общаться со многими различными внешними системами, и у меня действительно нет времени на отладку библиотеки. - person ; 10.02.2012
comment
Трой, если вам не нужна совместимость с C#, тогда LB3 должен быть самым простым решением. Если вам требуется совместимость с C#, то пока используйте OpenSSL или MS CryptAPI. Я посмотрю на расширение оболочки OpenSLL LB3, чтобы включить шифрование/дешифрование, но это может занять пару недель. Прямо сейчас LB3 предоставляет только функциональность подписи/проверки OpenSSL. - person Sean B. Durkin; 10.02.2012
comment
О, и еще одна маленькая мысль. Если вы можете контролировать обе стороны, я предлагаю выбрать режим потоковой передачи ключей, а не CBC. В режимах потоковой передачи ключей, таких как CFB, квантование блоков становится не проблемой, поскольку последний блок может быть безопасно усечен. Это лучше для межплатформенной совместимости. - person Sean B. Durkin; 10.02.2012
comment
Я посмотрел на OpenSSL, нашел оболочку Delphi, но застрял на 20 вещах, которые мне нужно было изучить, чтобы выполнить работу. Нет примеров (в Delphi) о том, как это сделать. Я склоняюсь к созданию собственной dll с LB3 и использованию XE2 для создания 64-битной версии. Но я все еще ищу готовую dll, которую я могу p/вызвать из С# и использовать из Delphi, которая проста и выполняет свою работу. Я бы предпочел один вызов функции для шифрования и один вызов функции для расшифровки! Я надеюсь найти тот, который имеет хорошую репутацию и поставляется как в 32-битной, так и в 64-битной версиях. Знаете какой-нибудь? - person Troy; 10.02.2012
comment
Да, я сочувствую. OpenSSL может быть очень сложным. Я мало знаю о С#. Может ли С# вызывать DLL, созданную в Delphi? Если это так, это может быть решением. Кстати, Lockbox3 может работать как с 32-битной, так и с 64-битной системой. - person Sean B. Durkin; 10.02.2012
comment
Я считаю, что С# может вызывать любую DLL через P/Invoke. - person Troy; 14.02.2012
comment
Если вы хотите попробовать решение DLL и вам нужна помощь, создайте DLL (Delphi) с API, как вам нравится, и я напишу реализацию для вас, чтобы вы могли шифровать со стороны Delphi и расшифровывать с C # боковая сторона. - person Sean B. Durkin; 14.02.2012
comment
Не могли бы вы указать свою принадлежность к LockBox 3 в этом ответе? Я до сих пор не нашел официальной документации (API или что-то еще) для библиотеки, вопросы на GitHub остаются без ответа. Вы по-прежнему поддерживаете LockBox или мы уже можем предположить, что он не набрал обороты? - person Maarten Bodewes; 09.01.2019

  • Не используйте LockBox 3. Это некачественная библиотека.
  • Не возвращайте зашифрованные данные в «текстовые» строки. Зашифрованные данные представляют собой произвольные последовательности байтов, а не строки (как текстовые данные). Delphi использует строки с «контролируемой длиной» и может хранить почти все, что он подрезает, но может, но вы можете столкнуться с проблемами при передаче строк, содержащих последовательности байтов, которые могут быть неправильно интерпретированы другими языками, например, $00 приложением C/C++. .). Если в самой библиотеке используются строки, то это признак того, что библиотека низкого качества.
  • Не преобразовывайте зашифрованные данные! Когда вы конвертируете зашифрованную строку ANSIString в строку Unicode (думаю, это причина вашего последнего приведения), вы уничтожаете зашифрованное значение. Если вы передадите эту строку, она не будет расшифрована, если не будет применено обратное преобразование, если она не «с потерями».
person Community    schedule 08.02.2012
comment
+1 Не знаю о качестве LockBox, но использование строк для последовательностей байтов было довольно нормальным в Delphi до Unicode. Я думаю, из-за простой индексации и управления свободной памятью. Это стало настоящей проблемой с Unicode Delphi только из-за: явных и неявных преобразований строк. - person Marjan Venema; 08.02.2012
comment
Как я уже сказал, я не зациклен на использовании Lockbox. Но что бы вы предложили мне использовать вместо него? Я отредактировал свой вопрос, чтобы показать, что я не застрял в использовании строки для хранения зашифрованных данных. - person Troy; 08.02.2012
comment
Преобразование в моем примере из AnsiString в Unicode выполняется в строке, закодированной в base64. Так что шансов на потерю данных нет. Но опять же, моя точка зрения не мой пример. Мой пример просто показывает возможную отправную точку. - person Troy; 08.02.2012
comment
@Troy: один из вариантов - использовать Windows CryptoAPI (который, вероятно, также будет вызываться C#). Другой вариант — OpenSSL. Библиотеки Delphi можно найти на сайте Eldos SecureBlackBox или, если вам нужна бесплатная версия code.google.com /p/дельфидес - person ; 08.02.2012
comment
Это честно. Я проголосовал за ваш ответ, потому что он только критиковал мой пример, что не было целью моего вопроса. Я ценю твою попытку помочь, и я не чувствую потребности в мести. Я только что нашел ваш ответ бесполезным, что означает отрицательный голос. Больше ничего не предполагается. Извините, если вы считаете иначе. - person Troy; 09.02.2012
comment
ldsandon, я сейчас голосую против из-за вашего грубого комментария. Кажется очевидным, что вы обвинили Шона в понижении голосов. Я прочитал несколько ваших ответов и комментариев. Я считаю, что большинство из них бесполезны. Шон, с другой стороны, опубликовал вдумчивый и полезный ответ. Ваше личное мнение о Шоне или его действиях не отвечает на вопрос ОП. - person Jon Robertson; 09.02.2012
comment
@Troy: если ты не готов выдерживать критику, ты никогда не станешь хорошим программистом. Я указал на проблемы в вашем коде, которые могут сделать зашифрованные данные нечитаемыми другим приложением, написанным на другом языке, насколько я мог видеть из кода, который вы разместили. Были ли они неправы? Нет. Если вы ожидали готовое решение, вам следует попробовать вместо этого кодер напрокат. Поскольку AES является стандартным алгоритмом, обычно проблемы возникают в пользовательском коде или ошибках в библиотеках. Вот почему важно использовать хорошо протестированные библиотеки с хорошей поддержкой. А в безопасности это важнее всего остального. - person ; 09.02.2012
comment
Хорошо, я вижу, что люди здесь используют отрицательные голоса как свой личный метод мести. Stackoverflow действительно становится плохим местом. Я не обвинял никого в отрицательном голосовании - просто сказал, что, когда голосуют против, люди могут объяснить, почему - и отрицательные голоса должны основываться на технических достоинствах ответа, а не на том, что вы обо мне думаете. Вы говорите, что мои ответы бесполезны, но, учитывая, что моя репутация немного выше вашей, похоже, что это не так. Я не отвечаю здесь, чтобы кодировать от чьего-то имени. Мой ответ обычно указывает на то, что я считаю правильным направлением, тогда разработчик должен использовать свой собственный мозг. - person ; 10.02.2012
comment
Я проголосовал против, потому что ваш ответ был бесполезен для ответа на мой конкретный вопрос. Я не воспринял это как критику, так как я просто собрал код примера, толком его не понимая. Вот почему я обратился за советом к сообществу StackOverflow. Я не имею в виду ничего личного под отрицательным голосом. И я ценю информацию ... на случай, если я решу зафиксировать свой пример кода. - person Troy; 10.02.2012
comment
@Troy: вы должны прочитать часто задаваемые вопросы Stackoverflow о понижении голосов. Я мог бы понизить ваш вопрос, потому что он был неясен, и вы не объяснили, что не так между вашим кодом Delphi и C#. Например, где вы застряли? Были ли у вас сообщения об ошибках? Если да, то? Но вместо того, чтобы понизить голосование, потому что вы выглядите новичком — по крайней мере, в шифровании — я просто указал, что может вызвать проблемы в единственном представленном вами коде. Я мог бы написать весь код для вас, но должен ли? Вы не получите большой помощи, если будете отрицать любого, кто попытается вам помочь, если только он не даст вам полный ответ, готовый к использованию. - person ; 10.02.2012
comment
Отлично. Я удалил свой отрицательный голос. Я думаю, я могу быть счастлив быть нейтральным. :) - person Troy; 10.02.2012
comment
Как насчет LockBox 10? Они по-прежнему утверждают, что поддерживают AES, но его нет в их библиотеке. Кроме того, нет буквально никакой документации. И у меня есть ошибки, когда расшифровка работает только в половине случаев (расшифровка работает через раз, одна хорошая, одна плохая, одна хорошая, одна плохая...) - person Jerry Dodge; 10.01.2015

Мне удалось успешно внедрить код Troy's Delphi в 10.2 Tokyo с парой модификаций.

Я убрал TNTLxUtils из Uses за ненадобностью (и у меня его не было) и добавил IdGlobal. Причина использования IdGlobal заключается в том, что вам нужно преобразовать тип TBytes в TIdBytes в функции Base64_Encode и TIBytes обратно в TBytes в Base64_Decode.

Примечание. Этот модуль будет работать только в 32-разрядных приложениях, поскольку он ссылается на 32-разрядный API Windows.

Спасибо, Трой, за то, что указал мне правильное направление для бесплатного метода шифрования, для реализации которого не требуется покупать набор инструментов.

unit CryptoUtils;

interface

function AES128_Encrypt(Value, Password: string): string;
function AES128_Decrypt(Value, Password: string): string;

implementation

uses
  SysUtils, Windows, IdCoderMIME, IdGlobal;

//-------------------------------------------------------------------------------------------------------------------------
//    Base64 Encode/Decode
//-------------------------------------------------------------------------------------------------------------------------

function Base64_Encode(Value: TBytes): string;
var
  Encoder: TIdEncoderMIME;
begin
  Encoder := TIdEncoderMIME.Create(nil);
  try
    Result := Encoder.EncodeBytes(TIdBytes(Value));
  finally
    Encoder.Free;
  end;
end;

function Base64_Decode(Value: string): TBytes;
var
  Encoder: TIdDecoderMIME;
begin
  Encoder := TIdDecoderMIME.Create(nil);
  try
    Result := TBytes(Encoder.DecodeBytes(Value));
  finally
    Encoder.Free;
  end;
end;

//-------------------------------------------------------------------------------------------------------------------------
//    WinCrypt.h
//-------------------------------------------------------------------------------------------------------------------------

type
  HCRYPTPROV  = Cardinal;
  HCRYPTKEY   = Cardinal;
  ALG_ID      = Cardinal;
  HCRYPTHASH  = Cardinal;

const
  _lib_ADVAPI32    = 'ADVAPI32.dll';
  CALG_SHA_256     = 32780;
  CALG_AES_128     = 26126;
  CRYPT_NEWKEYSET  = $00000008;
  PROV_RSA_AES     = 24;
  KP_MODE          = 4;
  CRYPT_MODE_CBC   = 1;

function CryptAcquireContext(var Prov: HCRYPTPROV; Container: PChar; Provider: PChar; ProvType: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptAcquireContextW';
function CryptDeriveKey(Prov: HCRYPTPROV; Algid: ALG_ID; BaseData: HCRYPTHASH; Flags: LongWord; var Key: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDeriveKey';
function CryptSetKeyParam(hKey: HCRYPTKEY; dwParam: LongInt; pbData: PBYTE; dwFlags: LongInt): LongBool stdcall; stdcall; external _lib_ADVAPI32 name 'CryptSetKeyParam';
function CryptEncrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt; BufLen: LongInt): LongBool;stdcall;external _lib_ADVAPI32 name 'CryptEncrypt';
function CryptDecrypt(Key: HCRYPTKEY; Hash: HCRYPTHASH; Final: LongBool; Flags: LongWord; pbData: PBYTE; var Len: LongInt): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDecrypt';
function CryptCreateHash(Prov: HCRYPTPROV; Algid: ALG_ID; Key: HCRYPTKEY; Flags: LongWord; var Hash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptCreateHash';
function CryptHashData(Hash: HCRYPTHASH; Data: PChar; DataLen: LongWord; Flags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptHashData';
function CryptReleaseContext(hProv: HCRYPTPROV; dwFlags: LongWord): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptReleaseContext';
function CryptDestroyHash(hHash: HCRYPTHASH): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyHash';
function CryptDestroyKey(hKey: HCRYPTKEY): LongBool; stdcall; external _lib_ADVAPI32 name 'CryptDestroyKey';

//-------------------------------------------------------------------------------------------------------------------------

{$WARN SYMBOL_PLATFORM OFF}

function __CryptAcquireContext(ProviderType: Integer): HCRYPTPROV;
begin
  if (not CryptAcquireContext(Result, nil, nil, ProviderType, 0)) then
  begin
    if HRESULT(GetLastError) = NTE_BAD_KEYSET then
      Win32Check(CryptAcquireContext(Result, nil, nil, ProviderType, CRYPT_NEWKEYSET))
    else
      RaiseLastOSError;
  end;
end;

function __AES128_DeriveKeyFromPassword(m_hProv: HCRYPTPROV; Password: string): HCRYPTKEY;
var
  hHash: HCRYPTHASH;
  Mode: DWORD;
begin
  Win32Check(CryptCreateHash(m_hProv, CALG_SHA_256, 0, 0, hHash));
  try
    Win32Check(CryptHashData(hHash, PChar(Password), Length(Password) * SizeOf(Char), 0));
    Win32Check(CryptDeriveKey(m_hProv, CALG_AES_128, hHash, 0, Result));
    // Wine uses a different default mode of CRYPT_MODE_EBC
    Mode := CRYPT_MODE_CBC;
    Win32Check(CryptSetKeyParam(Result, KP_MODE, Pointer(@Mode), 0));
  finally
    CryptDestroyHash(hHash);
  end;
end;

function AES128_Encrypt(Value, Password: string): string;
var
  hCProv: HCRYPTPROV;
  hKey: HCRYPTKEY;
  lul_datalen: Integer;
  lul_buflen: Integer;
  Buffer: TBytes;
begin
  Assert(Password <> '');
  if (Value = '') then
    Result := ''
  else begin
    hCProv := __CryptAcquireContext(PROV_RSA_AES);
    try
      hKey := __AES128_DeriveKeyFromPassword(hCProv, Password);
      try
        // allocate buffer space
        lul_datalen := Length(Value) * SizeOf(Char);
        Buffer := TEncoding.Unicode.GetBytes(Value + '        ');
        lul_buflen := Length(Buffer);
        // encrypt to buffer
        Win32Check(CryptEncrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen, lul_buflen));
        SetLength(Buffer, lul_datalen);
        // base 64 result
        Result := Base64_Encode(Buffer);
      finally
        CryptDestroyKey(hKey);
      end;
    finally
      CryptReleaseContext(hCProv, 0);
    end;
  end;
end;

function AES128_Decrypt(Value, Password: string): string;
var
  hCProv: HCRYPTPROV;
  hKey: HCRYPTKEY;
  lul_datalen: Integer;
  Buffer: TBytes;
begin
  Assert(Password <> '');
  if Value = '' then
    Result := ''
  else begin
    hCProv := __CryptAcquireContext(PROV_RSA_AES);
    try
      hKey := __AES128_DeriveKeyFromPassword(hCProv, Password);
      try
        // decode base64
        Buffer := Base64_Decode(Value);
        // allocate buffer space
        lul_datalen := Length(Buffer);
        // decrypt buffer to to string
        Win32Check(CryptDecrypt(hKey, 0, True, 0, @Buffer[0], lul_datalen));
        Result := TEncoding.Unicode.GetString(Buffer, 0, lul_datalen);
      finally
        CryptDestroyKey(hKey);
      end;
    finally
      CryptReleaseContext(hCProv, 0);
    end;
  end;
end;

end.
person Brad Weaver    schedule 15.07.2017
comment
Привет, Брэд, я только что попробовал ваш код в Delphi XE8 в своем 32-битном приложении, но он выдает ошибку при попытке расшифровать строку; Системная ошибка. Код: -2146893819. Неверные данные. - person Jerry Mallett; 02.05.2021

У меня была такая же проблема. Я знаю, что это старая тема, но она мне очень помогла. Я просто оставлю это здесь для протокола.

Function LockBoxDecrypt(Password As String, Data() As Byte) As String

    Dim AesProvider = AesCryptoServiceProvider.Create()
    Dim IV(15) As Byte, PaddedData(15) As Byte

    Array.Copy(Data, 0, IV, 0, 8)
    Array.Copy(Data, 8, PaddedData, 0, Data.Length - 8)

    AesProvider.Key = SHA1.Create().ComputeHash(Encoding.Default.GetBytes(Password)).Take(16).ToArray()
    AesProvider.IV = IV
    AesProvider.Mode = CipherMode.CFB
    AesProvider.Padding = PaddingMode.None

    Return Encoding.Default.GetString(AesProvider.CreateDecryptor().TransformFinalBlock(PaddedData, 0, PaddedData.Length), 0, Data.Length - 8)

End Function

Основываясь на ответе Шона, я предполагаю, что режим следует изменить на CTS при наличии более 1 блока. Я не пробовал, т.к. мне достаточно 1 блока, но адаптировать код должно быть легко.

person Leonardo    schedule 25.10.2016