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.