[C#] SecureString을 System.String으로 변환하는 방법?

그것의 선택 System.String을 작성하여 SecureString의 보안을 해제에 대한 모든 예약 제외하고는 , 어떻게 수행 할 수 있습니다?

일반 System.Security.SecureString을 System.String으로 어떻게 변환 할 수 있습니까?

SecureString에 익숙한 많은 분들이 SecureString을 일반 .NET 문자열로 변환해서는 안된다고 대답 할 것입니다. 모든 보안 보호를 제거하기 때문입니다. 알아 . 그러나 지금 당장 내 프로그램은 어쨌든 일반 문자열로 모든 작업을 수행하고 있으며 보안을 강화하려고 노력하고 있으며 SecureString을 반환하는 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);
    }
}


답변

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

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

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

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


답변

메모리에서 해독 된 문자열을 더 잘 제어 할 수 있도록 SecureString종속 함수가 익명 함수로 종속 논리 를 캡슐화 하는 것이 가장 좋습니다 (한 번 고정됨).

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

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

이렇게하면 덜 바람직한 대안에 의존하는 것보다 호출자를 “표준화”하고 유지 관리하는 것이 훨씬 쉬워집니다.

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

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

  1. static T DecryptSecureString<T>이를 통해 Func호출자로부터 델리게이트 의 결과에 액세스 할 수 있습니다 ( DecryptSecureStringWithFunc테스트 메서드에 표시됨).
  2. static void DecryptSecureStringAction실제로 아무것도 반환하지 않으려는 경우 ( 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
}

즐거운 코딩 되세요!


답변

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