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


159

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

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

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

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

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

답변:


195

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

1
도움을 주셔서 감사합니다. 참고 : 이것은 자체 메모리에서 정적으로 작동합니다.
John Suit

나는 4.6 초를 사용 StopWatch하고 SecureStringToString달렸다. 나를 위해 천천히. 누구든지 같은 시간이나 더 빠른 것을 얻습니까?
radbyx

@radbyx 빠르고 더러운 테스트 설정에서 76ms에 1000 번 호출 할 수 있습니다. 첫 번째 호출에는 0.3ms가 걸리고 후속 호출에는 ~ 0.07ms가 걸립니다. 보안 문자열은 얼마나 크고 어떤 버전의 프레임 워크를 사용하고 있습니까?
Rasmus Faber

내 secureString의 길이는 168입니다. 질문에 대한 답변이 있다면 .NET Framework 3.5를 사용하고 있습니까? 난 항상 주위 4.5-4.65 초 ~ 내가 시간을 얻을 싶어요되는 5 ~ 10 시간을 tryed 한
radbyx

@RasmusFaber 내 Database.GetConnectionString()안타깝게도, 보안 문자열을 얻기 위해 코드에을 추가했습니다. 이는 거의 5 초가 걸린 사악한 부분이었습니다. 문제 없다. 올바른 방향을 알려 주셔서 감사합니다.
radbyx

115

분명히 이것이 SecureString의 전체 목적을 무너 뜨리는 방법을 알고 있지만 어쨌든 다시 언급하겠습니다.

한 줄짜리를 원하면 다음을 시도하십시오. (. NET 4 이상 만 해당)

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

여기서 securePassword는 SecureString입니다.


10
프로덕션의 목적에 맞지 않지만 솔루션은 단위 테스트에 적합합니다. 감사.
beterthanlife dec.

이것은 SecureString (System.Security.SecureString)이 ApiController (webapi)에 전달되지 않는다는 것을 알아내는 데 도움이되었습니다. Thx
granadaCoder

5
PowerShell의 주이다[System.Net.NetworkCredential]::new('', $securePassword).Password
스테인

1
@ TheIncorrigible1 자세히 설명해 주시겠습니까? 예 :는 언제 ''와 같은 유형이 [String]::Empty아닌가? 또한 New-Object Net.Credential나를 위해 작동하지 않습니다 : 유형 [Net.Credential]을 찾을 수 없습니다 :이 유형을 포함하는 어셈블리가로드되었는지 확인하십시오
stijn

2
SecureString 콘텐츠의 암호화되지 않은 복사본을 일반 문자열로 만들기 때문에 SecureString의 목적을 무효화합니다. 그렇게 할 때마다 암호화되지 않은 문자열의 복사본을 적어도 하나 (그리고 가비지 컬렉션과 함께) 메모리에 추가합니다. 이는 보안에 민감한 일부 애플리케이션의 위험으로 간주되며 특히 위험을 줄이기 위해 SecureString이 구현되었습니다.
Steve In CO

49

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

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

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

당신은 확실히 사용할 수있는 unsafe키워드와 char*, 바로 전화 bstr.ToPointer()및 캐스트.
Ben Voigt

@BenVoigt BSTR에는 안전을 위해 문자열 데이터 뒤에 null 종결자가 있지만 문자열에 null 문자를 포함 할 수도 있습니다. 그래서 그것은 그것보다 조금 더 복잡합니다. 또한 그 포인터 앞에있는 길이 접두사를 검색해야합니다. docs.microsoft.com/en-us/previous-versions/windows/desktop/…
Wim Coenen

@WimCoenen : 사실이지만 중요하지 않습니다. BSTR에 저장된 길이는에서 이미 사용 가능한 길이의 복사본입니다 SecureString.Length.
Ben Voigt 19

@BenVoigt 아, 내 나쁜. SecureString이 문자열에 대한 정보를 노출하지 않았다고 생각했습니다.
Wim Coenen 19.11.

@WimCoenen : SecureString값을 숨기려고하는 것이 아니라, 가비지 수집 된 메모리, 페이지 파일 등과 같이 안정적으로 덮어 쓸 수없는 영역에 값의 복사본이 만들어지지 않도록 방지하려고합니다. 의도는 SecureString수명이 끝날 때 절대적으로 비밀의 사본은 기억에 남지 않습니다. 그것은 당신이 사본을 만들고 유출하는 것을 막지는 못하지만 결코 그렇지 않습니다.
Ben Voigt

15

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

나는 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

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


14

메모리에서 해독 된 문자열을 더 잘 제어 할 수 있도록 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
}

즐거운 코딩 되세요!


섹션 Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);대신 사용 하지 fixed않겠습니까?
sclarke81

@ sclarke81, 좋은 생각,하지만 당신은 사용해야합니다 [char], 없습니다 [byte].
mklement0

1
전반적인 접근 방식은 유망하지만 안전하지 않은 (일반 텍스트) 복사본을 포함하는 관리되는 문자열을 고정하려는 시도가 효과적이라고 생각하지 않습니다. 대신 고정하는 것은 초기화 한 원래 문자열 개체입니다 String.Empty. 에서 생성 및 반환 한 새로 할당 된 인스턴스가 아닙니다 Marshal.PtrToStringUni().
mklement0

7

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

코드가 문자열 사본을 유출하지는 않지만 여전히 절망구덩이를 나타냅니다 . System.String개체 에 대한 거의 모든 작업은 고정 및 삭제되지 않은 복사본을 만듭니다. 이것이 SecureString.
Ben Voigt

좋지만 전체 문자열을 0으로 만들려면 사용해야합니다 new char[length](또는으로 곱 length해야 함 sizeof(char)).
mklement0

@BenVoigt : action델리게이트가 임시로 고정 된 다음 비워진 문자열의 복사본을 생성하지 않는 한,이 접근 방식은 SecureString그 자체 만큼 안전하거나 안전하지 않아야합니다 . 후자를 사용하려면 일반 텍스트 표현도해야합니다. 보안 문자열이 OS 수준의 구조가 아니라는 점을 감안할 때 어느 시점에서 생성되어야합니다. 상대적 보안은 해당 문자열의 수명을 제어하고 사용 후 지워지는 것을 보장하는 데 있습니다.
mklement0

@ mklement0 : 여기 저기 SecureString복사하는 멤버 함수와 오버로드 된 연산자가 없습니다. System.String그렇습니다.
Ben Voigt

1
@ mklement0 : .NET Framework를 NetworkCredential허용하는 생성자에 전달한다는 점을 고려하면 어리석은 일 SecureString입니다.
Ben Voigt

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

0

나는 sclarke81의 This answer 에서 파생되었습니다 . 나는 그의 대답을 좋아하고 파생물을 사용하고 있지만 sclarke81에는 버그가 있습니다. 평판이 없어서 댓글을 달 수 없습니다. 문제는 다른 답변을 보장 할 수 없을 정도로 충분히 작아 보이며 편집 할 수 있습니다. 그래서 했어요. 거부되었습니다. 이제 우리는 또 다른 답을 얻었습니다.

sclarke81 나는 당신이 이것을 보길 바랍니다 (드디어) :

Marshal.Copy(new byte[length], 0, insecureStringPointer, length);

해야한다:

Marshal.Copy(new byte[length * 2], 0, insecureStringPointer, length * 2);

그리고 버그 수정에 대한 전체 답변 :


    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// Generic type returned by Func delegate.
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// 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.
    /// 
    public static T UseDecryptedSecureString(this SecureString secureString, Func 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 * 2], 0, insecureStringPointer, length * 2);
            insecureStringHandler.Free();

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

    /// 
    /// Allows a decrypted secure string to be used whilst minimising the exposure of the
    /// unencrypted string.
    /// 
    /// The string to decrypt.
    /// 
    /// Func delegate which will receive the decrypted password as a string object
    /// 
    /// Result of Func delegate
    /// 
    /// 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.
    /// 
    public static void UseDecryptedSecureString(this SecureString secureString, Action action)
    {
        UseDecryptedSecureString(secureString, (s) =>
        {
            action(s);
            return 0;
        });
    }
}

좋은 지적; OP에 알려야하는 참조 된 답변에 대한 의견을 남겼습니다.
mklement0

0

sclarke81 솔루션 및 John Flaherty 수정 사항에 따른 최종 작업 솔루션은 다음과 같습니다.

    public static class Utils
    {
        /// <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 * 2], 0, insecureStringPointer, length * 2);
                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;
            });
        }
    }

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

이 답변에는 메모리 누수가 있습니다.
Ben Voigt 2015 년

@BenVoigt 메모리 누수가 어떻게 발생하는지 자세히 설명해 주시겠습니까?
El Ronnoco

4
@ElRonnoco : BSTR명시 적으로 해제하는 것은 없으며 .NET 개체가 아니므로 가비지 수집기에서도 처리하지 않습니다. 5 년 전에 게시되었으며 유출되지 않은 stackoverflow.com/a/818709/103167 과 비교하십시오 .
Ben Voigt

이 답변은 Windows가 아닌 플랫폼에서는 작동하지 않습니다. PtrToStringAuto는 설명이 잘못되었습니다. github.com/PowerShell/PowerShell/issues/…
K. Frank

-5

StringBuilder대신 a를 사용하면 string완료했을 때 메모리의 실제 값을 덮어 쓸 수 있습니다. 이렇게하면 가비지 컬렉션에서 암호를 찾을 때까지 암호가 메모리에 저장되지 않습니다.

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

2
이것이 사실이지만, 가비지 수집기는 생성 압축 중에 메모리에서 StringBuilder 버퍼를 계속 이동할 수 있습니다. 이로 인해 "실제 값 덮어 쓰기"가 실패합니다. 왜냐하면 파괴되지 않은 다른 (또는 그 이상) 남은 복사본이 있기 때문입니다.
Ben Voigt

4
이것은 질문에 원격으로 대답하지도 않습니다.
Jay Sullivan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.