Outlook拒绝来自CryptProtectData()的密码

时间:2018-06-27 14:55:09

标签: c# c++ outlook

我正在开发一种使用PRF文件导入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个字节。

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

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

谢谢!

3 个答案:

答案 0 :(得分:0)

此行看起来可疑:

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

从C#调用crypt时:

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

GetBytesCall实际上是在调整“ MyPassword”中所有字符的字节数组(从Md),但没有空终止字节。

因此,当您调用strlen时,您可能会从数据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);

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

答案 1 :(得分:0)

我猜测问题是您无法将使用CryptProtectData生成的密码导入Outlook。而且,密码长度不同可能是原因。

这就是我的想法。 我认为Outlook用于加密密码的加密方法不同于CryptProtectData。为了解密由CryptProtectData加密的任何内容,您需要与用于加密的计算机相同。请参见MSDN上的备注。但是,Outlook支持使用同一凭据从不同的计算机进行访问。当Outlook尝试解密密码时,它假定密码是由其选择的方法生成的。如果发现加密方法因算法或加密方式不同而异,则无法解密密码并显示错误。

为了让您直接将密码导入注册表,您可能需要弄清楚Outlook如何加密用户输入的密码。

答案 2 :(得分:0)

尝试在密码字节数组中添加一些零:

...
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密码。