Làm cách nào để chuyển đổi SecureString sang System.String?


156

Tất cả dè dặt về unsecuring SecureString của bạn bằng cách tạo ra một System.String ra khỏi nó sang một bên , làm thế nào nó có thể được thực hiện?

Làm cách nào tôi có thể chuyển đổi một System.Security.SecureString sang System.String thông thường?

Tôi chắc rằng nhiều bạn quen thuộc với SecureString sẽ phản hồi rằng người ta không bao giờ nên chuyển đổi SecureString thành một chuỗi .NET thông thường vì nó loại bỏ tất cả các biện pháp bảo vệ. Tôi biết . Nhưng ngay bây giờ chương trình của tôi vẫn làm mọi thứ với các chuỗi thông thường và tôi đang cố gắng tăng cường bảo mật và mặc dù tôi sẽ sử dụng API trả lại SecureString cho tôi nhưng tôi không cố sử dụng nó để tăng cường bảo mật.

Tôi biết về Marshal.SecureStringToBSTR, nhưng tôi không biết cách lấy BSTR đó và tạo một System.String ra khỏi nó.

Đối với những người có thể yêu cầu biết lý do tại sao tôi muốn làm điều này, tôi sẽ lấy mật khẩu từ người dùng và gửi nó dưới dạng POST dạng POST để đăng nhập người dùng vào trang web. Vì vậy, ... điều này thực sự phải được thực hiện với các bộ đệm được quản lý, không được mã hóa. Nếu tôi thậm chí có thể có quyền truy cập vào bộ đệm không được quản lý, không được mã hóa, tôi tưởng tượng rằng tôi có thể thực hiện ghi luồng từng byte trên luồng mạng và hy vọng rằng điều đó sẽ giữ mật khẩu an toàn toàn bộ. Tôi hy vọng có câu trả lời cho ít nhất một trong những kịch bản này.

Câu trả lời:


191

Sử dụng System.Runtime.InteropServices.Marshallớp:

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

Nếu bạn muốn tránh tạo một đối tượng chuỗi được quản lý, bạn có thể truy cập dữ liệu thô bằng cách sử dụng 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
Có được sự bình chọn của tôi thậm chí nhiều năm sau đó, cảm ơn vì sự giúp đỡ! Chỉ cần một lưu ý nhanh: điều này cũng hoạt động như một tĩnh, trong bộ nhớ riêng của nó.
John Suit

Tôi đã sử dụng StopWatchSecureStringToStringmất 4.6 giây để chạy. Nó làm chậm cho tôi. Có ai nhận được cùng một lúc hoặc một cái gì đó nhanh hơn?
radbyx

@radbyx Trong một thiết lập thử nghiệm nhanh và bẩn, tôi có thể gọi nó 1000 lần trong 76ms. Yêu cầu đầu tiên mất 0,3 ms và các lần gọi tiếp theo ~ 0,07ms. Chuỗi bảo mật của bạn lớn đến mức nào và bạn đang sử dụng phiên bản khung nào?
Rasmus Faber

Độ dài om của tôi SecureString là 168. Tôi đang sử dụng .NET Framework 3.5 nếu điều đó trả lời câu hỏi của bạn? Tôi đã thử 5-10 lần luôn trong khoảng 4,5-4,65 giây ~ Tôi rất muốn có được thời gian của bạn
radbyx

@RasmusFaber Thật tệ, tôi đã thêm một Database.GetConnectionString()mã vào mã của bạn, để lấy SecureString của tôi, đó là phần xấu mất gần 5 giây (và vâng tôi nên xem xét điều đó! :) Mã của bạn mất 0,00 giây trong đồng hồ bấm giờ của tôi tất cả đều tốt Cám ơn đã chỉ tôi hướng đi đúng.
radbyx

108

Rõ ràng là bạn biết làm thế nào điều này đánh bại toàn bộ mục đích của SecureString, nhưng dù sao tôi cũng sẽ khôi phục nó.

Nếu bạn muốn có một lớp lót, hãy thử điều này: (Chỉ .NET 4 trở lên)

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

Trong đó SecurePassword là SecureString.


10
Mặc dù nó không đánh bại mục đích trong sản xuất, giải pháp của bạn là hoàn hảo cho các bài kiểm tra đơn vị. Cảm ơn.
beterthanlife

Điều này giúp tôi nhận ra rằng SecureString (System.Security.SecureString) đã không được chuyển đến ApiControll của tôi (webapi). Thx
granadaCoder

5
Lưu ý trong PowerShell đây là[System.Net.NetworkCredential]::new('', $securePassword).Password
stijn

1
@ TheIncorritable1 bạn có thể giải thích? Ví dụ khi ''không cùng loại với [String]::Empty? Cũng New-Object Net.Credentialkhông hoạt động đối với tôi: Không thể tìm thấy loại [Net.Credential]: xác minh rằng hội đồng chứa loại này đã được tải
stijn

2
Nó đánh bại mục đích của SecureString vì nó tạo một bản sao không được mã hóa của nội dung SecureString của bạn thành một chuỗi bình thường. Mỗi khi bạn làm điều đó, bạn sẽ thêm ít nhất một (và với Bộ sưu tập rác có thể nhiều bản sao) của chuỗi không được mã hóa vào bộ nhớ. Đây được coi là rủi ro đối với một số ứng dụng nhạy cảm bảo mật và SecureString được triển khai đặc biệt để giảm rủi ro.
Steve In CO

49

Đăng. Ngay sau khi đăng bài này, tôi đã tìm thấy câu trả lời sâu trong bài viết này . Nhưng nếu bất cứ ai biết cách truy cập vào bộ đệm IntPtr không được quản lý, không được mã hóa mà phương thức này lộ ra, mỗi lần một byte để tôi không phải tạo một đối tượng chuỗi được quản lý từ đó để giữ bảo mật cao, vui lòng thêm câu trả lời. :)

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

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

Bạn chắc chắn có thể sử dụng unsafetừ khóa và a char*, chỉ cần gọi bstr.ToPointer()và bỏ.
Ben Voigt

@BenVoigt BSTR có bộ kết thúc null sau dữ liệu chuỗi để đảm bảo an toàn, nhưng cũng cho phép các ký tự null được nhúng trong chuỗi. Vì vậy, nó phức tạp hơn thế một chút, bạn cũng cần truy xuất tiền tố độ dài nằm trước con trỏ đó. docs.microsoft.com/en-us/preingly-versions/windows/desktop/iêu
Wim Coenen

@WimCoenen: Đúng nhưng không quan trọng. Độ dài được lưu trữ trong BSTR sẽ là bản sao độ dài đã có sẵn từ đó SecureString.Length.
Ben Voigt

@BenVoigt ah, xấu của tôi. Tôi nghĩ SecureString đã không tiết lộ bất kỳ thông tin nào về chuỗi.
Wim Coenen

@WimCoenen: SecureStringkhông cố gắng che giấu giá trị, nó đang cố gắng để ngăn chặn bản có giá trị từ được thực hiện thành các vùng mà không thể được ghi đè đáng tin cậy, chẳng hạn như thu gom rác thải memory, pagefile vv Mục đích là khi SecureStringđời đầu, hoàn toàn không có bản sao của bí mật vẫn còn trong bộ nhớ. Nó không ngăn bạn tạo và rò rỉ một bản sao, nhưng nó không bao giờ làm được.
Ben Voigt

15

Theo tôi, phương pháp mở rộng là cách thoải mái nhất để giải quyết vấn đề này.

Tôi đã đưa Steve vào câu trả lời tuyệt vời của CO và đưa nó vào một lớp mở rộng như sau, cùng với một phương thức thứ hai tôi đã thêm để hỗ trợ hướng khác (chuỗi -> chuỗi bảo mật), vì vậy bạn có thể tạo một chuỗi bảo mật và chuyển đổi nó thành một chuỗi bình thường sau đó:

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

Với điều này, bây giờ bạn có thể chỉ cần chuyển đổi chuỗi của bạn qua lại như vậy:

// 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

Nhưng hãy nhớ rằng phương pháp giải mã chỉ nên được sử dụng để thử nghiệm.


14

Tôi nghĩ rằng nó là tốt nhất cho các SecureStringchức năng phụ thuộc để đóng gói logic phụ thuộc của chúng trong một hàm ẩn danh để kiểm soát tốt hơn chuỗi được giải mã trong bộ nhớ (một khi được ghim).

Việc triển khai để giải mã SecureStrings trong đoạn mã này sẽ:

  1. Ghim chuỗi trong bộ nhớ (đó là những gì bạn muốn làm nhưng dường như bị thiếu trong hầu hết các câu trả lời ở đây).
  2. Chuyển tham chiếu của nó cho đại biểu Func / Action.
  3. Chà nó khỏi bộ nhớ và giải phóng GC trong finallykhối.

Điều này rõ ràng làm cho việc "chuẩn hóa" và duy trì người gọi dễ dàng hơn nhiều so với việc dựa vào các lựa chọn ít mong muốn hơn:

  • Trả về chuỗi được giải mã từ string DecryptSecureString(...)hàm trợ giúp.
  • Sao chép mã này bất cứ nơi nào cần thiết.

Lưu ý ở đây, bạn có hai lựa chọn:

  1. static T DecryptSecureString<T>cho phép bạn truy cập kết quả của Funcđại biểu từ người gọi (như thể hiện trong phầnDecryptSecureStringWithFunc phương thức kiểm tra).
  2. static void DecryptSecureStringchỉ đơn giản là một phiên bản "void" sử dụng một Actionđại biểu trong trường hợp bạn thực sự không muốn / cần phải trả lại bất cứ điều gì (như thể hiện trong DecryptSecureStringWithActionphương pháp thử nghiệm).

Sử dụng ví dụ cho cả hai có thể được tìm thấy trong StringsTestlớp bao gồm.

Chuỗi.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;
            });
        }
    }
}

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

Rõ ràng, điều này không ngăn chặn việc lạm dụng chức năng này theo cách sau, vì vậy hãy cẩn thận không làm điều này:

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

Chúc mừng mã hóa!


Tại sao không sử dụng Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);thay vì fixedphần?
sclarke81

@ sclarke81, ý tưởng tốt, nhưng bạn sẽ cần sử dụng [char], không [byte].
mkuity0

1
Cách tiếp cận tổng thể hứa hẹn, nhưng tôi không nghĩ rằng nỗ lực của bạn tại ghim chuỗi quản lý có chứa không an toàn (plain-text) bản sao có hiệu quả: những gì bạn đang ghim thay vì là ban đầu đối tượng chuỗi mà bạn đã khởi tạo String.Empty, không phải là trường hợp mới được phân bổ được tạo và trả về bởi Marshal.PtrToStringUni().
mkuity0

7

Tôi đã tạo các phương thức mở rộng sau dựa trên câu trả lời từ ndev5 . Ghim chuỗi được quản lý rất quan trọng vì nó ngăn người thu gom rác di chuyển nó xung quanh và để lại các bản sao mà bạn không thể xóa.

Tôi nghĩ rằng lợi thế của giải pháp của tôi là không cần mã không an toàn.

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

Mặc dù mã của bạn không rò rỉ một bản sao của chuỗi, nó vẫn đại diện cho một hố sâu tuyệt vọng . Gần như mọi thao tác trên System.Stringđối tượng sẽ tạo ra các bản sao không được chỉnh sửa và không được chỉnh sửa. Đó là lý do tại sao điều này không được tích hợp vào SecureString.
Ben Voigt

Đẹp, mặc dù để loại bỏ toàn bộ chuỗi bạn sẽ phải sử dụng new char[length](hoặc nhân lênlength với sizeof(char)).
mkuity0

@BenVoigt: Miễn là actionđại biểu không tạo ra các bản sao của chuỗi tạm thời, được ghim, sau đó bỏ qua, cách tiếp cận này sẽ an toàn hoặc không an toàn nhưSecureString chính nó - để sử dụng sau này, một biểu diễn văn bản đơn giản cũng phải được tạo tại một số điểm, do các chuỗi bảo mật không phải là các cấu trúc cấp hệ điều hành; bảo mật tương đối đến từ việc kiểm soát tuổi thọ của chuỗi đó và đảm bảo rằng nó bị xóa sau khi sử dụng.
mkuity0

@ mkuity0: SecureStringkhông có chức năng thành viên và toán tử quá tải tạo bản sao ở mọi nơi. System.Stringlàm.
Ben Voigt

1
@ mkuity0: Điều này khá vô lý khi xem xét rằng nó chuyển nó đến nhà NetworkCredentialxây dựng mà DOES chấp nhận a SecureString.
Ben Voigt

0

Mã C # này là những gì bạn muốn.

%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

Tôi bắt nguồn từ câu trả lời này của sclarke81 . Tôi thích câu trả lời của anh ấy và tôi đang sử dụng công cụ phái sinh nhưng sclarke81 có một lỗi. Tôi không có tiếng tăm nên tôi không thể bình luận. Vấn đề dường như đủ nhỏ để nó không đảm bảo một câu trả lời khác và tôi có thể chỉnh sửa nó. Tôi cũng vậy. Nó đã bị từ chối. Vì vậy, bây giờ chúng tôi có một câu trả lời khác.

sclarke81 Tôi hy vọng bạn thấy điều này (cuối cùng):

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

nên là:

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

Và câu trả lời đầy đủ với sửa lỗi:


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

Điểm tốt; Tôi đã để lại nhận xét về câu trả lời được tham chiếu, cần thông báo cho OP.
mkuity0

0

Giải pháp làm việc cuối cùng theo giải pháp sclarke81 và bản sửa lỗi John Flaherty là:

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

Câu trả lời này có một rò rỉ bộ nhớ.
Ben Voigt

@BenVoigt Bạn có thể giải thích thêm xin vui lòng làm thế nào điều này có rò rỉ bộ nhớ?
El Ronnoco

4
@ElRonnoco: Không có gì giải phóng BSTRrõ ràng và nó không phải là một đối tượng .NET nên trình thu gom rác cũng không quan tâm đến nó. So sánh với stackoverflow.com/a/818709/103167 đã được đăng 5 năm trước và không bị rò rỉ.
Ben Voigt

Câu trả lời này không hoạt động trên các nền tảng không phải là windows. PtrToStringAuto sai khi giải thích, xem: github.com/PowerShell/PowerShell/issues/ mẹo
K. Frank

-5

Nếu bạn sử dụng StringBuilderthay vì a string, bạn có thể ghi đè giá trị thực trong bộ nhớ khi bạn hoàn tất. Bằng cách đó, mật khẩu sẽ không bị treo trong bộ nhớ cho đến khi bộ sưu tập rác nhặt được.

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

2
Mặc dù điều này là đúng, trình thu gom rác vẫn có thể di chuyển bộ đệm StringBuilder trong bộ nhớ trong quá trình nén thế hệ, khiến "ghi đè giá trị thực" không thành công, vì có một bản sao còn lại (hoặc nhiều hơn) không bị phá hủy.
Ben Voigt

4
Điều này thậm chí không trả lời từ xa câu hỏi.
Jay Sullivan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.