5

ディスクに保存できるように、DPAPIを使用してSecureStringを暗号化したいと思います。

.net DPAPIクラスはProtectedDataクラスですが、ProtectedData.Protectには、バイト配列を使用する単一のオーバーロードがあります。SecureStringを受け入れるオーバーロードはありません。

.NET app.configファイルでのパスワードの暗号化では、John Gallowayは、最初にSecureStringをセキュリティで保護されていない文字列に変換することにより、上記の過負荷を利用します。そもそもSecureStringを使用する目的が損なわれるため、これは避けたいと思います。

ConvertFrom-SecureString PowerShellコマンドレットは、「キーが指定されていない場合、Windows Data Protection API(DPAPI)を使用して標準の文字列表現を暗号化する」ため、必要なことを実行しているようですが、このコマンドレットをどのように使用するかわかりません。 .netから直接、またはそうすることをお勧めします。

4

3 に答える 3

4

ブログ投稿SecureString:Soup to Nuts、Part I by Jeff Griffinは、これを行う方法を示しています。アプローチは、SecureStringをアンマネージBSTRに変換してから、P/Invokeを使用してアンマネージDPAPI関数を呼び出すことです。

于 2012-12-01T09:26:35.943 に答える
2

将来この質問に出くわす人のために、ProtectedData.Protect(SecureStringExtensions.Protect)とProtectedData.Unprotect(SecureStringExtensions.AppendProtected)の動作を複製するが、SecureStringとシームレスに連携するSecureStringの拡張メソッドのペア作成ましたバイト配列ではなく文字列。

安全で堅牢な設計になっているので、お役に立てば幸いです。

/*
 * 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();
于 2019-01-04T03:12:46.230 に答える
1

今週末にたくさん掘り下げた後、私はこれを行う方法を理解したと思いますが、私は決して専門家ではなく、まだこれのほとんどを自分で理解しているだけであることに注意して序文を述べさせてください。

私が理解できることから、現在、DPAPIを使用してSecureStringsを暗号化することに関しては、APIのギャップが少しあります。使用可能なメソッドを調べ、 SecureString:Soup to Nuts、Part I by Jeff Griffinを読むと、バイト配列を使用しないため、便利なダンディなProtectedData.ProtectメソッドとUnprotectメソッドを使用できないようです。 SecureStringをバイト配列に変換すると、保護されていない状態で再び目的を達成できなくなります。

したがって、このソリューションでは、P / Invokeを使用して、ProtectedDataメソッドが使用する基になるC++コマンドを直接呼び出す必要があります。これはJeffGriffinのブログが行ったことだと思いますが、実際の拡張メソッドへのリンクは死んでいるように見えるので、失われないようにそれらがどのように見えるかをまとめようとしましたが、繰り返しになりますが、私はセキュリティではありません専門家なので、これをすべて一粒の塩で服用してください:

また、この例は参照として非常に役立ちます: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
}
于 2018-07-23T17:20:05.577 に答える