寻求cryptuiapi.h和wincrypt.h的官方C#包装器及证书迁移方案
Hey there! Let's tackle your problem of migrating those MFC certificate operations to a C# command-line app. First, let's address your core question about official NuGet wrappers for cryptuiapi.h and wincrypt.h, then break down your four proposed solutions to find the best fit.
Core Answer: Official .NET Support & NuGet Options
Most of the Win32 CryptoAPI functions you're using have first-class support in .NET's System.Security.Cryptography.X509Certificates namespace (no extra NuGet needed—it's part of the base class library). The big exception is CryptUIDlgSelectCertificateFromStore—since it's a UI-focused function, .NET doesn't include a managed wrapper for it out of the box.
There aren't any official Microsoft NuGet packages that wrap the full cryptuiapi.h or wincrypt.h surface area, but you don't need to copy the reference source code wholesale. Here's how to approach each of your options:
1. Use X509Store + Partial P/Invoke (Best Recommendation)
This is the cleanest path. Use .NET's built-in classes for 99% of your operations, and only write a small P/Invoke wrapper for the missing CryptUIDlgSelectCertificateFromStore function. Let's map your existing functions to .NET equivalents:
CertOpenStore/CertCloseStore→X509Store.Open()andX509Store.Close()(or useusingstatements for automatic disposal)CertEnumCertificatesInStore→X509Store.Certificates(a collection you can iterate over directly)CertVerifyRevocation→X509Chain.Build(X509Certificate2)(check the chain'sChainStatusproperty for revocation-related issues)CertVerifyTimeValidity→ CompareX509Certificate2.NotBeforeandX509Certificate2.NotAfteragainst the current timeCryptAcquireCertificatePrivateKey→X509Certificate2.GetRSAPrivateKey(),GetDSAPrivateKey(), orGetECDsaPrivateKey()(typed, managed private key access)CryptSignCertificate→ Use the private key'sSignData()method (e.g.,rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))CertFindCertificateInStore→X509Store.Certificates.Find(X509FindType.FindByIssuerName, issuerName, validOnly)CertGetIntendedKeyUsage→ AccessX509Certificate2.Extensionsto retrieve theKeyUsageExtensionorEnhancedKeyUsageExtensionCertNameToStr→ UseX509Certificate2.SubjectName.NameorX500DistinguishedName.Format()for human-readable distinguished namesCertDuplicateCertificateContext→ Create a newX509Certificate2instance from an existing one (e.g.,new X509Certificate2(existingCert.RawData))CertAddCertificateContextToStore→X509Store.Add(X509Certificate2)
For the missing CryptUIDlgSelectCertificateFromStore, here's a minimal P/Invoke implementation you can drop into your project (no need to wrap all CryptoAPI functions):
using System; using System.Runtime.InteropServices; using System.Security.Cryptography.X509Certificates; public static class CryptUiHelper { [DllImport("cryptui.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CryptUIDlgSelectCertificateFromStore( IntPtr hCertStore, IntPtr hwndParent, string pszTitle, string pszDisplayString, uint dwDontUseColumn, uint dwFlags, IntPtr pvReserved); [DllImport("crypt32.dll", SetLastError = true)] private static extern bool CertFreeCertificateContext(IntPtr pCertContext); public static X509Certificate2 SelectCertificateFromStore(X509Store store) { IntPtr certContext = CryptUIDlgSelectCertificateFromStore( store.StoreHandle, IntPtr.Zero, // No parent window for console apps; pass a handle if needed "Select Certificate", "Choose a certificate from the store", 0, 0, IntPtr.Zero); if (certContext == IntPtr.Zero) { // Handle user cancellation or error (check Marshal.GetLastWin32Error() if needed) return null; } try { return new X509Certificate2(certContext); } finally { // Ensure the unmanaged certificate context is freed CertFreeCertificateContext(certContext); } } }
Call this helper method after opening your X509Store to replicate the "select certificate" dialog from your MFC app.
2. Full P/Invoke (Not Recommended)
Writing P/Invoke wrappers for every CryptoAPI function you use is technically possible, but it's tedious and error-prone. You'd have to manually handle memory management, type conversions, and error checking—all things .NET's managed classes already solve for you. Stick with option 1 instead.
3. Reuse Unmanaged C++ Code (Avoid If Possible)
If your main MFC project is huge and hard to extract code from, this path will likely create more headaches than it solves. You'd have to build a separate unmanaged DLL, handle interop between C# and C++, and deal with potential versioning or deployment issues. Not worth the effort when .NET covers almost all your needs.
4. C++/CLR Wrapper (Not Ideal)
The Unicode/ASCII encoding issues you're worried about are very real here. C++/CLR interop can get messy with string conversions, and you'd still be tied to unmanaged code. This middle ground doesn't offer much benefit over option 1.
Final Takeaway
Go with option 1: use .NET's built-in X509Store and related classes for all your core certificate operations, and add a small P/Invoke wrapper just for CryptUIDlgSelectCertificateFromStore. This gives you the productivity of C#, avoids the pain of full P/Invoke or unmanaged interop, and lets you reuse most of your existing logic in a managed way.
内容的提问来源于stack exchange,提问作者Lucian

