P/Invoke CryptUnprotectData 中断 SqlConnection 构造函数

P/Invoke CryptUnprotectData breaks SqlConnection constructor

我正在尝试使用 CryptUnprotectData 将使用 CryptProtectData 保护的密码读入 SecureString 并使用它连接到数据库。我可以得到正确的密码,但之后尝试创建一个新的 SqlConnection 失败并显示以下内容:

System.TypeInitializationException was unhandled
  HResult=-2146233036
  Message=The type initializer for 'System.Data.SqlClient.SqlConnection' threw an exception.
  Source=System.Data
  TypeName=System.Data.SqlClient.SqlConnection
  StackTrace:
       at System.Data.SqlClient.SqlConnection..ctor()
       at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)
       at System.Data.SqlClient.SqlConnection..ctor(String connectionString)
       at ProtectedSqlTest.Program.Main() in C:\Git\ProtectedSqlTest\ProtectedSqlTest\Program.cs:line 16
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 
       HResult=-2146233036
       Message=The type initializer for 'System.Data.SqlClient.SqlConnectionFactory' threw an exception.
       Source=System.Data
       TypeName=System.Data.SqlClient.SqlConnectionFactory
       StackTrace:
            at System.Data.SqlClient.SqlConnection..cctor()
       InnerException: 
            HResult=-2146233036
            Message=The type initializer for 'System.Data.SqlClient.SqlPerformanceCounters' threw an exception.
            Source=System.Data
            TypeName=System.Data.SqlClient.SqlPerformanceCounters
            StackTrace:
                 at System.Data.SqlClient.SqlConnectionFactory..cctor()
            InnerException: 
                 HResult=-2147024809
                 Message=The parameter is incorrect. (Exception from HRESULT: 0x80070057 (E_INVALIDARG))
                 Source=mscorlib
                 StackTrace:
                      at System.Globalization.TextInfo.InternalChangeCaseString(IntPtr handle, IntPtr handleOrigin, String localeName, String str, Boolean isToUpper)
                      at System.Globalization.TextInfo.ToLower(String str)
                      at System.String.ToLower(CultureInfo culture)
                      at System.Diagnostics.PerformanceCounterLib.GetPerformanceCounterLib(String machineName, CultureInfo culture)
                      at System.Diagnostics.PerformanceCounterLib.IsCustomCategory(String machine, String category)
                      at System.Diagnostics.PerformanceCounter.InitializeImpl()
                      at System.Diagnostics.PerformanceCounter.set_RawValue(Int64 value)
                      at System.Data.ProviderBase.DbConnectionPoolCounters.Counter..ctor(String categoryName, String instanceName, String counterName, PerformanceCounterType counterType)
                      at System.Data.ProviderBase.DbConnectionPoolCounters..ctor(String categoryName, String categoryHelp)
                      at System.Data.SqlClient.SqlPerformanceCounters..ctor()
                      at System.Data.SqlClient.SqlPerformanceCounters..cctor()
                 InnerException: 

只需调用 CryptUnprotectData 即可使 SqlConnection 失败,连接本身不需要使用返回的 SecureString

我正在使用扩展方法 from here as described in this post 进行我的最小复制:

class Program
{
    const string ProtectedSecret = /* SNIP - base 64 encoded protected data here */;
    static void Main()
    {
        // calling AppendProtectedData breaks the following SqlConnection
        // without the following line the application works fine
        new SecureString().AppendProtectedData(Convert.FromBase64String(ProtectedSecret));

        using (var conn = new SqlConnection("Server=(localdb)\MSSqlLocalDb;Trusted_Connection=true"))
        using (var cmd = new SqlCommand("select 1", conn))
        {
            conn.Open();
            cmd.ExecuteNonQuery();
        }
    }
}

如果我在 SqlConnection 之前创建一个新的 我加载密码,我可以在应用程序期间创建新的 SqlConnections,因为它似乎使用相同的 SqlConnectionFactory,但这意味着作为一种解决方法,我必须在应用程序开始时执行类似的操作:

new SqlConnection().Dispose();

...我想避免。

以下没有帮助:

Windows10、VS Enterprise 2015,控制台应用程序(.NET 4.6.1)

更新: 运行 另一个线程中的数据保护代码给出了具有不同根本原因的类似异常:

System.TypeInitializationException was unhandled
  HResult=-2146233036
  Message=The type initializer for 'System.Data.SqlClient.SqlConnection' threw an exception.
  Source=System.Data
  TypeName=System.Data.SqlClient.SqlConnection
  StackTrace:
       at System.Data.SqlClient.SqlConnection..ctor()
       at System.Data.SqlClient.SqlConnection..ctor(String connectionString, SqlCredential credential)
       at System.Data.SqlClient.SqlConnection..ctor(String connectionString)
       at ProtectedSqlTest.Program.Main() in C:\Git\ProtectedSqlTest\ProtectedSqlTest\Program.cs:line 17
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 
       HResult=-2146233036
       Message=The type initializer for 'System.Data.SqlClient.SqlConnectionFactory' threw an exception.
       Source=System.Data
       TypeName=System.Data.SqlClient.SqlConnectionFactory
       StackTrace:
            at System.Data.SqlClient.SqlConnection..cctor()
       InnerException: 
            HResult=-2146233036
            Message=The type initializer for 'System.Data.SqlClient.SqlPerformanceCounters' threw an exception.
            Source=System.Data
            TypeName=System.Data.SqlClient.SqlPerformanceCounters
            StackTrace:
                 at System.Data.SqlClient.SqlConnectionFactory..cctor()
            InnerException: 
                 BareMessage=Configuration system failed to initialize
                 HResult=-2146232062
                 Line=0
                 Message=Configuration system failed to initialize
                 Source=System.Configuration
                 StackTrace:
                      at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
                      at System.Configuration.ClientConfigurationSystem.PrepareClientConfigSystem(String sectionName)
                      at System.Configuration.ClientConfigurationSystem.System.Configuration.Internal.IInternalConfigSystem.GetSection(String sectionName)
                      at System.Configuration.ConfigurationManager.GetSection(String sectionName)
                      at System.Configuration.PrivilegedConfigurationManager.GetSection(String sectionName)
                      at System.Diagnostics.DiagnosticsConfiguration.Initialize()
                      at System.Diagnostics.DiagnosticsConfiguration.get_SwitchSettings()
                      at System.Diagnostics.Switch.InitializeConfigSettings()
                      at System.Diagnostics.Switch.InitializeWithStatus()
                      at System.Diagnostics.Switch.get_SwitchSetting()
                      at System.Data.ProviderBase.DbConnectionPoolCounters..ctor(String categoryName, String categoryHelp)
                      at System.Data.SqlClient.SqlPerformanceCounters..ctor()
                      at System.Data.SqlClient.SqlPerformanceCounters..cctor()
                 InnerException: 
                      HResult=-2147024809
                      Message=Item has already been added. Key in dictionary: 'MACHINE'  Key being added: 'MACHINE'
                      Source=mscorlib
                      StackTrace:
                           at System.Collections.Hashtable.Insert(Object key, Object nvalue, Boolean add)
                           at System.Collections.Hashtable.Add(Object key, Object value)
                           at System.Configuration.Internal.InternalConfigRoot.GetConfigRecord(String configPath)
                           at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
                      InnerException:

有趣的是错误代码是:

internal static PerformanceCounterLib GetPerformanceCounterLib(string machineName, CultureInfo culture) {
    SharedUtils.CheckEnvironment();

    string lcidString = culture.LCID.ToString("X3", CultureInfo.InvariantCulture);
    if (machineName.CompareTo(".") == 0)
            machineName = ComputerName.ToLower(CultureInfo.InvariantCulture);
    else
        machineName = machineName.ToLower(CultureInfo.InvariantCulture);
    ...

调用ComputerName.ToLower(CultureInfo.InvariantCulture)的行导致异常。

您只需调用代码即可重现相同的行为

new SecureString().AppendProtectedData(Convert.FromBase64String(ProtectedSecret));
string lower = "Something".ToLower(CultureInfo.InvariantCulture);

不知何故在TextInfoclass

的构造函数中
this.m_dataHandle = CompareInfo.InternalInitSortHandle(m_textInfoName, out handleOrigin);

returns 如果未在 CryptUnprotectData 函数之前调用,则数据无效。

这似乎是框架中的一个错误。你可以提交给微软。同时你可以提前调用这一行来防止错误。

"".ToLower(CultureInfo.InvariantCulture); 

我最近遇到了类似的症状,使用来自 http://www.griffinscs.com/?p=12 的相同代码:任何对 CryptUnprotectData 的调用都会导致一些不相关的代码出现异常。有趣的是,故障只发生在 Windows 10 台机器上;相同的代码在 Windows 7 机器上运行良好。

我通过将 CryptProtectDataCryptUnprotectDataszDataDescr 参数的声明从 string 更改为 IntPtr 并传递 IntPtr.Zero 而不是两次调用中的 string.Empty