我正在开发一种使用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个字节。
我的猜测是,这些丢失的字节来自我没有使用的特定熵,甚至是我缺少的某些细节。
正确方向上的任何提示都会有很大帮助。
谢谢!
答案 0 :(得分:0)
此行看起来可疑:
DWORD cbDataInput = strlen((char *)pbDataInput)+1;
从C#调用crypt时:
crypt(Encoding.UTF8.GetBytes("MyPassword"));
GetBytesCall
实际上是在调整“ MyPassword”中所有字符的字节数组(从M
到d
),但没有空终止字节。
因此,当您调用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密码。