访问模拟帐户的私钥

Access impersonated account's private keys

我正在开发一个程序,旨在模拟另一个帐户,然后使用来自模拟帐户的密钥库的私钥解密文件。

问题是,虽然我能够打开模拟帐户的证书存储并使用适当的证书实例化 X509Certificate2 对象,但是当程序尝试访问私钥时,我收到异常:

System.Security.Cryptography.CryptographicException: The system cannot find the file specified...at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()

我能够使用 FindPrivateKey.exe 实用程序在文件系统中找到模拟用户的私钥,并发现它位于 C:\Users\TESTUSR\AppData\Roaming\Microsoft\Crypto\RSA... 我使用 this article 作为确保我自己的帐户可以访问私钥文件的指南。我绝对可以从 NTFS 的角度访问密钥文件,因为我可以在记事本中加载它(尽管它显然是胡言乱语)。

我很偶然地发现,如果我使用 runas 预加载模拟帐户的本地配置文件,程序 确实 工作,例如,打开命令提示符用户。

如有任何建议,我们将不胜感激!

class Program
{
    [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
    int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken);

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    public extern static bool CloseHandle(IntPtr handle);

    [PermissionSetAttribute(SecurityAction.Demand, Name = "FullTrust")]
    static void Main(string[] args)
    {
        const int LOGON32_PROVIDER_DEFAULT = 0;
        const int LOGON32_LOGON_INTERACTIVE = 2;

        SafeTokenHandle safeTokenHandle;

        try
        {
            string username = "TESTUSR";
            string domain = "CONTOSO";
            string password = "P@ssw0rd";

            // Call LogonUser to obtain a handle to an access token.
            if (!LogonUser(username, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, out safeTokenHandle))
                return;

            if (safeTokenHandle == null)
            {
                int ret = Marshal.GetLastWin32Error();
                throw new System.ComponentModel.Win32Exception(ret);
            }
            using (safeTokenHandle)
            {
                using (WindowsIdentity newId = new WindowsIdentity(safeTokenHandle.DangerousGetHandle()))
                {
                    using (WindowsImpersonationContext impersonatedUser = newId.Impersonate())
                    {
                        string neededCertSN = "17396B080000000000A5";
                        X509Store my = new X509Store(StoreLocation.CurrentUser);
                        my.Open(OpenFlags.ReadOnly);

                        string result;
                        foreach (X509Certificate2 cert in my.Certificates)
                        {
                            if (Regex.IsMatch(neededCertSN, cert.SerialNumber))
                            {
                                result = DecryptFile(args[0], ".txt", (RSACryptoServiceProvider)cert.PrivateKey);
                                Console.WriteLine("Result:\r\n" + result);
                                return;
                            }
                        }

                        Console.WriteLine("Encryption certificate not found.");
                    }
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("An exception occurred:\r\n" + ex.ToString());
        }
    }

    static string DecryptFile(string cypherTextFile, string plainTextFileExtension, RSACryptoServiceProvider rsaPrivateKey)
    {
        string plainTextFile = string.Empty;
        try
        {
            if (!File.Exists(cypherTextFile))
                return "Cyphertext file not found";

            // Create instance of AesManaged for 
            // symetric decryption of the data. 
            using (AesManaged aesManaged = new AesManaged())
            {
                aesManaged.KeySize = 256;
                aesManaged.BlockSize = 128;
                aesManaged.Mode = CipherMode.CBC;

                // Create byte arrays to get the length of 
                // the encrypted key and IV. 
                // These values were stored as 4 bytes each 
                // at the beginning of the encrypted package. 
                byte[] LenK = new byte[4];
                byte[] LenIV = new byte[4];

                // Consruct the file name for the decrypted file. 
                if (plainTextFileExtension[0] != '.')
                    plainTextFileExtension = "." + plainTextFileExtension;
                string[] parts = cypherTextFile.Split('.');
                for (int x = 0; x < parts.Length - 1; x++)
                    plainTextFile = plainTextFile + parts[x];
                plainTextFile = plainTextFile + plainTextFileExtension;

                // Use FileStream objects to read the encrypted 
                // file (inFs) and save the decrypted file (outFs). 
                using (FileStream inFs = new FileStream(cypherTextFile, FileMode.Open))
                {

                    inFs.Seek(0, SeekOrigin.Begin);
                    inFs.Seek(0, SeekOrigin.Begin);
                    inFs.Read(LenK, 0, 3);
                    inFs.Seek(4, SeekOrigin.Begin);
                    inFs.Read(LenIV, 0, 3);

                    // Convert the lengths to integer values. 
                    int lenK = BitConverter.ToInt32(LenK, 0);
                    int lenIV = BitConverter.ToInt32(LenIV, 0);

                    // Determine the start postition of 
                    // the ciphter text (startC) 
                    // and its length(lenC). 
                    int startC = lenK + lenIV + 8;
                    int lenC = (int)inFs.Length - startC;

                    // Create the byte arrays for 
                    // the encrypted AesManaged key, 
                    // the IV, and the cipher text. 
                    byte[] KeyEncrypted = new byte[lenK];
                    byte[] IV = new byte[lenIV];

                    // Extract the key and IV 
                    // starting from index 8 
                    // after the length values. 
                    inFs.Seek(8, SeekOrigin.Begin);
                    inFs.Read(KeyEncrypted, 0, lenK);
                    inFs.Seek(8 + lenK, SeekOrigin.Begin);
                    inFs.Read(IV, 0, lenIV);

                    // Use RSACryptoServiceProvider 
                    // to decrypt the AesManaged key. 
                    byte[] KeyDecrypted = rsaPrivateKey.Decrypt(KeyEncrypted, false);

                    // Decrypt the key. 
                    using (ICryptoTransform transform = aesManaged.CreateDecryptor(KeyDecrypted, IV))
                    {

                        // Decrypt the cipher text from 
                        // from the FileSteam of the encrypted 
                        // file (inFs) into the FileStream 
                        // for the decrypted file (outFs). 
                        using (FileStream outFs = new FileStream(plainTextFile, FileMode.Create))
                        {

                            int count = 0;
                            int offset = 0;

                            int blockSizeBytes = aesManaged.BlockSize / 8;
                            byte[] data = new byte[blockSizeBytes];

                            // By decrypting a chunk a time, 
                            // you can save memory and 
                            // accommodate large files. 

                            // Start at the beginning 
                            // of the cipher text. 
                            inFs.Seek(startC, SeekOrigin.Begin);
                            using (CryptoStream outStreamDecrypted = new CryptoStream(outFs, transform, CryptoStreamMode.Write))
                            {
                                do
                                {
                                    count = inFs.Read(data, 0, blockSizeBytes);
                                    offset += count;
                                    outStreamDecrypted.Write(data, 0, count);

                                }
                                while (count > 0);

                                outStreamDecrypted.FlushFinalBlock();
                                outStreamDecrypted.Close();
                            }
                            outFs.Close();
                        }
                        inFs.Close();
                    }
                }
            }

            return plainTextFile;
        }
        catch (Exception e)
        {
            return "An exception occurred:\r\n" + e.ToString();
        }
    }

}

public sealed class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
    private SafeTokenHandle()
        : base(true)
    {
    }

    [DllImport("kernel32.dll")]
    [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
    [SuppressUnmanagedCodeSecurity]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool CloseHandle(IntPtr handle);

    protected override bool ReleaseHandle()
    {
        return CloseHandle(handle);
    }
}

我遇到的问题是,虽然我能够打开模拟帐户的证书存储并在 X509Certificate2 对象中获取正确的证书,但是当程序尝试使用私钥时,它是

您需要根据CreateProcessAsUser文档手动加载用户注册表:

CreateProcessAsUser does not load the specified user's profile into the HKEY_USERS registry key. Therefore, to access the information in the HKEY_CURRENT_USER registry key, you must load the user's profile information into HKEY_USERS with the LoadUserProfile function before calling CreateProcessAsUser.

RunAs 不会在您的进程中更改 HKEY_CURRENT_USER,但它会导致配置单元在 HKEY_USERS 下加载。

您可以尝试在 LogonUser 之后调用 LoadUserProfile...