programing tip

SecureString을 System.String로 변환하는 방법?

itbloger 2020. 6. 16. 20:36
반응형

SecureString을 System.String로 변환하는 방법?


따로 System.String을 만들어 SecureString 보안을 해제하는 방법에 대한 모든 예약은 어떻게 할 수 있습니까?

일반 System.Security.SecureString을 System.String으로 변환하려면 어떻게해야합니까?

SecureString에 익숙한 많은 사람들이 모든 보안 보호 기능을 제거하므로 SecureString을 일반 .NET 문자열로 변환해서는 안된다고 응답 할 것입니다. 알고 있습니다. 그러나 지금 내 프로그램은 어쨌든 일반 문자열로 모든 작업을 수행하며 보안을 강화하려고 노력하고 있지만 보안 문자열을 반환하는 API 를 사용하려고하지만 보안을 높이기 위해 사용 하지는 않습니다 .

Marshal.SecureStringToBSTR을 알고 있지만 BSTR을 가져 와서 System.String을 만드는 방법을 모르겠습니다.

왜 내가 이것을하고 싶어하는지 알고 싶어하는 사람들을 위해, 나는 사용자로부터 암호를 가져 와서 웹 사이트에 사용자를 로그인하기 위해 html 양식 POST로 제출하고 있습니다. 따라서 ... 이것은 실제로 암호화되지 않은 관리 버퍼로 수행해야합니다. 관리되지 않고 암호화되지 않은 버퍼에 액세스 할 수 있다면 네트워크 스트림에서 바이트 단위 스트림 쓰기를 수행 할 수 있다고 생각하며 암호가 전체적으로 안전하게 유지되기를 바랍니다. 이 시나리오 중 하나 이상에 대한 답변을 기대하고 있습니다.


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);
  }
}

분명히 이것이 이것이 SecureString의 전체 목적을 어떻게 극복하는지 알고 있지만 어쨌든 다시 설명하겠습니다.

하나의 라이너를 원한다면 다음을 시도하십시오. (. NET 4 이상에만 해당)

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

여기서 securePassword는 SecureString입니다.


바로 게시 한 후이 난에 응답 깊은 발견 이 기사를 . 그러나 누군가이 방법으로 노출되는 관리되지 않는 암호화되지 않은 버퍼에 IntPtr에 액세스하는 방법을 알고 있다면 한 번에 한 바이트 씩 보안 문자열을 유지하기 위해 관리되는 문자열 객체를 만들 필요가 없으므로 답변을 추가하십시오. :)

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

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

메모리에서 해독 된 문자열을 한 번 더 잘 제어 SecureString하기 위해 종속 함수가 종속 함수를 익명 함수 캡슐화 하는 것이 가장 좋습니다 .

이 스 니펫에서 SecureStrings를 해독하기위한 구현은 다음과 같습니다.

  1. 문자열을 메모리에 고정하십시오 (원하는 것이지만 대부분의 답변에서 누락 된 것처럼 보입니다).
  2. 패스 의 참조 Func을 / 액션 위양합니다.
  3. 메모리에서 제거하고 finally블록 에서 GC를 해제하십시오 .

이렇게하면 덜 바람직한 대안에 의존하는 것보다 발신자를 "표준화"하고 유지하는 것이 훨씬 쉬워집니다.

  • string DecryptSecureString(...)도우미 함수 에서 해독 된 문자열을 반환합니다 .
  • 필요할 때마다이 코드를 복제합니다.

여기에 두 가지 옵션이 있습니다.

  1. static T DecryptSecureString<T>테스트 메소드에 Func표시된대로 발신자 델리게이트 결과에 액세스 할 수 있습니다 DecryptSecureStringWithFunc.
  2. static void DecryptSecureStringAction실제로 DecryptSecureStringWithAction테스트 방법 에서 설명한대로 아무것도 반환하지 않으려는 경우 델리게이트 를 사용하는 "void"버전입니다 .

두 가지 사용법의 예는 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
}

행복한 코딩!


내 생각에 확장 방법 은이를 해결하는 가장 편안한 방법입니다.

나는 Steve를 CO의 훌륭한 대답으로 가져 와서 다음과 같이 확장 클래스에 넣었습니다. 다른 방향 (string-> secure string)을 지원하기 위해 추가 한 두 번째 방법과 함께 안전한 문자열을 만들고 변환 할 수 있습니다 이후 일반 문자열 :

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

그러나 디코딩 방법은 테스트 용도로만 사용해야합니다.


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;
    });
}

이 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;
        }
    }
}

위의 예를 사용하면 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

// using so that Marshal doesn't have to be qualified
using System.Runtime.InteropServices;    
//using for SecureString
using System.Security;
public string DecodeSecureString (SecureString Convert) 
{
    //convert to IntPtr using Marshal
    IntPtr cvttmpst = Marshal.SecureStringToBSTR(Convert);
    //convert to string using Marshal
    string cvtPlainPassword = Marshal.PtrToStringAuto(cvttmpst);
    //return the now plain string
    return cvtPlainPassword;
}

StringBuilder대신에 를 사용하면 string완료시 메모리의 실제 값을 덮어 쓸 수 있습니다. 이렇게하면 가비지 수집에서 암호를 찾을 때까지 암호가 메모리에 걸리지 않습니다.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());

참고 URL : https://stackoverflow.com/questions/818704/how-to-convert-securestring-to-system-string

반응형