Bagaimana cara mengubah SecureString ke System.String?

156

Semua keberatan tentang unsecuring SecureString Anda dengan menciptakan System.String dari itu selain , bagaimana hal itu dapat dilakukan?

Bagaimana saya bisa mengubah System.Security.SecureString biasa ke System.String?

Saya yakin banyak dari Anda yang terbiasa dengan SecureString akan menjawab bahwa seseorang tidak boleh mengubah SecureString menjadi string .NET biasa karena menghapus semua perlindungan keamanan. Aku tahu . Tetapi sekarang program saya melakukan semuanya dengan string biasa, dan saya mencoba untuk meningkatkan keamanannya dan meskipun saya akan menggunakan API yang mengembalikan SecureString kepada saya, saya tidak mencoba menggunakannya untuk meningkatkan keamanan saya.

Saya mengetahui Marshal. SecureStringToBSTR, tapi saya tidak tahu bagaimana cara mengambil BSTR itu dan membuat System.String darinya.

Bagi mereka yang mungkin ingin tahu mengapa saya ingin melakukan ini, saya mengambil kata sandi dari pengguna dan mengirimkannya sebagai bentuk html POST untuk mencatat pengguna ke situs web. Jadi ... ini benar-benar harus dilakukan dengan buffer yang dikelola dan tidak terenkripsi. Jika saya bahkan bisa mendapatkan akses ke buffer yang tidak dikelola dan tidak terenkripsi, saya bayangkan saya bisa menulis aliran byte-by-byte pada aliran jaringan dan berharap itu menjaga keamanan kata sandi sepanjang jalan. Saya berharap jawaban untuk setidaknya satu dari skenario ini.

Andrew Arnott
sumber

Jawaban:

192

Gunakan System.Runtime.InteropServices.Marshalkelas:

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

Jika Anda ingin menghindari membuat objek string yang dikelola, Anda dapat mengakses data mentah menggunakan 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);
  }
}
Rasmus Faber
sumber
1
Terima suara saya bertahun-tahun kemudian, terima kasih atas bantuannya! Hanya catatan singkat: ini juga berfungsi sebagai statis, dalam ingatannya sendiri.
John Suit
Saya menggunakan StopWatchdan SecureStringToStringmengambil 4.6sec untuk dijalankan. Ini lambat bagiku. Adakah yang mendapatkan waktu yang sama atau sesuatu yang lebih cepat?
radbyx
@radbyx Dalam pengaturan tes cepat dan kotor, saya bisa menyebutnya 1000 kali dalam 76ms. Doa pertama membutuhkan 0,3 ms dan doa berikutnya ~ 0,07 ms. Seberapa besar string aman Anda dan versi kerangka mana yang Anda gunakan?
Rasmus Faber
Panjang om my secureString adalah 168. Saya menggunakan .NET Framework 3.5 jika itu menjawab pertanyaan Anda? Saya telah mencoba 5-10 kali selalu sekitar 4,5-4,65 detik ~ Saya akan senang mendapatkan waktu Anda
radbyx
@RasmusFaber Saya buruk, saya telah menambahkan Database.GetConnectionString()ke dalam kode Anda, untuk mendapatkan secureString saya, yang merupakan bagian jahat yang memakan waktu hampir 5 detik (dan ya saya harus melihat ke dalamnya! :) Kode Anda mengambil 0,00 mili detik di stopwatch saya jadi semuanya bagus. Terima kasih telah mengarahkan saya ke arah yang benar.
radbyx
108

Jelas Anda tahu bagaimana ini mengalahkan seluruh tujuan SecureString, tapi saya akan menyatakannya kembali.

Jika Anda menginginkan one-liner, coba ini: (.NET 4 dan di atasnya)

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

Di mana securePassword adalah SecureString.

Steve In CO
sumber
10
Meskipun tidak mengalahkan tujuan dalam produksi, solusi Anda sangat cocok untuk pengujian unit. Terima kasih.
beterthanlife
Ini membantu saya untuk mengetahui bahwa SecureString (System.Security.SecureString) tidak diteruskan ke ApiController saya (webapi). Thx
granadaCoder
5
Catatan di PowerShell ini[System.Net.NetworkCredential]::new('', $securePassword).Password
stijn
1
@ TheIncorrigible1 dapatkah Anda menguraikan? Misal kapan ''bukan tipe yang sama [String]::Empty? Juga New-Object Net.Credentialtidak berfungsi untuk saya: Tidak dapat menemukan tipe [Net.Kredensial]: verifikasi bahwa rakitan yang berisi tipe ini dimuat
stijn
2
Ini mengalahkan tujuan SecureString karena membuat salinan konten SecureString Anda yang tidak dienkripsi ke string normal. Setiap kali Anda melakukannya, Anda menambahkan setidaknya satu (dan dengan Koleksi Sampah kemungkinan banyak) salinan string yang tidak terenkripsi ke memori. Ini dianggap sebagai risiko untuk beberapa aplikasi sensitif keamanan dan SecureString diimplementasikan secara khusus untuk mengurangi risiko.
Steve In CO
49

Dang. tepat setelah memposting ini saya menemukan jawabannya dalam artikel ini . Tetapi jika ada yang tahu bagaimana mengakses buffer yang tidak dikelola, tidak terenkripsi IntPtr yang mengekspos metode ini, satu byte pada suatu waktu sehingga saya tidak harus membuat objek string terkelola darinya untuk menjaga keamanan saya tinggi, silakan tambahkan jawaban. :)

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

    try
    {
        return Marshal.PtrToStringBSTR(bstr);
    }
    finally
    {
        Marshal.FreeBSTR(bstr);
    }
}
Andrew Arnott
sumber
Anda tentu dapat menggunakan unsafekata kunci dan char*, cukup panggil bstr.ToPointer()dan putar.
Ben Voigt
@BenVoigt BSTR memiliki terminator nol setelah data string untuk keamanan, tetapi juga memungkinkan karakter null yang tertanam dalam string. Jadi, ini sedikit lebih rumit dari itu, Anda juga perlu mengambil awalan panjang yang duduk sebelum pointer itu. docs.microsoft.com/en-us/previous-versions/windows/desktop/…
Wim Coenen
@WimCoenen: Benar tapi tidak penting. Panjang yang disimpan dalam BSTR akan menjadi salinan dari panjang yang sudah tersedia dari SecureString.Length.
Ben Voigt
@ BenVoigt ah, salahku. Saya pikir SecureString tidak memaparkan informasi tentang string.
Wim Coenen
@WimCoenen: SecureStringtidak mencoba untuk menyembunyikan nilai, itu mencoba untuk mencegah salinan dari nilai yang dibuat menjadi daerah yang tidak dapat ditimpa dengan andal, seperti memori yang dikumpulkan sampah, pagefile, dll. Maksudnya adalah bahwa ketika masa SecureStringhidup berakhir, tentu saja tidak ada salinan rahasia yang tersisa di memori. Itu tidak mencegah Anda membuat dan membocorkan salinan, tetapi itu tidak pernah terjadi.
Ben Voigt
15

Menurut pendapat saya, metode ekstensi adalah cara paling nyaman untuk menyelesaikan ini.

Saya mengambil jawaban Steve yang luar biasa dari CO dan memasukkannya ke dalam kelas ekstensi sebagai berikut, bersama dengan metode kedua yang saya tambahkan untuk mendukung arah yang lain (string -> string yang aman) juga, sehingga Anda dapat membuat string yang aman dan mengubahnya menjadi string normal setelahnya:

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

Dengan ini, Anda sekarang dapat dengan mudah mengubah string Anda bolak - balik seperti:

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

Namun perlu diingat metode decoding hanya digunakan untuk pengujian.

Mat
sumber
14

Saya pikir itu akan menjadi yang terbaik untuk SecureStringfungsi dependen untuk merangkum logika dependen mereka dalam fungsi anonim untuk kontrol yang lebih baik atas string yang didekripsi dalam memori (setelah disematkan).

Implementasi untuk mendekripsi SecureStrings dalam cuplikan ini akan:

  1. Sematkan string dalam memori (yang ingin Anda lakukan tetapi tampaknya tidak ada sebagian besar jawaban di sini).
  2. Berikan referensi ke delegasi Func / Action.
  3. Gosok dari memori dan lepaskan GC di finallyblok.

Ini jelas membuatnya jauh lebih mudah untuk "membakukan" dan mempertahankan penelepon vs mengandalkan alternatif yang kurang diinginkan:

  • Mengembalikan string yang didekripsi dari a string DecryptSecureString(...) fungsi pembantu.
  • Menggandakan kode ini dimanapun dibutuhkan.

Perhatikan di sini, Anda memiliki dua opsi:

  1. static T DecryptSecureString<T>yang memungkinkan Anda untuk mengakses hasil Funcdelegasi dari pemanggil (seperti yang ditunjukkan dalam DecryptSecureStringWithFuncmetode pengujian).
  2. static void DecryptSecureStringhanyalah versi "void" yang mempekerjakan Actiondelegasi dalam kasus di mana Anda sebenarnya tidak ingin / perlu mengembalikan apa pun (seperti yang ditunjukkan dalam DecryptSecureStringWithActionmetode pengujian).

Contoh penggunaan untuk keduanya dapat ditemukan di StringsTestkelas yang disertakan.

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

Jelas, ini tidak mencegah penyalahgunaan fungsi ini dengan cara berikut, jadi berhati-hatilah untuk tidak melakukan ini:

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

Selamat coding!

Matt Borja
sumber
Mengapa tidak menggunakan Marshal.Copy(new byte[insecureString.Length], 0, insecureStringPointer, (int)insecureString.Length);bukan fixedbagian?
sclarke81
@ sclarke81, ide bagus, tetapi Anda harus menggunakannya [char], tidak [byte].
mklement0
1
Pendekatan keseluruhan menjanjikan, tetapi saya tidak berpikir upaya Anda menyematkan string terkelola yang berisi salinan tidak aman (teks biasa) efektif: apa yang Anda sematkan adalah objek string asli yang Anda inisialisasi String.Empty, bukan instance yang baru dialokasikan yang dibuat dan dikembalikan oleh Marshal.PtrToStringUni().
mklement0
7

Saya membuat metode ekstensi berikut berdasarkan jawaban dari rdev5 . Menyematkan string yang dikelola penting karena mencegah pengumpul sampah untuk berpindah dan meninggalkan salinan yang tidak dapat Anda hapus.

Saya pikir keuntungan dari solusi saya adalah tidak diperlukan kode yang tidak aman.

/// <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;
    });
}
sclarke81
sumber
Meskipun kode Anda tidak membocorkan salinan string, itu tetap merupakan lubang keputusasaan . Hampir setiap operasi pada System.Stringobjek akan membuat salinan tanpa penempelan dan unerased. Itu sebabnya ini tidak dibangun ke dalam SecureString.
Ben Voigt
Bagus, meskipun untuk nol seluruh string Anda harus menggunakan new char[length](atau mengalikan lengthdengan sizeof(char)).
mklement0
@BenVoigt: Selama actiondelegasi tidak membuat salinan dari string sementara, disematkan, kemudian di-zeroed-out, pendekatan ini harus seaman atau tidak aman seperti SecureStringitu sendiri - untuk menggunakan yang terakhir, representasi teks biasa juga harus dibuat pada beberapa titik, mengingat bahwa string aman bukan konstruksi tingkat OS; keamanan relatif berasal dari mengendalikan masa pakai string itu dan memastikan bahwa itu terhapus setelah digunakan.
mklement0
@ mklement0: SecureStringtidak memiliki fungsi anggota dan operator kelebihan beban yang membuat salinan di semua tempat. System.Stringtidak.
Ben Voigt
1
@ mklement0: Yang sangat sangat aneh mengingat bahwa ia meneruskannya ke NetworkCredentialkonstruktor yang TIDAK menerima SecureString.
Ben Voigt
0

Kode C # ini adalah yang Anda inginkan.

%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;
        }
    }
}
Eric Alexander Silveira
sumber
0

Saya berasal dari jawaban ini oleh sclarke81 . Saya suka jawabannya dan saya menggunakan turunan tetapi sclarke81's memiliki bug. Saya tidak punya reputasi jadi saya tidak bisa berkomentar. Masalahnya tampaknya cukup kecil sehingga tidak memerlukan jawaban lain dan saya bisa mengeditnya. Jadi saya lakukan. Itu ditolak. Jadi sekarang kita punya jawaban lain.

sclarke81 Saya harap Anda melihat ini (pada akhirnya):

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

seharusnya:

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

Dan jawaban lengkap dengan perbaikan bug:


    /// 
    /// 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;
        });
    }
}
John Flaherty
sumber
Poin bagus; Saya telah meninggalkan komentar pada jawaban yang dirujuk, yang harus memberi tahu OP.
mklement0
0

Solusi kerja akhir menurut solusi sclarke81 dan perbaikan John Flaherty adalah:

    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;
            });
        }
    }
Rustam Shafigullin
sumber
-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;
}
Jesse Motes
sumber
Jawaban ini memiliki kebocoran memori.
Ben Voigt
@ BenVoigt Bisakah Anda jelaskan lebih lanjut bagaimana ini memiliki kebocoran memori?
El Ronnoco
4
@ ElRonnoco: Tidak ada yang membebaskan BSTRsecara eksplisit, dan itu bukan objek .NET sehingga pengumpul sampah tidak mengurusnya juga. Dibandingkan dengan stackoverflow.com/a/818709/103167 yang diposting 5 tahun sebelumnya dan tidak bocor.
Ben Voigt
Jawaban ini tidak berfungsi pada platform non windows. PtrToStringAuto salah untuk penjelasan, lihat: github.com/PowerShell/PowerShell/issues/…
K. Frank
-5

Jika Anda menggunakan StringBuilderalih - alih a string, Anda dapat menimpa nilai sebenarnya dalam memori setelah selesai. Dengan begitu kata sandi tidak akan berkeliaran di memori sampai pengumpulan sampah mengambilnya.

StringBuilder.Append(plainTextPassword);
StringBuilder.Clear();
// overwrite with reasonably random characters
StringBuilder.Append(New Guid().ToString());
Michael Liben
sumber
2
Meskipun ini benar, pengumpul sampah masih dapat memindahkan buffer StringBuilder di memori selama pemadatan generasi, yang membuat "menimpa nilai aktual" gagal, karena ada salinan sisa (atau lebih) lain yang tidak dihancurkan.
Ben Voigt
4
Ini bahkan tidak menjawab pertanyaan dari jarak jauh.
Jay Sullivan