本文介绍了使用 oldschool .NET 导出 CNG RSA 证书的私钥 (PKCS#8)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
我有一个 PKCS #12 (PFX) 文件,它是一个带有 CNG RSA 密钥的证书,我想导出私钥.
I have a PKCS #12 (PFX) file that is a certificate with CNG RSA key and want to export the private key.
文件为复制样本,密码为:1234
我已经尝试导出 RsaParameters
以及从 CngKey
导出私钥但没有成功 - 操作不支持
.
I already tried to export the RsaParameters
as well as exporting the private key from the CngKey
with no success - operation not supported
.
问题是缺少 CngExportPolicies.AllowPlaintextExport
标志.由于密钥处于最终状态,因此我在使用原生 NCrypt
调用设置标志时也没有成功.
The issue is the missing CngExportPolicies.AllowPlaintextExport
flag. I also had no success in setting the flag with native NCrypt
calls as the key is in a finalized state.
在另一个问题评论(无法导出 RSA 私钥参数,不支持请求的操作),它也指向几行 .NET 核心代码.
There was a hint on the process (export, import & set flag & export) in another questions comment (Cannot export RSA private key parameters, the requested operation is not supported) that also points to a few lines of .NET core code.
我尝试将代码移植到 oldschool C# .NET(例如,没有 Span<T>
),但在以下调用中获得了 invalid argument
:
I tried to port the code to oldschool C# .NET (without Span<T>
for instance) but get a invalid argument
on the following call:
我为完整代码创建了一个存储库,其中包含在 github 上失败的单元测试:https://github.com/lennybacon/CngPfxKeyExport
I created a repository for the complete code with a unit test that fails at github: https://github.com/lennybacon/CngPfxKeyExport
欢迎任何提示我从 .Net Core 转换失败或填充错误的数据或指针,因为有关使用的文档似乎非常罕见...
Any hints where I failed in the conversion from .Net Core or stuffed wrong data or pointers are welcome as documentation on the usage seems to be very rare...
推荐答案
您似乎引入了两个主要的移植错误和一个调用本机方法:
You seem to have introduced two main porting errors and one calling the native method:
1) PbeParams
.
你的:
CoreFX:
你的内存布局是在 CRYPT_PKCS12_PBE_PARAMS 值之后是指向更多数据的指针.CoreFX 版本的布局是直接在 CRYPT_PKCS12_PBE_PARAMS 之后是 8 个字节的占位符用于 salt,这是加密 API 所期望的(因为它不需要 pbSalt).
The layout in memory of yours is that after the CRYPT_PKCS12_PBE_PARAMS value is a pointer to more data. The layout of the CoreFX version is that directly after CRYPT_PKCS12_PBE_PARAMS is 8 bytes of placeholder for the salt, which is what the crypto API expects (since it doesn't take pbSalt).
恢复固定字节rgbSalt[RgbSaltSize]
很重要.
2) NCryptExportKey
的pbOutput
:
你的:
CoreFX:
值得注意的是,CoreFX 版本是 ref byte pbOutput
而你的版本是 ref byte[] pbOutput
,使得值因指针间接而不同.
Notably, the CoreFX version was ref byte pbOutput
and yours is ref byte[] pbOutput
, making the value differ by a pointer indirection.
3) 第一次调用 export 需要 C NULL
,不是有效的指针.
3) The first call to export wants C NULL
, not valid pointer.
将更正后的互操作代码压缩到一个文件中,删除评论和未使用的枚举成员(以减少帖子大小)并修复它(然后简化使用,因为您可以使用 string
(保证
终止符)而不是 ReadOnlySpan
(无终止符保证))在 .NET Framework 4.7.2 上产生此结果:
Squishing your corrected interop code into one file, dropping the comments and unused enum members (for reducing post size) and fixing it up (then simplifying the usage since you can use string
(guaranteed
terminator) instead of ReadOnlySpan<char>
(no terminator guarantee)) yields this on .NET Framework 4.7.2:
internal static class CngEncryptedExport
{
internal const string NCRYPT_PKCS8_PRIVATE_KEY_BLOB = "PKCS8_PRIVATEKEY";
private static readonly byte[] s_pkcs12TripleDesOidBytes =
System.Text.Encoding.ASCII.GetBytes("1.2.840.113549.1.12.1.3");
internal static void Go()
{
using (var cert = new X509Certificate2(s_pfx, PfxPassword, X509KeyStorageFlags.Exportable))
using (RSA rsa = cert.GetRSAPrivateKey())
{
RSACng rsaCng = (RSACng)rsa;
using (CngKey key = rsaCng.Key)
{
Console.WriteLine(key.ExportPolicy);
Console.WriteLine(
Convert.ToBase64String(
ExportPkcs8KeyBlob(key.Handle, "123", 21)));
}
}
}
private static unsafe byte[] ExportPkcs8KeyBlob(
SafeNCryptKeyHandle keyHandle,
string password,
int kdfCount)
{
var pbeParams = new NativeMethods.NCrypt.PbeParams();
NativeMethods.NCrypt.PbeParams* pbeParamsPtr = &pbeParams;
byte[] salt = new byte[NativeMethods.NCrypt.PbeParams.RgbSaltSize];
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
pbeParams.Params.cbSalt = salt.Length;
Marshal.Copy(salt, 0, (IntPtr)pbeParams.rgbSalt, salt.Length);
pbeParams.Params.iIterations = kdfCount;
fixed (char* stringPtr = password)
fixed (byte* oidPtr = s_pkcs12TripleDesOidBytes)
{
NativeMethods.NCrypt.NCryptBuffer* buffers =
stackalloc NativeMethods.NCrypt.NCryptBuffer[3];
buffers[0] = new NativeMethods.NCrypt.NCryptBuffer
{
BufferType = NativeMethods.NCrypt.BufferType.PkcsSecret,
cbBuffer = checked(2 * (password.Length + 1)),
pvBuffer = (IntPtr)stringPtr,
};
if (buffers[0].pvBuffer == IntPtr.Zero)
{
buffers[0].cbBuffer = 0;
}
buffers[1] = new NativeMethods.NCrypt.NCryptBuffer
{
BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgOid,
cbBuffer = s_pkcs12TripleDesOidBytes.Length,
pvBuffer = (IntPtr)oidPtr,
};
buffers[2] = new NativeMethods.NCrypt.NCryptBuffer
{
BufferType = NativeMethods.NCrypt.BufferType.PkcsAlgParam,
cbBuffer = sizeof(NativeMethods.NCrypt.PbeParams),
pvBuffer = (IntPtr)pbeParamsPtr,
};
var desc = new NativeMethods.NCrypt.NCryptBufferDesc
{
cBuffers = 3,
pBuffers = (IntPtr)buffers,
ulVersion = 0,
};
int result = NativeMethods.NCrypt.NCryptExportKey(
keyHandle,
IntPtr.Zero,
NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
ref desc,
null,
0,
out int bytesNeeded,
0);
if (result != 0)
{
throw new Win32Exception(result);
}
byte[] exported = new byte[bytesNeeded];
result = NativeMethods.NCrypt.NCryptExportKey(
keyHandle,
IntPtr.Zero,
NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
ref desc,
exported,
exported.Length,
out bytesNeeded,
0);
if (result != 0)
{
throw new Win32Exception(result);
}
if (bytesNeeded != exported.Length)
{
Array.Resize(ref exported, bytesNeeded);
}
return exported;
}
}
private static class NativeMethods
{
internal static class NCrypt
{
[DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
internal static extern int NCryptExportKey(
SafeNCryptKeyHandle hKey,
IntPtr hExportKey,
string pszBlobType,
ref NCryptBufferDesc pParameterList,
byte[] pbOutput,
int cbOutput,
[Out] out int pcbResult,
int dwFlags);
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct PbeParams
{
internal const int RgbSaltSize = 8;
internal CryptPkcs12PbeParams Params;
internal fixed byte rgbSalt[RgbSaltSize];
}
[StructLayout(LayoutKind.Sequential)]
internal struct CryptPkcs12PbeParams
{
internal int iIterations;
internal int cbSalt;
}
[StructLayout(LayoutKind.Sequential)]
internal struct NCryptBufferDesc
{
public int ulVersion;
public int cBuffers;
public IntPtr pBuffers;
}
[StructLayout(LayoutKind.Sequential)]
internal struct NCryptBuffer
{
public int cbBuffer;
public BufferType BufferType;
public IntPtr pvBuffer;
}
internal enum BufferType
{
PkcsAlgOid = 41,
PkcsAlgParam = 42,
PkcsSecret = 46,
}
}
}
}
这篇关于使用 oldschool .NET 导出 CNG RSA 证书的私钥 (PKCS#8)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!