web-dev-qa-db-ja.com

SecureStringをSystem.Stringに変換する方法は?

System.Stringを作成してSecureStringのセキュリティを解除することに関するすべての予約aside、どのように行うことができますか?

通常のSystem.Security.SecureStringをSystem.Stringに変換するにはどうすればよいですか?

SecureStringに精通している皆さんの多くは、すべてのセキュリティ保護が解除されるため、SecureStringを通常の.NET文字列に決して変換してはならないと答えるでしょう。 知っている。しかし、今のところ、私のプログラムはとにかく通常の文字列ですべてを行い、セキュリティを強化しようとしていますが、SecureStringを返すAPIを使用するつもりですが、私はではありませんセキュリティを強化するためにそれを使用しようとしています。

Marshal.SecureStringToBSTRは知っていますが、そのBSTRを取得してSystem.Stringを作成する方法がわかりません。

なぜ私がこれをしたいのかを知りたいと思う人のために、ユーザーからパスワードを取得し、ユーザーをWebサイトにログインするためにPOSTのHTMLフォームとして送信します。だから...これは本当に管理された、暗号化されていないバッファで行われなければなりません。管理されていない、暗号化されていないバッファにアクセスできれば、ネットワークストリームでバイト単位のストリーム書き込みができ、パスワードが安全に保たれることを期待できます。これらのシナリオの少なくとも1つに対する回答を期待しています。

143
Andrew Arnott

System.Runtime.InteropServices.Marshal クラスを使用します。

String SecureStringToString(SecureString value) {
  IntPtr valuePtr = IntPtr.Zero;
  try {
    valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
    return Marshal.PtrToStringUni(valuePtr);
  } finally {
    Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
  }
}

管理された文字列オブジェクトの作成を避けたい場合は、 Marshal.ReadInt16(IntPtr, Int32) を使用して生データにアクセスできます。

void HandleSecureString(SecureString value) {
  IntPtr valuePtr = IntPtr.Zero;
  try {
    valuePtr = Marshal.SecureStringToGlobalAllocUnicode(value);
    for (int i=0; i < value.Length; i++) {
      short unicodeChar = Marshal.ReadInt16(valuePtr, i*2);
      // handle unicodeChar
    }
  } finally {
    Marshal.ZeroFreeGlobalAllocUnicode(valuePtr);
  }
}
177
Rasmus Faber

明らかに、これによってSecureStringの目的全体がどのように無効になるかはご存知でしょうが、とにかく説明します。

ワンライナーが必要な場合は、これを試してください:(.NET 4以降のみ)

string password = new System.Net.NetworkCredential(string.Empty, securePassword).Password;

ここで、securePasswordはSecureStringです。

96
Steve In CO

ダン。 rightこれを投稿した後、私は この記事 の深い答えを見つけました。しかし、誰かがこのメソッドが公開するIntPtrの管理されていない暗号化されていないバッファにアクセスする方法を知っている場合、一度に1バイトずつ、セキュリティを高く保つために管理された文字列オブジェクトを作成する必要がないように、答えを追加してください。 :)

static String SecureStringToString(SecureString value)
{
    IntPtr bstr = Marshal.SecureStringToBSTR(value);

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
46
Andrew Arnott

SecureString依存関数がencapsulateを匿名関数でカプセル化(---)して、メモリ内の復号化された文字列をより適切に制御する(ピン留め)のが最適だと思います。

このスニペットのSecureStringsを復号化するための実装は次のとおりです。

  1. 文字列をメモリにピン留めします(これはあなたがしたいことですが、ここのほとんどの回答にはないようです)。
  2. 参照 をFunc/Actionデリゲートに渡します。
  3. メモリからスクラブし、finallyブロックでGCを解放します。

これにより、望ましくない代替手段に依存するよりも、呼び出し元を「標準化」および保守することがはるかに簡単になります。

  • string DecryptSecureString(...)ヘルパー関数から復号化された文字列を返します。
  • このコードを必要な場所に複製します。

ここで、2つのオプションがあることに注意してください。

  1. static T DecryptSecureString<T>これにより、呼び出し元からのFuncデリゲートの結果にアクセスできます(DecryptSecureStringWithFuncテストメソッドに示されているように)。
  2. static void DecryptSecureStringは、実際に何かを返したくない場合にActionデリゲートを使用する単なる「void」バージョンです(DecryptSecureStringWithActionテストメソッドで示されているように)。

両方の使用例は、含まれているStringsTestクラスにあります。

Strings.cs

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace SecurityUtils
{
    public partial class Strings
    {
        /// <summary>
        /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
        /// </summary>
        /// <typeparam name="T">Generic type returned by Func delegate</typeparam>
        /// <param name="action">Func delegate which will receive the decrypted password pinned in memory as a String object</param>
        /// <returns>Result of Func delegate</returns>
        public static T DecryptSecureString<T>(SecureString secureString, Func<string, T> action)
        {
            var insecureStringPointer = IntPtr.Zero;
            var insecureString = String.Empty;
            var gcHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

            try
            {
                insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(secureString);
                insecureString = Marshal.PtrToStringUni(insecureStringPointer);

                return action(insecureString);
            }
            finally
            {
                //clear memory immediately - don't wait for garbage collector
                fixed(char* ptr = insecureString )
                {
                    for(int i = 0; i < insecureString.Length; i++)
                    {
                        ptr[i] = '\0';
                    }
                }

                insecureString = null;

                gcHandler.Free();
                Marshal.ZeroFreeGlobalAllocUnicode(insecureStringPointer);
            }
        }

        /// <summary>
        /// Runs DecryptSecureString with support for Action to leverage void return type
        /// </summary>
        /// <param name="secureString"></param>
        /// <param name="action"></param>
        public static void DecryptSecureString(SecureString secureString, Action<string> action)
        {
            DecryptSecureString<int>(secureString, (s) =>
            {
                action(s);
                return 0;
            });
        }
    }
}

StringsTest.cs

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security;

namespace SecurityUtils.Test
{
    [TestClass]
    public class StringsTest
    {
        [TestMethod]
        public void DecryptSecureStringWithFunc()
        {
            // Arrange
            var secureString = new SecureString();

            foreach (var c in "UserPassword123".ToCharArray())
                secureString.AppendChar(c);

            secureString.MakeReadOnly();

            // Act
            var result = Strings.DecryptSecureString<bool>(secureString, (password) =>
            {
                return password.Equals("UserPassword123");
            });

            // Assert
            Assert.IsTrue(result);
        }

        [TestMethod]
        public void DecryptSecureStringWithAction()
        {
            // Arrange
            var secureString = new SecureString();

            foreach (var c in "UserPassword123".ToCharArray())
                secureString.AppendChar(c);

            secureString.MakeReadOnly();

            // Act
            var result = false;

            Strings.DecryptSecureString(secureString, (password) =>
            {
                result = password.Equals("UserPassword123");
            });

            // Assert
            Assert.IsTrue(result);
        }
    }
}

明らかに、これは次の方法でこの関数の悪用を防ぐものではないので、これを行わないように注意してください。

[TestMethod]
public void DecryptSecureStringWithAction()
{
    // Arrange
    var secureString = new SecureString();

    foreach (var c in "UserPassword123".ToCharArray())
        secureString.AppendChar(c);

    secureString.MakeReadOnly();

    // Act
    string copyPassword = null;

    Strings.DecryptSecureString(secureString, (password) =>
    {
        copyPassword = password; // Please don't do this!
    });

    // Assert
    Assert.IsNull(copyPassword); // Fails
}

ハッピーコーディング!

13
rdev5

私の意見では、拡張方法はこれを解決する最も快適な方法です。

Steve in CO'sexcellent answer を取得し、次のように拡張クラスに追加しました。他の方向をサポートするために追加した2番目のメソッド(文字列->安全な文字列)同様に、安全な文字列を作成し、後で通常の文字列に変換できます。

public static class Extensions
{
    // convert a secure string into a normal plain text string
    public static String ToPlainString(this System.Security.SecureString secureStr)
    {
        String plainStr=new System.Net.NetworkCredential(string.Empty, secureStr).Password;
        return plainStr;
    }

    // convert a plain text string into a secure string
    public static System.Security.SecureString ToSecureString(this String plainStr)
    {
        var secStr = new System.Security.SecureString(); secStr.Clear();
        foreach (char c in plainStr.ToCharArray())
        {
            secStr.AppendChar(c);
        }
        return secStr;
    }
}

これにより、文字列を簡単に前後に変換できます

// create a secure string
System.Security.SecureString securePassword = "MyCleverPwd123".ToSecureString(); 
// convert it back to plain text
String plainPassword = securePassword.ToPlainString();  // convert back to normal string

ただし、デコード方法はテストにのみ使用してください。

10
Matt

rdev5からの回答 に基づいて、次の拡張メソッドを作成しました。マネージストリングを固定することは、ガベージコレクターがそれを移動させ、消去できないコピーを残さないようにするために重要です。

私のソリューションの利点は、安全でないコードが必要ないことです。

/// <summary>
/// Allows a decrypted secure string to be used whilst minimising the exposure of the
/// unencrypted string.
/// </summary>
/// <typeparam name="T">Generic type returned by Func delegate.</typeparam>
/// <param name="secureString">The string to decrypt.</param>
/// <param name="action">
/// Func delegate which will receive the decrypted password as a string object
/// </param>
/// <returns>Result of Func delegate</returns>
/// <remarks>
/// This method creates an empty managed string and pins it so that the garbage collector
/// cannot move it around and create copies. An unmanaged copy of the the secure string is
/// then created and copied into the managed string. The action is then called using the
/// managed string. Both the managed and unmanaged strings are then zeroed to erase their
/// contents. The managed string is unpinned so that the garbage collector can resume normal
/// behaviour and the unmanaged string is freed.
/// </remarks>
public static T UseDecryptedSecureString<T>(this SecureString secureString, Func<string, T> action)
{
    int length = secureString.Length;
    IntPtr sourceStringPointer = IntPtr.Zero;

    // Create an empty string of the correct size and pin it so that the GC can't move it around.
    string insecureString = new string('\0', length);
    var insecureStringHandler = GCHandle.Alloc(insecureString, GCHandleType.Pinned);

    IntPtr insecureStringPointer = insecureStringHandler.AddrOfPinnedObject();

    try
    {
        // Create an unmanaged copy of the secure string.
        sourceStringPointer = Marshal.SecureStringToBSTR(secureString);

        // Use the pointers to copy from the unmanaged to managed string.
        for (int i = 0; i < secureString.Length; i++)
        {
            short unicodeChar = Marshal.ReadInt16(sourceStringPointer, i * 2);
            Marshal.WriteInt16(insecureStringPointer, i * 2, unicodeChar);
        }

        return action(insecureString);
    }
    finally
    {
        // Zero the managed string so that the string is erased. Then unpin it to allow the
        // GC to take over.
        Marshal.Copy(new byte[length], 0, insecureStringPointer, length);
        insecureStringHandler.Free();

        // Zero and free the unmanaged string.
        Marshal.ZeroFreeBSTR(sourceStringPointer);
    }
}

/// <summary>
/// Allows a decrypted secure string to be used whilst minimising the exposure of the
/// unencrypted string.
/// </summary>
/// <param name="secureString">The string to decrypt.</param>
/// <param name="action">
/// Func delegate which will receive the decrypted password as a string object
/// </param>
/// <returns>Result of Func delegate</returns>
/// <remarks>
/// This method creates an empty managed string and pins it so that the garbage collector
/// cannot move it around and create copies. An unmanaged copy of the the secure string is
/// then created and copied into the managed string. The action is then called using the
/// managed string. Both the managed and unmanaged strings are then zeroed to erase their
/// contents. The managed string is unpinned so that the garbage collector can resume normal
/// behaviour and the unmanaged string is freed.
/// </remarks>
public static void UseDecryptedSecureString(this SecureString secureString, Action<string> action)
{
    UseDecryptedSecureString(secureString, (s) =>
    {
        action(s);
        return 0;
    });
}
4
sclarke81

上記の例をいくつか使用して、funcデリゲートコールバックの代わりに、このケースをより明確にしました。もちろん、破棄するのは開発者の責任です。

 public class SecureStringContext : IDisposable
{
    #region fields
    private GCHandle? _gcHandler = null;
    private string _insecureString = null;
    private IntPtr? _insecureStringPointer = null;
    private SecureString _secureString = null;      
    #endregion

    #region ctor
    public SecureStringContext(SecureString secureString)
    {
        _secureString = secureString;
        _secureString.MakeReadOnly();
        DecryptSecureString();

    }
    #endregion

    #region methos
    /// <summary>
    /// Passes decrypted password String pinned in memory to Func delegate scrubbed on return.
    /// </summary>
    private string DecryptSecureString()
    {
        _insecureStringPointer = IntPtr.Zero;
        _insecureString = String.Empty;
        _gcHandler = GCHandle.Alloc(_insecureString, GCHandleType.Pinned);

        _insecureStringPointer = Marshal.SecureStringToGlobalAllocUnicode(_secureString);
        _insecureString = Marshal.PtrToStringUni(_insecureStringPointer.GetValueOrDefault(IntPtr.Zero));

        return _insecureString;
    }

    private void WipeInsecureString()
    {
        //clear memory immediately - don't wait for garbage collector
        unsafe
        {
            fixed (char* ptr = _insecureString)
            {
                for (int i = 0; i < _insecureString.Length; i++)
                {
                    ptr[i] = '\0';
                }
            }
        }
        _insecureString = null;
    }
    #endregion

    #region properties
    public string InsecureString { get => _insecureString; }
    #endregion

    #region dispose
    public void Dispose()
    {
        //clear memory immediately - don't wait for garbage collector
        WipeInsecureString();
    }
    #endregion
}

使用法(参照が破棄されると、それも参照されます。)

using (var secureStringContext = new SecureStringContext(FabricSettingsHelper.GetConnectionSecureString()))
{
   //this is the clear text connection string
   x.UseSqlServerStorage(secureStringContext.InsecureString);
} //disposed clear text is removed from memory
0
C0r3yh

このC#コードはあなたが望むものです。

%ProjectPath%/SecureStringsEasy.cs

using System;
using System.Security;
using System.Runtime.InteropServices;
namespace SecureStringsEasy
{
    public static class MyExtensions
    {
        public static SecureString ToSecureString(string input)
        {
            SecureString secureString = new SecureString();
            foreach (var item in input)
            {
                secureString.AppendChar(item);
            }
            return secureString;
        }
        public static string ToNormalString(SecureString input)
        {
            IntPtr strptr = Marshal.SecureStringToBSTR(input);
            string normal = Marshal.PtrToStringBSTR(strptr);
            Marshal.ZeroFreeBSTR(strptr);
            return normal;
        }
    }
}