Как AES-128 зашифровать строку с помощью пароля в Delphi и расшифровать в C #? - PullRequest
4 голосов
/ 08 февраля 2012

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

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

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

Ответы [ 5 ]

15 голосов
/ 15 февраля 2012

Наконец-то я нашел совместимое решение между 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.

А вот мой код C #:

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);
        }
    }
}
10 голосов
/ 09 февраля 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»).Стоит помнить, что кодировка mypassword UTF-16 отличается от кодировки mypassword UTF-8.

  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 (другие режимы могут быть другим случаем).Если открытый текст представляет собой целое число блоков, количественное определение не требуется или не производится, в противном случае используется стандартное кража CipherText.Если размер открытого текста очень мал (от 1 до 15 байт), кража зашифрованного текста невозможна, и вместо этого используется схема заполнения.Чтобы обеспечить совместимость в отношении квантования блоков, вы можете выбрать следующие варианты:

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

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

5 голосов
/ 08 февраля 2012
  • Не используйте LockBox 3. Это не очень качественная библиотека.
  • Не возвращать зашифрованные данные в «текстовые» строки. Зашифрованные данные - это произвольные последовательности байтов, а не строки (как текстовые данные). Delphi использует «строки с контролируемой длиной» и может хранить практически все, что угодно, но может, но вы можете столкнуться с проблемами передачи строк, содержащих последовательности байтов, которые могут неправильно интерпретироваться другими языками, например, $ 00 приложением C / C ++. .). Если в самой библиотеке используются строки, это признак того, что это некачественная библиотека
  • Не преобразовывайте зашифрованные данные! Когда вы конвертируете зашифрованный ANSIString в Unicode (я думаю, это причина вашего последнего приведения), вы уничтожаете зашифрованное значение. Если вы передадите эту строку, она не будет расшифрована, если не будет применено обратное преобразование, если только она не «с потерями».
1 голос
/ 15 июля 2017

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

Я удалил TNTLxUtils из использования, поскольку он не был нужен (и у меня его не было), и добавил 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.
0 голосов
/ 25 октября 2016

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

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 блока. Я не пробовал, потому что мне достаточно одного блока, но адаптировать код должно быть легко.

...