27

有没有办法在不包含安全性的情况下获得 SecureString 的值?例如,在下面的代码中,只要您执行 PtrToStringBSTR,字符串就不再安全,因为字符串是不可变的,并且垃圾回收对于字符串来说是不确定的。

IntPtr ptr = Marshal.SecureStringToBSTR(SecureString object);
string value = Marshal.PtrToStringBSTR(ptr);

如果有办法获取非托管 BSTR 字符串的 char[] 或 byte[] 怎么办?这是否意味着垃圾收集更可预测(因为您将使用 char[] 或 byte[] 而不是字符串?这个假设是否正确,如果正确,您将如何取回 char[] 或 byte[]?

4

6 回答 6

33

这是我专门为此目的编写的一门课程。是否完全 100% 防黑客?不——你几乎无法让应用程序 100% 安全,但是如果你需要将 aSecureString转换为String.

下面是你如何使用这个类:

using(SecureStringToStringMarshaler sm = new SecureStringToStringMarshaler(secureString))
{
    // Use sm.String here.  While in the 'using' block, the string is accessible
    // but pinned in memory.  When the 'using' block terminates, the string is zeroed
    // out for security, and garbage collected as usual.
}

这是课程

/// Copyright (C) 2010 Douglas Day
/// All rights reserved.
/// MIT-licensed: http://www.opensource.org/licenses/mit-license.php

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace DDay.Base
{
    public class SecureStringToStringMarshaler : IDisposable
    {
        #region Private Fields

        private string _String;
        private SecureString _SecureString;
        private GCHandle _GCH;

        #endregion

        #region Public Properties

        public SecureString SecureString
        {
            get { return _SecureString; }
            set
            {
                _SecureString = value;
                UpdateStringValue();
            }
        }

        public string String
        {
            get { return _String; }
            protected set { _String = value; }
        } 

        #endregion

        #region Constructors

        public SecureStringToStringMarshaler()
        {
        }

        public SecureStringToStringMarshaler(SecureString ss)        
        {
            SecureString = ss;
        }

        #endregion

        #region Private Methods

        void UpdateStringValue()
        {
            Deallocate();

            unsafe
            {
                if (SecureString != null)
                {
                    int length = SecureString.Length;
                    String = new string('\0', length);

                    _GCH = new GCHandle();

                    // Create a CER (Contrained Execution Region)
                    RuntimeHelpers.PrepareConstrainedRegions();
                    try { }
                    finally
                    {
                        // Pin our string, disallowing the garbage collector from
                        // moving it around.
                        _GCH = GCHandle.Alloc(String, GCHandleType.Pinned);
                    }

                    IntPtr stringPtr = IntPtr.Zero;
                    RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(
                        delegate
                        {
                            // Create a CER (Contrained Execution Region)
                            RuntimeHelpers.PrepareConstrainedRegions();
                            try { }
                            finally
                            {
                                stringPtr = Marshal.SecureStringToBSTR(SecureString);
                            }

                            // Copy the SecureString content to our pinned string
                            char* pString = (char*)stringPtr;
                            char* pInsecureString = (char*)_GCH.AddrOfPinnedObject();
                            for (int index = 0; index < length; index++)
                            {
                                pInsecureString[index] = pString[index];
                            }
                        },
                        delegate
                        {
                            if (stringPtr != IntPtr.Zero)
                            {
                                // Free the SecureString BSTR that was generated
                                Marshal.ZeroFreeBSTR(stringPtr);
                            }
                        },
                        null);
                }
            }
        }

        void Deallocate()
        {            
            if (_GCH.IsAllocated)
            {
                unsafe
                {
                    // Determine the length of the string
                    int length = String.Length;

                    // Zero each character of the string.
                    char* pInsecureString = (char*)_GCH.AddrOfPinnedObject();
                    for (int index = 0; index < length; index++)
                    {
                        pInsecureString[index] = '\0';
                    }

                    // Free the handle so the garbage collector
                    // can dispose of it properly.
                    _GCH.Free();
                }
            }
        } 

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Deallocate();
        }

        #endregion
    }
}

此代码要求您可以编译unsafe代码,但它的工作原理就像一个魅力。

问候,

-道格

于 2010-08-25T15:24:28.483 回答
13

这应该对您有所帮助:将 SecureString 密码编组为字符串

从文章来看,关键点是:

  • 将字符串固定在内存中。
  • 使用托管指针来改变 System.String。
  • 使用 ExecuteCodeWithGuaranteedCleanup 方法的强保证。
于 2009-11-25T23:44:32.710 回答
9

SecureStrings 只有在您不使用它们时才是安全的。)-;

你不应该做的一件事是复制到一个字符串(不管方法如何)。该字符串是不可变的,并且可能会在内存中保留很长时间。

只要您采取预防措施尽快将该数组归零,将其复制到 char[] 会更安全一些。但是数组在内存中存在了一段时间,这是一个安全风险(违规)。

不幸的是,库中对 SecureStrings 的支持很少。最常见的处理方式是一次一个字符。

编辑:

char[]数组应该被固定,并且 Mark Byers 提供了一个链接,指向一篇使用固定字符串做同样事情的文章。这是一个选择问题,但字符串的风险在于它很容易被复制(将它传递给一些执行 a 的方法Trim()就足够了)。

于 2009-11-25T23:43:55.613 回答
1

Mark 提供的链接是您能做的最好的,也是我的团队为解决这个问题所采取的方法(尽管我们没有谈到使用 CER 的复杂性)。我有点怀疑使用 pinning 从本质上破坏 C# String 的不变性,但它确实有效。

于 2009-11-26T00:01:52.943 回答
0

使用Marshal.ZeroFreeBSTR

编辑:是的,创建一个新字符串将创建一个副本,因此您将失去对内容清理的控制。您可以通过在不安全的上下文中转换 IntPtr.ToPointer() 返回的指针来访问 char[]:

IntPtr ptr = Marshal.SecureStringToBSTR(str);
unsafe
{
    char *cp = (char*)ptr.ToPointer();
    //access char[] through cp
}

Marshal.ZeroFreeBSTR(ptr);
于 2009-11-25T23:45:19.693 回答
0

这是一个释放本机缓冲区的函数,因此内存中没有字符串。

    protected static string ConvertToUnsecureString(SecureString securePassword)
    {
        if (securePassword == null)
            throw new ArgumentNullException("securePassword");

        IntPtr unmanagedString = IntPtr.Zero;
        try
        {
            unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(securePassword);
            return Marshal.PtrToStringUni(unmanagedString);
        }
        finally
        {
            // Free the native buffer
            Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
        }
    }

来源

于 2019-12-11T19:51:34.660 回答