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.