在 Winforms 应用程序中从 app.config 加载后如何在内存中保护连接字符串

How to secure a connection string in memory after loading it from app.config in a Winforms application

我有一个遗留应用程序使用 SQL 服务器身份验证连接字符串连接到本地或基于 Intranet 的 SQL 服务器实例。它目前使用 System.Configuration.ConfigurationManager 从 app.config 文件中获取连接字符串。但是,一旦从 app.config 文件中读取该连接字符串,它的值就会加载到内存中,并且可以使用 Process Hacker 等工具来查看应用程序内存。我目前有一个模块,其方法 returns 存储在 SecureString 对象中的连接字符串的值。连接字符串值在创建 ConnectionStringSection 对象时加载到内存中。 app.config 连接字符串 xml 通过 Microsoft 文档

中给出的说明进行加密

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/connection-strings-and-configuration-files

我了解使用集成安全性是最佳做法,在这种情况下我们的连接必须使用 SQL 服务器身份验证。有没有办法消除或最小化连接字符串在应用程序内存中的暴露?

Public Function GetConnectionString() As SecureString
    Dim fileMap As ExeConfigurationFileMap = New ExeConfigurationFileMap
    fileMap.ExeConfigFilename = Environment.CurrentDirectory + "\app.config"
    Dim config As Configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None)
    Dim section As ConnectionStringsSection = TryCast(config.GetSection("connectionStrings"), ConnectionStringsSection)

    Dim secureString As New SecureString

    For Each character As Char In section.ConnectionStrings("ConString").ConnectionString.ToCharArray
        secureString.AppendChar(character)
    Next

    Return secureString
End Function

memory dump after first call to get connection string

这是我用来将密码以纯文本形式保存在内存之外的工作流程。

1) 使用对称加密在连接字符串本身中加密密码,因此当它通过调用加载到内存中时

Dim section As ConnectionStringsSection = TryCast(config.GetSection("connectionStrings"), ConnectionStringsSection)

暴露的值是加密值而不是纯文本。

https://docs.microsoft.com/en-us/dotnet/standard/security/encrypting-data

2) 解密密码并将其一次一个字符附加到 SecureString 对象中进行存储。

https://docs.microsoft.com/en-us/dotnet/api/system.security.securestring?view=netframework-4.8

Public Shared Function DecryptString(ByVal srcString As String) As SecureString

        Dim p As Byte() = Convert.FromBase64String(srcString)
        Dim rv As RijndaelManaged = New RijndaelManaged
        Dim ms As MemoryStream = New MemoryStream(p)
        Dim cs As CryptoStream = New CryptoStream(ms, rv.CreateDecryptor(keyb, ivb), CryptoStreamMode.Read)
        Dim secureString As New SecureString
        Try
            Do
                Dim character As Integer = cs.ReadByte()
                If character = -1 Then
                    Exit Do
                End If
                secureString.AppendChar(Chr(character))
            Loop
        Finally
            ms.Close()
            ms.Dispose()
            cs.Close()
            cs.Dispose()
        End Try
        secureString.MakeReadOnly()
        Return secureString
    End Function

3) 使用 SecureString 对象和用户名构造一个 SqlCredential 对象,其中 "GetUserName()" 从连接字符串中获取用户名,"GetPassword()" 获取 SecureString 密码

https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcredential.-ctor?view=netframework-4.8

Dim SqlCredential = New SqlCredential(GetUserName(), GetPassword())

4) 从这里您可以使用仅包含连接字符串的 "Initial Catalog=;Data Source=;" 部分和 SQLCredential 对象的连接字符串构造 SQLConnection 对象。 "GetConnectionString()" 将 return 上述连接字符串。

https://docs.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlconnection.-ctor?view=netframework-4.8#System_Data_SqlClient_SqlConnection__ctor_System_String_System_Data_SqlClient_SqlCredential_

Dim connection = New SqlConnection(GetConnectionString(), SqlCredential)

5) 如果您正在使用 Entity Framework 并且需要将该连接传递给您的 DBContext 对象,您可以使用此构造函数。

https://docs.microsoft.com/en-us/ef/ef6/fundamentals/connection-management

Dim myDbContext = New DBContext(New SqlConnection(ConnectionString, SqlCredential))