จะแปลง SecureString เป็น System.String ได้อย่างไร


156

การจองทั้งหมดเกี่ยวกับการรักษาความปลอดภัยของ SecureString ของคุณโดยการสร้าง System.String นอกเหนือจากมันจะทำอย่างไร?

ฉันจะแปลง System.Security.SecureString สามัญเป็น System.String สามัญได้อย่างไร

ฉันแน่ใจว่าพวกคุณหลายคนที่คุ้นเคยกับ SecureString จะตอบว่าไม่ควรแปลง SecureString เป็นสตริง. NET ธรรมดาเพราะมันจะเอาการป้องกันความปลอดภัยทั้งหมด ฉันรู้ว่า แต่ตอนนี้โปรแกรมของฉันทำทุกอย่างด้วยสตริงปกติอยู่แล้วและฉันพยายามปรับปรุงความปลอดภัยของมันและถึงแม้ว่าฉันจะใช้ API ที่ส่งคืน SecureString ให้ฉันฉันไม่ได้พยายามใช้สิ่งนั้นเพื่อเพิ่มความปลอดภัยของฉัน

ฉันรู้เรื่อง Marshal.SecureStringToBSTR แต่ฉันไม่รู้ว่าจะใช้ BSTR นั้นอย่างไรและสร้างระบบออกมา

สำหรับผู้ที่อาจต้องการทราบว่าทำไมฉันต้องการทำเช่นนี้ดีฉันใช้รหัสผ่านจากผู้ใช้และส่งเป็นแบบฟอร์ม POST แบบ HTML เพื่อบันทึกผู้ใช้ลงในเว็บไซต์ ดังนั้น ... สิ่งนี้จะต้องมีการจัดการบัฟเฟอร์ที่ไม่ได้เข้ารหัส หากฉันสามารถเข้าถึงบัฟเฟอร์ที่ไม่ได้รับการจัดการฉันจินตนาการว่าฉันสามารถทำการสตรีมไบต์ต่อไบต์ที่เขียนบนสตรีมเครือข่ายและหวังว่าจะช่วยให้รหัสผ่านปลอดภัยตลอดทาง ฉันหวังว่าจะได้คำตอบสำหรับสถานการณ์เหล่านี้อย่างน้อยหนึ่งสถานการณ์

คำตอบ:


192

ใช้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

ฉันใช้StopWatchและSecureStringToStringใช้ 4.6sec เพื่อทำงาน มันจะช้าสำหรับฉัน ไม่มีใครได้รับเวลาเดียวกันหรือบางสิ่งบางอย่างเร็วขึ้น?
radbyx

@radbyx ในการตั้งค่าการทดสอบที่รวดเร็วและสกปรกฉันสามารถเรียกมันได้ 1,000 ครั้งใน 76ms การเรียกใช้ครั้งแรกใช้เวลา 0.3 ms และการเรียกใช้ที่ตามมา ~ 0.07ms สตริงที่ปลอดภัยของคุณมีขนาดใหญ่เพียงใดและคุณใช้เฟรมเวิร์กเวอร์ชันใด
Rasmus Faber

ความยาวของ SecureString ของฉันคือ 168 ฉันกำลังใช้. NET Framework 3.5 หากตอบคำถามของคุณ ฉันได้ลอง 5-10 ครั้งอยู่ที่ประมาณ 4.5-4.65 วินาที ~ ฉันชอบที่จะได้รับเวลาของคุณ
radbyx

@RasmusFaber ฉันไม่ดีฉันได้เพิ่มDatabase.GetConnectionString()รหัสของคุณเพื่อรับ secureString ของฉันซึ่งเป็นส่วนที่ชั่วร้ายที่ใช้เวลาเกือบ 5 วินาที (และใช่ฉันควรมองเข้าไปในนั้น! :) รหัสของคุณใช้เวลา 0.00 ล้านวินาทีในนาฬิกาจับเวลาของฉันดังนั้นมันจึง ทั้งหมดดี. ขอบคุณที่ชี้นำฉันในทิศทางที่ถูกต้อง
radbyx

108

เห็นได้ชัดว่าคุณรู้ว่าสิ่งนี้เอาชนะจุดประสงค์ทั้งหมดของ SecureString แต่ฉันจะกล่าวซ้ำ

ถ้าคุณต้องการซับแบบหนึ่งให้ลองสิ่งนี้: (. NET 4 ขึ้นไปเท่านั้น)

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

โดยที่ securePassword คือ SecureString


10
แม้ว่ามันจะเอาชนะวัตถุประสงค์ในการผลิต แต่โซลูชันของคุณสมบูรณ์แบบสำหรับการทดสอบหน่วย ขอบคุณ
beterthanlife

สิ่งนี้ช่วยให้ฉันทราบว่า SecureString (System.Security.SecureString) ไม่ได้ถูกส่งผ่านไปยัง ApiController (webapi) ของฉัน ขอบคุณ
granadaCoder

5
หมายเหตุใน PowerShell นี่คือ[System.Net.NetworkCredential]::new('', $securePassword).Password
stijn

1
@ TheIncorrential1 คุณสามารถทำอย่างละเอียด? เช่นเมื่อ''ไม่ได้เป็นชนิดเดียวกับที่[String]::Empty? ยังNew-Object Net.Credentialใช้งานไม่ได้สำหรับฉัน: ไม่สามารถหาประเภท [Net.Credential]: ตรวจสอบว่ามีการโหลดแอสเซมบลีที่มีประเภทนี้อยู่
stijn

2
มันเอาชนะวัตถุประสงค์ของ SecureString เพราะทำสำเนาเนื้อหา SecureString ของคุณที่ไม่ได้เข้ารหัสในสตริงปกติ ทุกครั้งที่คุณทำเช่นนั้นคุณกำลังเพิ่มอย่างน้อยหนึ่งสำเนา (และอาจมี Garbage Collection หลายสำเนา) ของสตริงที่ไม่ได้เข้ารหัสในหน่วยความจำ สิ่งนี้ถือว่าเป็นความเสี่ยงสำหรับแอปพลิเคชันที่มีความสำคัญด้านความปลอดภัยและมีการใช้ 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คำหลักและ a 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

@ BenVoigt อ่าฉันแย่มาก ฉันคิดว่า SecureString ไม่เปิดเผยข้อมูลใด ๆ เกี่ยวกับสตริง
Wim Coenen

@WimCoenen: SecureStringไม่ได้พยายามซ่อนค่า แต่พยายามป้องกันไม่ให้มีการทำสำเนาค่าในภูมิภาคที่ไม่สามารถเขียนทับได้อย่างเชื่อถือได้เช่นหน่วยความจำเก็บขยะ, ไฟล์เพจเป็นต้นความตั้งใจคือเมื่อSecureStringอายุการใช้งานสิ้นสุดลงอย่างแน่นอน ไม่มีสำเนาลับในหน่วยความจำ มันไม่ได้ป้องกันคุณจากการทำและการลอกเลียนแบบ แต่ไม่เคยทำ
Ben Voigt

15

ในความคิดของฉันวิธีการขยายเป็นวิธีที่สะดวกสบายที่สุดในการแก้ปัญหานี้

ฉันใช้คำตอบที่ดีเลิศของสตีฟ ไปที่คลาสต่อไปนี้พร้อมด้วยวิธีที่สองที่ฉันเพิ่มเพื่อสนับสนุนทิศทางอื่น (สตริง -> สตริงที่ปลอดภัย) เช่นกันดังนั้นคุณสามารถสร้างสตริงที่ปลอดภัยและแปลงเป็น สตริงปกติหลังจากนั้น:

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 / Action
  3. ขัดออกจากหน่วยความจำและปล่อย GC ในfinallyบล็อก

เห็นได้ชัดว่าสิ่งนี้ทำให้ง่ายขึ้นมากในการ "สร้างมาตรฐาน" และรักษาผู้โทรไว้กับการพึ่งพาทางเลือกที่ต้องการน้อยกว่า:

  • การส่งคืนสตริงถอดรหัสจากstring DecryptSecureString(...)ฟังก์ชันตัวช่วย
  • ทำซ้ำรหัสนี้ทุกที่ที่จำเป็น

ประกาศที่นี่คุณมีสองตัวเลือก:

  1. static T DecryptSecureString<T>ซึ่งช่วยให้คุณสามารถเข้าถึงผลลัพธ์ของFuncผู้รับมอบสิทธิ์จากผู้โทร (ดังที่แสดงในDecryptSecureStringWithFuncวิธีการทดสอบ)
  2. static void DecryptSecureStringเป็นเพียงรุ่น "โมฆะ" ซึ่งใช้Actionผู้รับมอบสิทธิ์ในกรณีที่คุณไม่ต้องการ / จำเป็นต้องส่งคืนสิ่งใด (ตามที่แสดงใน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

ดีแม้ว่าจะเป็นศูนย์สตริงทั้งหมดที่คุณจะต้องใช้new char[length](หรือคูณlengthด้วยsizeof(char))
mklement0

@BenVoigt: ตราบใดที่actionผู้ร่วมประชุมไม่ได้สร้างสำเนาของสตริงชั่วคราว, ตรึง, แล้ว zeroed-out วิธีการนี้ควรจะปลอดภัยหรือไม่ปลอดภัยเหมือนSecureStringตัวเอง - เพื่อใช้หลัง, การแสดงข้อความธรรมดาก็ต้อง ถูกสร้างขึ้นในบางจุดเนื่องจากสตริงที่ปลอดภัยไม่ใช่โครงสร้างระดับ OS ความปลอดภัยสัมพัทธ์มาจากการควบคุมอายุการใช้งานของสตริงนั้นและทำให้แน่ใจว่าจะถูกลบทิ้งหลังการใช้งาน
mklement0

@ mklement0: SecureStringไม่มีฟังก์ชั่นสมาชิกและโอเปอเรเตอร์ที่โอเวอร์โหลดที่ทำสำเนาทั่วสถานที่ System.Stringทำ.
Ben Voigt

1
@ mklement0: ซึ่งเป็นสวยไร้สาระยี้พิจารณาว่ามันผ่านไปยังคอนสตรัคซึ่งไม่ยอมรับ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 ฉันชอบคำตอบของเขาและฉันใช้อนุพันธ์ แต่ 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

@BenVoigt คุณช่วยอธิบายเพิ่มเติมได้ไหมว่าหน่วยความจำรั่วได้อย่างไร
El Ronnoco

4
@ElRonnoco: ไม่มีอะไรทำให้BSTRชัดเจนและมันไม่ใช่วัตถุ. NET ดังนั้นตัวรวบรวมขยะก็ไม่สนใจเช่นกัน เปรียบเทียบกับstackoverflow.com/a/818709/103167ซึ่งโพสต์เมื่อ 5 ปีก่อนและไม่รั่วไหล
Ben Voigt

คำตอบนี้ใช้ไม่ได้กับแพลตฟอร์มที่ไม่ใช่ windows PtrToStringAuto ผิดสำหรับคำอธิบายดู: github.com/PowerShell/PowerShell/issues/ ......
K. Frank

-5

หากคุณใช้ a StringBuilderแทนคุณstringสามารถเขียนทับค่าจริงในหน่วยความจำเมื่อคุณทำเสร็จแล้ว ด้วยวิธีนี้รหัสผ่านจะไม่แขวนในหน่วยความจำจนกว่าการเก็บรวบรวมขยะจะเก็บมัน

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

2
แม้ว่าสิ่งนี้จะเป็นจริงตัวเก็บรวบรวมขยะอาจยังคงย้ายบัฟเฟอร์ StringBuilder ไปรอบ ๆ ในหน่วยความจำในระหว่างการบีบอัด generational ซึ่งทำให้ "เขียนทับค่าที่แท้จริง" ล้มเหลวเนื่องจากมีสำเนาที่เหลืออีก (หรือมากกว่า) ที่ไม่ถูกทำลาย
Ben Voigt

4
สิ่งนี้ไม่ได้ตอบคำถามจากระยะไกล
Jay Sullivan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.