c# - 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 字节。
我的猜测是这些丢失的字节来自我没有使用的特定熵,甚至是我缺少的一些细节。
任何关于正确方向的提示都会有很大帮助。
谢谢!
解决方案
这条线看起来很可疑:
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);
我不确定这是否会解决您来回传递密码的所有问题,但上述方法是在托管代码和本机代码之间传递字节数组的更好方法。
推荐阅读
- python - python dataframe:显示一系列事件
- avro - 具有默认对象数组的 AVRO 模式
- python - 连接数据框中的两行
- python - Python 类:避免在函数中多次使用“self”参数
- java - 将 jsonb 数据从 Java 保存到 Postgres
- phpunit - 在 Laravel 中重现随机测试(使用 Faker)
- c# - 对 C# 后端的 MVC 请求中可变数量的输入(来自 db)?
- java - 如何从 Android 中的字节数组列表创建文件?
- python - 无法使用 powershell 运行 Jupyter
- java - 如何使用@Transient 在@ManyToOne 对象上强制生成Querydsl 路径