首页 > 解决方案 > Outlook 拒绝来自 CryptProtectData() 的密码

问题描述

我正在开发一个工具来使用 PRF 文件导入 Outlook 配置文件,但它不导入密码,所以我必须手动将其添加到注册表中。

我花了很多时间阅读、测试和调试这个过程,并设法弄清楚一切是如何工作的,除了 Outlook 拒绝 CryptProtectData() 生成的密码。我尝试使用 C# .NET 包装器 ProtectedData.Protect() 和调用 CryptProtectData() 的 C++ DLL,但均无济于事。

每当我更改注册表上的密码并打开 Outlook 时,它都会显示凭据对话框。如果我输入密码,那么它会成功登录到我的电子邮件。

这是使用 .NET 包装器的 C# 代码:

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 字节的二进制数据,比 Outlook 生成的密码(273 字节)少 39 字节。

这是 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 的 C# 代码:

[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 字节的密码,但仍然不是我从 Outlook 获得的 273 字节。

我的猜测是这些丢失的字节来自我没有使用的特定熵,甚至是我缺少的一些细节。

任何关于正确方向的提示都会有很大帮助。

谢谢!

标签: c#c++outlook

解决方案


这条线看起来很可疑:

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

当您从 C# 调用 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);

我不确定这是否会解决您来回传递密码的所有问题,但上述方法是在托管代码和本机代码之间传递字节数组的更好方法。


推荐阅读