Outlook отказывает пароль от CryptProtectData()

Я разрабатываю инструмент для импорта профилей Outlook с использованием файла PRF, но он не импортирует пароль, поэтому мне нужно вручную добавить его в реестр.

Я потратил много часов на чтение, тестирование и отладку этой процедуры и сумел выяснить, как все работает, за исключением того, что Outlook отказывается от пароля, сгенерированного CryptProtectData(). Я пробовал использовать как С#.NET-оболочку ProtectedData.Protect(), так и DLL-библиотеку C++, вызывающую CryptProtectData(), но безрезультатно.

Всякий раз, когда я меняю пароль в реестре и открываю Outlook, он показывает диалог учетных данных. Если я наберу пароль, он успешно войдет в мой адрес электронной почты.

Вот код С#, использующий оболочку.NET:

RegistryKey RegKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\MyProfile\\9375CFF0413111d3B88A00104B2A6676\\0000000b", true);
Byte[] Password = Encoding.UTF8.GetBytes("MyPassword");
Byte[] EncPassword = ProtectedData.Protect(Password, null, DataProtectionScope.CurrentUser);
RegKey.SetValue("IMAP Password", EncPassword);

Этот код генерирует двоичные данные с 234 байтами, что на 39 байт меньше пароля, сгенерированного Outlook (273 байта).

Вот код C++:

extern "C" DLLEXPORT DATA_BLOB crypt(BYTE *input) {
    DATA_BLOB DataIn;
    DATA_BLOB DataOut;
    BYTE *pbDataInput = input;
    DWORD cbDataInput = strlen((char *)pbDataInput)+1;
    DataIn.pbData = pbDataInput;    
    DataIn.cbData = cbDataInput;

    if( !CryptProtectData(&DataIn, L"IMAP Password", NULL, NULL, NULL, 0, &DataOut) )
    {
        printf("Encryption error");
    }

    return DataOut;
}

Код С#, вызывающий DLL:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DATA_BLOB
{
    public int cbData;
    public IntPtr pbData;
}

[DllImport("MyLib.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern DATA_BLOB crypt(Byte[] input);

(...)

RegistryKey RegKey = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows Messaging Subsystem\\Profiles\\MyProfile\\9375CFF0413111d3B88A00104B2A6676\\0000000b", true);
Byte[] Password = Encoding.UTF8.GetBytes("MyPassword");
DATA_BLOB BlobData = crypt(Encoding.UTF8.GetBytes("MyPassword"));
Byte[] EncPassword = new Byte[BlobData.cbData];
Marshal.Copy(BlobData.pbData, EncPassword, 0, BlobData.cbData);

RegKey.SetValue("IMAP Password", EncPassword);

Этот код генерирует пароль с 256 байтами, но не 273 байта, которые я получаю из Outlook.

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

Любой наконечник в правильном направлении поможет много.

Спасибо!

Ответ 1

Эта строка выглядит подозрительно:

DWORD cbDataInput = strlen((char *)pbDataInput)+1;

Когда вы вызываете crypt из С#:

crypt(Encoding.UTF8.GetBytes("MyPassword"));

GetBytesCall буквально перенастраивает байтовый массив, всех символов в "MyPassword" (M в d), но без нулевого завершающего байта.

Поэтому, когда вы вызываете strlen, вы можете получать больше символов, потребляемых из массива данных blob, input чем вы думаете. Кроме того, в +1 не гарантируется включение нулевого байта.

Изменение crypt функции, чтобы быть:

extern "C" DLLEXPORT DATA_BLOB crypt(BYTE *input, DWORD cbDataInput)
    DATA_BLOB DataIn;
    DATA_BLOB DataOut;
    BYTE *pbDataInput = input;
    DataIn.pbData = pbDataInput;    
    DataIn.cbData = cbDataInput;

И вызовите его следующим образом:

byte[] passwordBytes = Encoding.UTF8.GetBytes("MyPassword");
DATA_BLOB BlobData = crypt(passwordBytes, passwordBytes.length);

Или, если вам нужен нулевой байт:

byte[] passwordBytes = Encoding.UTF8.GetBytes("MyPassword" + "\0");
DATA_BLOB BlobData = crypt(passwordBytes, passwordBytes.Length);

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

Ответ 2

Я предполагаю, что проблема в том, что вы не можете импортировать пароль, сгенерированный с помощью CryptProtectData, в Outlook. И, разная длина пароля может быть причиной.

Вот что я думаю. Я думаю, что метод шифрования, который Outlook использует для шифрования пароля, отличается от метода CryptProtectData. Чтобы расшифровать все, что зашифровано CryptProtectData, вы должны находиться на том же компьютере, что и для шифрования. Смотрите замечания на MSDN. Но Outlook поддерживает доступ с разных компьютеров с одинаковыми учетными данными. Поскольку Outlook пытается расшифровать пароль, он предполагает, что пароль генерируется выбранным им методом. Если он обнаружил, что метод шифрования отличается из-за другого алгоритма или энтропии, он не сможет расшифровать пароль и покажет ошибку.

Чтобы импортировать пароль непосредственно в реестр, вам может понадобиться выяснить, как Outlook шифрует пароль, который пользователь вводит для своего пароля.

Ответ 3

Попробуйте добавить несколько нулей в массив байтов пароля:

...
crypt(Encoding.UTF8.GetBytes("MyPassword").AddZeros());
...

private static IEnumerable<byte> AddZeros(this IEnumerable<byte> pass)
{
    foreach (var b in pass)
    {
        yield return b;
        yield return 0;
    }

    yield return 0;
    yield return 0;
}

Я использовал его с паролем POP3, но я не уверен насчет пароля IMAP.