如何使用 DPAPI 加密 SecureString 以保存到磁盘,而不首先转换为不安全的字符串?

问题描述 投票:0回答:3

我想使用 DPAPI 加密 SecureString,以便将其保存到磁盘。

.net DPAPI 类是 ProtectedData 类,但是,ProtectedData.Protect 有一个采用字节数组的重载。没有接受 SecureString 的重载。

加密 .NET app.config 文件中的密码中,John Galloway 通过首先将 SecureString 转换为不安全的字符串来利用上述重载。我想避免这种情况,因为它首先违背了使用 SecureString 的目的。

ConvertFrom-SecureString PowerShell cmdlet 似乎可以满足我的需要,因为“如果未指定密钥,则使用 Windows 数据保护 API (DPAPI) 来加密标准字符串表示形式”,但我不确定如何执行直接从 .net 使用此 cmdlet,或者即使这样做是个好主意。

.net securestring
3个回答
4
投票
SecureString: Soup to Nuts, Part I

展示了如何做到这一点。方法是将 SecureString 转换为非托管 BSTR,然后使用 P/Invoke 调用非托管 DPAPI 函数。


2
投票
SecureString

编写了一对扩展方法,它们复制了 ProtectedData.Protect (SecureStringExtensions.Protect) 和 ProtectedData.Unprotect (SecureStringExtensions.AppendProtected) 的行为),但可以与安全字符串而不是字节数组无缝协作。 它们的设计是安全和稳健的,所以我希望它们有用。 (.NET Framework 版本直接在下面;较新的 .NET 8 版本在下面。)

/* * This code is licensed under a Creative Commons Attribution 4.0 International License. * See: https://creativecommons.org/licenses/by/4.0/ */ using System; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; namespace MyNamespace { /// <summary>Extension methods for <see cref="T:System.Security.SecureString" /> to enable safe serialisation and deserialisation of secure strings. This class cannot be inherited.</summary> /// <remarks>The <see cref="M:Protect(SecureString, System.Byte[], DataProtectionScope)" /> and <see cref="M:AppendProtected(SecureString, System.Byte[], System.Byte[], DataProtectionScope)" /> methods can be treated as secure string-based equivalents of <see cref="M:System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> and <see cref="M:System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, respectively.</remarks> /// <seealso cref="T:System.Security.Cryptography.ProtectedData" /> public static class SecureStringExtensions { /// <summary>Specifies the scope of the data protection to be applied by the <see cref="Protect(SecureString, byte[], DataProtectionScope)" /> and <see cref="AppendProtected(SecureString, byte[], byte[], DataProtectionScope)" /> methods.</summary> /// <remarks>This enumeration is equivalent to <see cref="T:System.Security.Cryptography.DataProtectionScope" />.</remarks> /// <seealso cref="T:System.Security.Cryptography.DataProtectionScope" /> public enum DataProtectionScope { /// <summary> /// The protected data is associated with the current user. Only threads running under the current user context can unprotect the data. /// </summary> CurrentUser, /// <summary> /// The protected data is associated with the machine context. Any process running on the computer can unprotect data. /// This enumeration value is usually used in server-specific applications that run on a server where untrusted users are not allowed access. /// </summary> LocalMachine } /// <summary>Encrypts the data in a secure string and returns a byte array that contains the encrypted data.</summary> /// <remarks>This method can be treated as equivalent to <see cref="M:System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, except that it encrypts a secure string instead of a byte array.</remarks> /// <param name="secureString">The secure string.</param> /// <param name="optionalEntropy">An optional additional byte array used to increase the complexity of the encryption, or <see langword="null" /> for no additional complexity.</param> /// <param name="scope">One of the enumeration values that specifies the scope of encryption.</param> /// <returns>A byte array representing the encrypted data.</returns> /// <exception cref="T:System.Security.Cryptography.CryptographicException">The encryption failed.</exception> /// <exception cref="T:System.OutOfMemoryException">The system ran out of memory while encrypting the data.</exception> /// <seealso cref="M:System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> public static byte[] Protect(this SecureString secureString, byte[] optionalEntropy, DataProtectionScope scope) { byte[] result = null; NativeMethods.DATA_BLOB dataIn = null; NativeMethods.DATA_BLOB entropy = null; NativeMethods.DATA_BLOB dataOut = new NativeMethods.DATA_BLOB(); GCHandle ptrOptionalEntropy = new GCHandle(); try { // +++ Handle secureString dataIn = new NativeMethods.DATA_BLOB { cbData = secureString.Length * sizeof(char), pbData = Marshal.SecureStringToGlobalAllocUnicode(secureString) }; // --- // +++ Handle optionalEntropy if (optionalEntropy != null) { ptrOptionalEntropy = GCHandle.Alloc(optionalEntropy, GCHandleType.Pinned); entropy = new NativeMethods.DATA_BLOB { cbData = optionalEntropy.Length, pbData = ptrOptionalEntropy.AddrOfPinnedObject() }; } // --- // +++ Handle scope NativeMethods.CryptProtectFlags flags = NativeMethods.CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN; if (scope.HasFlag(DataProtectionScope.LocalMachine)) flags |= NativeMethods.CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE; // --- if (!NativeMethods.CryptProtectData(dataIn, IntPtr.Zero, entropy, IntPtr.Zero, IntPtr.Zero, flags, dataOut)) throw new CryptographicException(Marshal.GetLastWin32Error()); else { if (dataOut.pbData == IntPtr.Zero) throw new OutOfMemoryException(); result = new byte[dataOut.cbData]; Marshal.Copy(dataOut.pbData, result, 0, dataOut.cbData); } } finally { if (dataOut.pbData != IntPtr.Zero) { NativeMethods.ZeroMemory(dataOut.pbData, (UIntPtr)dataOut.cbData); Marshal.FreeHGlobal(dataOut.pbData); } if (ptrOptionalEntropy.IsAllocated) ptrOptionalEntropy.Free(); if (dataIn != null) Marshal.ZeroFreeGlobalAllocUnicode(dataIn.pbData); } return (result); } /// <summary>Decrypts the data in a specified byte array and appends it to a secure string.</summary> /// <remarks>This method can be treated as equivalent to <see cref="M:System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, except that it appends the decrypted data to a secure string instead returning it in a byte array.</remarks> /// <param name="secureString">The secure string.</param> /// <param name="encryptedData">A byte array containing data encrypted using the <see cref="M:Protect(SecureString, System.Byte[], DataProtectionScope)" /> method.</param> /// <param name="optionalEntropy">An optional additional byte array that was used to encrypt the data, or <see langword="null" /> if the additional byte array was not used.</param> /// <param name="scope">One of the enumeration values that specifies the scope of data protection that was used to encrypt the data.</param> /// <exception cref="T:System.ArgumentNullException">The <paramref name="encryptedData" /> parameter is <see langword="null" />.</exception> /// <exception cref="T:System.InvalidOperationException">The secure string is read only.</exception> /// <exception cref="T:System.Security.Cryptography.CryptographicException">The decryption failed.</exception> /// <exception cref="T:System.OutOfMemoryException">The system ran out of memory while decrypting the data.</exception> /// <seealso cref="M:System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> public static void AppendProtected(this SecureString secureString, byte[] encryptedData, byte[] optionalEntropy, DataProtectionScope scope) { NativeMethods.DATA_BLOB dataIn = null; NativeMethods.DATA_BLOB entropy = null; NativeMethods.DATA_BLOB dataOut = new NativeMethods.DATA_BLOB(); GCHandle ptrEncryptedData = new GCHandle(); GCHandle ptrOptionalEntropy = new GCHandle(); if (encryptedData == null) throw new ArgumentNullException("encryptedData"); if (encryptedData.IsReadOnly) throw new InvalidOperationException(); try { // +++ Handle encryptedData ptrEncryptedData = GCHandle.Alloc(encryptedData, GCHandleType.Pinned); dataIn = new NativeMethods.DATA_BLOB { cbData = encryptedData.Length, pbData = ptrEncryptedData.AddrOfPinnedObject() }; // --- // +++ Handle optionalEntropy if (optionalEntropy != null) { ptrOptionalEntropy = GCHandle.Alloc(optionalEntropy, GCHandleType.Pinned); entropy = new NativeMethods.DATA_BLOB { cbData = optionalEntropy.Length, pbData = ptrOptionalEntropy.AddrOfPinnedObject() }; } // --- // +++ Handle scope NativeMethods.CryptProtectFlags flags = NativeMethods.CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN; if (scope.HasFlag(DataProtectionScope.LocalMachine)) flags |= NativeMethods.CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE; // --- if (!NativeMethods.CryptUnprotectData(dataIn, IntPtr.Zero, entropy, IntPtr.Zero, IntPtr.Zero, flags, dataOut)) throw new CryptographicException(Marshal.GetLastWin32Error()); else { if (dataOut.pbData == IntPtr.Zero) throw new OutOfMemoryException(); // Sanity check: can't be a valid string if length is not a multiple of sizeof(char) if ((dataOut.cbData % sizeof(char)) != 0) throw new CryptographicException(); for (int i = 0; i < dataOut.cbData; i += sizeof(char)) secureString.AppendChar((char)Marshal.ReadInt16(dataOut.pbData, i)); } } finally { if (dataOut.pbData != IntPtr.Zero) { NativeMethods.ZeroMemory(dataOut.pbData, (UIntPtr)dataOut.cbData); Marshal.FreeHGlobal(dataOut.pbData); } if (ptrOptionalEntropy.IsAllocated) ptrOptionalEntropy.Free(); if (ptrEncryptedData.IsAllocated) ptrEncryptedData.Free(); } } private static class NativeMethods { [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1049:TypesThatOwnNativeResourcesShouldBeDisposable")] [StructLayout(LayoutKind.Sequential)] public class DATA_BLOB { public int cbData; public IntPtr pbData; } [Flags] public enum CryptProtectFlags : uint { CRYPTPROTECT_UI_FORBIDDEN = 0x01, CRYPTPROTECT_LOCAL_MACHINE = 0x04, CRYPTPROTECT_VERIFY_PROTECTION = 0x40 } [DllImport("kernel32.dll", EntryPoint = "RtlZeroMemory")] public static extern void ZeroMemory(IntPtr destination, UIntPtr length); [DllImport("crypt32.dll", SetLastError = true)] public static extern bool CryptProtectData(DATA_BLOB pDataIn, IntPtr szDataDescr, DATA_BLOB pOptionalEntropy, IntPtr pvReserved, IntPtr pPromptStruct, CryptProtectFlags dwFlags, DATA_BLOB pDataOut); [DllImport("crypt32.dll", SetLastError = true)] public static extern bool CryptUnprotectData(DATA_BLOB pDataIn, IntPtr ppszDataDescr, DATA_BLOB pOptionalEntropy, IntPtr pvReserved, IntPtr pPromptStruct, CryptProtectFlags dwFlags, DATA_BLOB pDataOut); } } }

示例:

byte[] entropy = { 1, 2, 3, 4, 5 }; // Example entropy string password = "This is my password"; // Don't store passwords like this! // Add password to secure string SecureString ss1 = new SecureString(); Array.ForEach(password.ToCharArray(), ss1.AppendChar); ss1.MakeReadOnly(); // Encrypt secure string byte[] protectedBytes = ss1.Protect(entropy, SecureStringExtensions.DataProtectionScope.CurrentUser); Console.WriteLine("Encrypted secure string: {0}", Convert.ToBase64String(protectedBytes)); // Decrypt and add to a new secure string SecureString ss2 = new SecureString(); ss2.AppendProtected(protectedBytes, entropy, SecureStringExtensions.DataProtectionScope.CurrentUser); ss2.MakeReadOnly();

多年后,我最近不得不将此代码移植到.NET 8。这是更新的版本:

/* * This code is licensed under a Creative Commons Attribution 4.0 International License. * See: https://creativecommons.org/licenses/by/4.0/ */ using System; using System.Runtime.InteropServices; using System.Security; using System.Security.Cryptography; namespace MyNamespace { /// <summary>Extension methods for <see cref="System.Security.SecureString" /> to enable safe serialisation and deserialisation of secure strings. This class cannot be inherited.</summary> /// <remarks>The <see cref="Protect(SecureString, System.Byte[], DataProtectionScope)" /> and <see cref="AppendProtected(SecureString, System.Byte[], System.Byte[], DataProtectionScope)" /> methods can be treated as secure string-based equivalents of <see cref="System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> and <see cref="System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, respectively.</remarks> /// <seealso cref="System.Security.Cryptography.ProtectedData" /> public static partial class SecureStringExtensions { /// <summary>Specifies the scope of the data protection to be applied by the <see cref="Protect(SecureString, byte[], DataProtectionScope)" /> and <see cref="AppendProtected(SecureString, byte[], byte[], DataProtectionScope)" /> methods.</summary> /// <remarks>This enumeration is equivalent to <see cref="System.Security.Cryptography.DataProtectionScope" />.</remarks> /// <seealso cref="System.Security.Cryptography.DataProtectionScope" /> public enum DataProtectionScope { /// <summary> /// The protected data is associated with the current user. Only threads running under the current user context can unprotect the data. /// </summary> CurrentUser, /// <summary> /// The protected data is associated with the machine context. Any process running on the computer can unprotect data. /// This enumeration value is usually used in server-specific applications that run on a server where untrusted users are not allowed access. /// </summary> LocalMachine } /// <summary>Encrypts the data in a secure string and returns a byte array that contains the encrypted data.</summary> /// <remarks>This method can be treated as equivalent to <see cref="System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, except that it encrypts a secure string instead of a byte array.</remarks> /// <param name="secureString">The secure string.</param> /// <param name="optionalEntropy">An optional additional byte array used to increase the complexity of the encryption, or <see langword="null" /> for no additional complexity.</param> /// <param name="scope">One of the enumeration values that specifies the scope of encryption.</param> /// <returns>A byte array representing the encrypted data.</returns> /// <exception cref="System.Security.Cryptography.CryptographicException">The encryption failed.</exception> /// <seealso cref="System.Security.Cryptography.ProtectedData.Protect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> public static unsafe byte[] Protect(this SecureString secureString, byte[]? optionalEntropy, DataProtectionScope scope) { byte[]? protectedBytes = null; NativeMethods.DATA_BLOB dataIn = default; NativeMethods.DATA_BLOB dataOut = default; try { bool result; NativeMethods.DATA_BLOB entropy; fixed (byte* ptrOptionalEntropy = optionalEntropy) { NativeMethods.DATA_BLOB* ptrEntropy; // +++ Handle secureString dataIn = new NativeMethods.DATA_BLOB { cbData = (uint)secureString.Length * sizeof(char), pbData = (byte*)Marshal.SecureStringToGlobalAllocUnicode(secureString) }; // --- // +++ Handle optionalEntropy if (optionalEntropy is not null) { entropy = new NativeMethods.DATA_BLOB { cbData = (uint)optionalEntropy.Length, pbData = ptrOptionalEntropy }; ptrEntropy = &entropy; } else ptrEntropy = (NativeMethods.DATA_BLOB*)nint.Zero; // --- // +++ Handle scope NativeMethods.CryptProtectFlags flags = NativeMethods.CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN; if (scope.HasFlag(DataProtectionScope.LocalMachine)) flags |= NativeMethods.CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE; // --- result = NativeMethods.CryptProtectData(&dataIn, nint.Zero, ptrEntropy, nint.Zero, nint.Zero, flags, &dataOut); } if (result) { if (dataOut.pbData == (byte*)nint.Zero) throw new CryptographicException(); ReadOnlySpan<byte> buffer = new(dataOut.pbData, (int)dataOut.cbData); protectedBytes = buffer.ToArray(); } else throw new CryptographicException(Marshal.GetHRForLastWin32Error()); } finally { if (dataOut.pbData != (byte*)nint.Zero) { CryptographicOperations.ZeroMemory(new Span<byte>(dataOut.pbData, (int)dataOut.cbData)); Marshal.FreeHGlobal((nint)dataOut.pbData); } if (dataIn.pbData != (byte*)nint.Zero) Marshal.ZeroFreeGlobalAllocUnicode((nint)dataIn.pbData); } return (protectedBytes); } /// <summary>Decrypts the data in a specified byte array and appends it to a secure string.</summary> /// <remarks>This method can be treated as equivalent to <see cref="System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" />, except that it appends the decrypted data to a secure string instead returning it in a byte array.</remarks> /// <param name="secureString">The secure string.</param> /// <param name="encryptedData">A byte array containing data encrypted using the <see cref="Protect(SecureString, System.Byte[], DataProtectionScope)" /> method.</param> /// <param name="optionalEntropy">An optional additional byte array that was used to encrypt the data, or <see langword="null" /> if the additional byte array was not used.</param> /// <param name="scope">One of the enumeration values that specifies the scope of data protection that was used to encrypt the data.</param> /// <exception cref="System.ArgumentNullException">The <paramref name="encryptedData" /> parameter is <see langword="null" />.</exception> /// <exception cref="System.InvalidOperationException">The secure string is read only.</exception> /// <exception cref="System.Security.Cryptography.CryptographicException">The decryption failed.</exception> /// <seealso cref="System.Security.Cryptography.ProtectedData.Unprotect(System.Byte[], System.Byte[], System.Security.Cryptography.DataProtectionScope)" /> public static unsafe void AppendProtected(this SecureString secureString, byte[] encryptedData, byte[]? optionalEntropy, DataProtectionScope scope) { ArgumentNullException.ThrowIfNull(encryptedData); if (encryptedData.IsReadOnly) throw new InvalidOperationException(); NativeMethods.DATA_BLOB dataOut = default; try { bool result; NativeMethods.DATA_BLOB dataIn; NativeMethods.DATA_BLOB entropy; fixed (byte* ptrEncryptedData = encryptedData) fixed (byte* ptrOptionalEntropy = optionalEntropy) { NativeMethods.DATA_BLOB* ptrEntropy; // +++ Handle encryptedData dataIn = new NativeMethods.DATA_BLOB { cbData = (uint)encryptedData.Length, pbData = ptrEncryptedData }; // --- // +++ Handle optionalEntropy if (optionalEntropy is not null) { entropy = new NativeMethods.DATA_BLOB { cbData = (uint)optionalEntropy.Length, pbData = ptrOptionalEntropy }; ptrEntropy = &entropy; } else ptrEntropy = (NativeMethods.DATA_BLOB*)nint.Zero; // --- // +++ Handle scope NativeMethods.CryptProtectFlags flags = NativeMethods.CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN; if (scope.HasFlag(DataProtectionScope.LocalMachine)) flags |= NativeMethods.CryptProtectFlags.CRYPTPROTECT_LOCAL_MACHINE; // --- result = NativeMethods.CryptUnprotectData(&dataIn, nint.Zero, ptrEntropy, nint.Zero, nint.Zero, flags, &dataOut); } if (result) { // Sanity check: can't be a valid string if length is not a multiple of sizeof(char), or if dataOut.pbData is null if (((dataOut.cbData % sizeof(char)) != 0) || (dataOut.pbData == (byte*)nint.Zero)) throw new CryptographicException(); ReadOnlySpan<char> buffer = new(dataOut.pbData, (int)dataOut.cbData / sizeof(char)); foreach (char c in buffer) secureString.AppendChar(c); } else throw new CryptographicException(Marshal.GetHRForLastWin32Error()); } finally { if (dataOut.pbData != (byte*)nint.Zero) { CryptographicOperations.ZeroMemory(new Span<byte>(dataOut.pbData, (int)dataOut.cbData)); Marshal.FreeHGlobal((nint)dataOut.pbData); } } } private static partial class NativeMethods { [StructLayout(LayoutKind.Sequential)] internal struct DATA_BLOB { public uint cbData; public unsafe byte* pbData; } [Flags] internal enum CryptProtectFlags : uint { CRYPTPROTECT_UI_FORBIDDEN = 0x01, CRYPTPROTECT_LOCAL_MACHINE = 0x04, CRYPTPROTECT_VERIFY_PROTECTION = 0x40 } [LibraryImport("crypt32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static unsafe partial bool CryptProtectData(DATA_BLOB* pDataIn, nint szDataDescr, DATA_BLOB* pOptionalEntropy, nint pvReserved, nint pPromptStruct, CryptProtectFlags dwFlags, DATA_BLOB* pDataOut); [LibraryImport("crypt32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static unsafe partial bool CryptUnprotectData(DATA_BLOB* pDataIn, nint ppszDataDescr, DATA_BLOB* pOptionalEntropy, nint pvReserved, nint pPromptStruct, CryptProtectFlags dwFlags, DATA_BLOB* pDataOut); } } }



1
投票

据我所知,目前在使用 DPAPI 加密 SecureStrings 方面存在一些 API 差距。通过查看可用的方法,并阅读 Jeff Griffin 的

SecureString: Soup to Nuts, Part I

,您似乎无法使用方便的花花公子 ProtectedData.Protect 和 Unprotect 方法,因为它们不采用字节数组,如果您将 SecureString 转换为字节数组,它将再次不受保护地坐在那里,从而达不到目的。 因此,解决方案涉及使用 P/Invoke 直接调用 ProtectedData 方法使用的底层 C++ 命令。我相信这就是 Jeff Griffin 的博客所做的,但到实际扩展方法的链接似乎已死,所以我试图拼凑出它们可能的样子,这样它们就不会迷路,但同样,我不是安全的专家所以对这一切持保留态度:

我还发现这个示例作为参考非常有用:

http://www.obviex.com/samples/dpapi.aspx

public static class EncryptionExtensions { public static string Decrypt(this byte[] data) { DATA_BLOB plainTextBlob = new DATA_BLOB();//we need to pass all of these as parameters DATA_BLOB cipherTextBlob = new DATA_BLOB(); DATA_BLOB entropyBlob = new DATA_BLOB();//though atm I'm omitting entropy so this will just be empty. CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT(); InitPrompt(ref prompt);//make it empty. try { // Convert ciphertext bytes into a BLOB structure. try { // Use empty array for null parameter. if (data == null) data = new byte[0]; // Allocate memory for the BLOB data. cipherTextBlob.pbData = Marshal.AllocHGlobal(data.Length); // Make sure that memory allocation was successful. if (cipherTextBlob.pbData == IntPtr.Zero) throw new Exception( "Unable to allocate data buffer for BLOB structure."); // Specify number of bytes in the BLOB. cipherTextBlob.cbData = data.Length; // Copy data from original source to the BLOB structure. Marshal.Copy(data, 0, cipherTextBlob.pbData, data.Length); } catch (Exception ex) { throw new Exception( "Cannot initialize ciphertext BLOB.", ex); } // Call DPAPI to decrypt data. bool success = CryptUnprotectData(ref cipherTextBlob, null, ref entropyBlob, IntPtr.Zero, ref prompt, CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN, ref plainTextBlob); // Check the result. if (!success) { // If operation failed, retrieve last Win32 error. int errCode = Marshal.GetLastWin32Error(); // Win32Exception will contain error message corresponding // to the Windows error code. throw new Exception( "CryptUnprotectData failed.", new Win32Exception(errCode)); } return Marshal.PtrToStringAuto(plainTextBlob.pbData);//convert your pointer back into a string. Not sure why PtrToStringBTSR doesn't work but Auto seems to. } catch (Exception ex) { throw new Exception("DPAPI was unable to decrypt data.", ex); } // Free all memory allocated for BLOBs. finally { if (plainTextBlob.pbData != IntPtr.Zero) Marshal.FreeHGlobal(plainTextBlob.pbData); if (cipherTextBlob.pbData != IntPtr.Zero) Marshal.FreeHGlobal(cipherTextBlob.pbData); if (entropyBlob.pbData != IntPtr.Zero) Marshal.FreeHGlobal(entropyBlob.pbData); } } public static Byte[] Encrypt(this SecureString self, int length) { IntPtr unmanagedString = Marshal.SecureStringToBSTR(self);//get the basic unmanaged string representation int len = Marshal.ReadInt32(unmanagedString, -4) + 2; //get the length of the bstr structure from it's index, this doesn't include the null bytes hence + 2. DATA_BLOB plainTextBlob = new DATA_BLOB();//initiate our blobs DATA_BLOB cipherTextBlob = new DATA_BLOB(); DATA_BLOB entropyTextBlob = new DATA_BLOB(); CRYPTPROTECT_PROMPTSTRUCT prompt = new CRYPTPROTECT_PROMPTSTRUCT(); try { //Processing code here. Resist the urge to Marshal.PtrToStringBSTR. plainTextBlob.cbData = len;//set the length of the array plainTextBlob.pbData = unmanagedString;//set the data to our pointer. InitPrompt(ref prompt); // Call DPAPI to encrypt data. bool success = CryptProtectData(ref plainTextBlob, null, ref entropyTextBlob, IntPtr.Zero, ref prompt, CryptProtectFlags.CRYPTPROTECT_UI_FORBIDDEN, ref cipherTextBlob); // Check the result. if (!success) { // If operation failed, retrieve last Win32 error. int errCode = Marshal.GetLastWin32Error(); // Win32Exception will contain error message corresponding // to the Windows error code. throw new Exception( "CryptProtectData failed.", new Win32Exception(errCode)); } // Allocate memory to hold ciphertext. byte[] cipherTextBytes = new byte[cipherTextBlob.cbData]; // Copy ciphertext from the BLOB to a byte array. Marshal.Copy(cipherTextBlob.pbData, cipherTextBytes, 0, cipherTextBlob.cbData); // Return the result. return cipherTextBytes; } finally { Marshal.ZeroFreeBSTR(unmanagedString); //free the buffer holding our secret if (cipherTextBlob.pbData != IntPtr.Zero) Marshal.FreeHGlobal(cipherTextBlob.pbData); } } //The below regions are all the PInvoke signatures. These translate C++ commands into usable C# commands. These come directly from pinvoke.net #region PInvokeSignatures /// <summary> /// Initializes empty prompt structure. /// </summary> /// <param name="ps"> /// Prompt parameter (which we do not actually need). /// </param> private static void InitPrompt(ref CRYPTPROTECT_PROMPTSTRUCT ps) { ps.cbSize = Marshal.SizeOf(typeof(CRYPTPROTECT_PROMPTSTRUCT)); ps.dwPromptFlags = 0; ps.hwndApp = IntPtr.Zero; ps.szPrompt = null; } [DllImport("Crypt32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CryptProtectData( ref DATA_BLOB pDataIn, String szDataDescr, ref DATA_BLOB pOptionalEntropy, IntPtr pvReserved, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, CryptProtectFlags dwFlags, ref DATA_BLOB pDataOut ); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct DATA_BLOB { public int cbData; public IntPtr pbData; } [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct CRYPTPROTECT_PROMPTSTRUCT { public int cbSize; public CryptProtectPromptFlags dwPromptFlags; public IntPtr hwndApp; public String szPrompt; } [Flags] private enum CryptProtectPromptFlags { // prompt on unprotect CRYPTPROTECT_PROMPT_ON_UNPROTECT = 0x1, // prompt on protect CRYPTPROTECT_PROMPT_ON_PROTECT = 0x2 } [Flags] private enum CryptProtectFlags { // for remote-access situations where ui is not an option // if UI was specified on protect or unprotect operation, the call // will fail and GetLastError() will indicate ERROR_PASSWORD_RESTRICTION CRYPTPROTECT_UI_FORBIDDEN = 0x1, // per machine protected data -- any user on machine where CryptProtectData // took place may CryptUnprotectData CRYPTPROTECT_LOCAL_MACHINE = 0x4, // force credential synchronize during CryptProtectData() // Synchronize is only operation that occurs during this operation CRYPTPROTECT_CRED_SYNC = 0x8, // Generate an Audit on protect and unprotect operations CRYPTPROTECT_AUDIT = 0x10, // Protect data with a non-recoverable key CRYPTPROTECT_NO_RECOVERY = 0x20, // Verify the protection of a protected blob CRYPTPROTECT_VERIFY_PROTECTION = 0x40 } [DllImport("Crypt32.dll", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CryptUnprotectData( ref DATA_BLOB pDataIn, StringBuilder szDataDescr, ref DATA_BLOB pOptionalEntropy, IntPtr pvReserved, ref CRYPTPROTECT_PROMPTSTRUCT pPromptStruct, CryptProtectFlags dwFlags, ref DATA_BLOB pDataOut ); #endregion }

© www.soinside.com 2019 - 2024. All rights reserved.