Como usar o Always Encrypted com o Provedor de Dados do Microsoft .NET para SQL Server

Aplicável a: .NET Framework .NET .NET Standard

Este artigo fornece informações sobre como desenvolver aplicativos .NET usando o Always Encrypted ou o Always Encrypted com enclaves seguros e o Provedor de Dados do Microsoft .NET para SQL Server.

O Always Encrypted permite que os aplicativos cliente criptografem dados confidenciais e nunca revelem os dados nem as chaves de criptografia para o SQL Server ou o Banco de Dados SQL do Azure. Um driver habilitado para Always Encrypted, como o Provedor de Dados do Microsoft .NET para SQL Server, atinge essa segurança ao criptografar e descriptografar dados confidenciais de maneira transparente no aplicativo cliente. O driver determina automaticamente quais parâmetros de consulta correspondem às colunas de banco de dados confidenciais (protegidas com o Always Encrypted) e criptografa os valores desses parâmetros antes de passar os dados para o servidor. Da mesma forma, o driver descriptografa de modo transparente os dados recuperados das colunas de banco de dados criptografadas nos resultados da consulta. Para obter mais informações, confira Desenvolver aplicativos usando o Always Encrypted e Desenvolver aplicativos usando o Always Encrypted com enclaves seguros.

Pré-requisitos

  • Configure o Sempre Criptografado em seu banco de dados. Esse processo envolve o provisionamento de chaves do Always Encrypted e a configuração de criptografia de colunas de banco de dados selecionadas. Se você ainda não tem um banco de dados com Always Encrypted configurado, siga as instruções em Tutorial: introdução ao Always Encrypted.
  • Se você estiver usando o Always Encrypted com enclaves seguros, confira Desenvolver aplicativos usando o Always Encrypted com enclaves seguros para obter mais pré-requisitos.
  • Verifique se a plataforma .NET necessária está instalada em seu computador de desenvolvimento. Com o Microsoft.Data.SqlClient, o recurso do Always Encrypted será compatível com o .NET Framework e o .NET Core. Verifique se o .NET Framework 4.6 (ou superior) ou o .NET Core 2.1 (ou superior) está configurado como a versão de destino da plataforma .NET em seu ambiente de desenvolvimento. Com a versão 2.1.0 do Microsoft.Data.SqlClient e superior, o recurso Always Encrypted também tem suporte no .NET Standard 2.0. Para usar o Always Encrypted com enclaves seguros, o .NET Standard 2.1 é necessário. Se você quiser usar enclaves VBS sem atestado, será necessário o Microsoft.Data.SqlClient versão 4.1 ou superior. Caso esteja usando o Visual Studio, confira a Visão geral de direcionamento do Framework.

A tabela a seguir resume as plataformas .NET necessárias para usar o Always Encrypted com Microsoft.Data.SqlClient.

Suporte ao Always Encrypted Suporte ao Always Encrypted com enclaves seguros Estrutura de Destino Versão do Microsoft.Data.SqlClient Sistema operacional
Sim Sim .NET Framework 4.6 ou versão posterior 1.1.0+ Windows
Sim Sim .NET Core 2.1+ 2.1.0+1 Windows, Linux, macOS
Sim Não .NET Standard 2.0 2.1.0+ Windows, Linux, macOS
Sim Sim .NET Standard 2.1+ 2.1.0+ Windows, Linux, macOS

Observação

1 Antes da versão 2.1.0 do Microsoft.Data.SqlClient, só há suporte para o Always Encrypted no Windows.

Habilitando o Always Encrypted para consultas de aplicativo

A maneira mais fácil de habilitar a criptografia de parâmetros e a descriptografia dos resultados da consulta direcionados às colunas criptografadas é configurando o valor da palavra-chave da cadeia de conexão Column Encryption Setting como habilitado.

O seguinte exemplo usa uma cadeia de conexão que habilita o Always Encrypted:

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";
SqlConnection connection = new SqlConnection(connectionString);

O snippet de código a seguir é um exemplo equivalente que usa a propriedade SqlConnectionStringBuilder.ColumnEncryptionSetting.

SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
builder.DataSource = "server63";
builder.InitialCatalog = "Clinic";
builder.IntegratedSecurity = true;
builder.ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Enabled;
SqlConnection connection = new SqlConnection(builder.ConnectionString);
connection.Open();

O Sempre Criptografado também pode ser habilitado para consultas individuais. Veja abaixo a seção Como controlar o impacto no desempenho do Always Encrypted. Habilitar Always Encrypted não é suficiente para o êxito da criptografia ou descriptografia. Você também precisa garantir que:

  • O aplicativo tem as permissões de banco de dados VIEW ANY COLUMN MASTER KEY DEFINITION e VIEW ANY COLUMN ENCRYPTION KEY DEFINITION , necessárias para acessar os metadados sobre as chaves do Always Encrypted no banco de dados. Para obter detalhes, veja a seção Permissões do Banco de dados no Always Encrypted (Mecanismo de Banco de Dados).
  • O aplicativo pode acessar a chave mestra de coluna que protege as chaves de criptografia de coluna, o que criptografa as colunas de banco de dados consultadas.

Como habilitar o Always Encrypted com enclaves seguros

Desde a versão 1.1.0 do Microsoft.Data.SqlClient, o driver é compatível com o Always Encrypted com enclaves seguros.

Para informações gerais sobre o desenvolvimento de aplicativos usando enclaves, confira Desenvolver aplicativos usando o Always Encrypted com enclaves seguros.

Para habilitar computações de enclave para uma conexão de banco de dados, você precisará definir as seguintes palavras-chave da cadeia de conexão, além de habilitar o Always Encrypted (conforme explicado na seção anterior):

  • Attestation Protocol – especifica um protocolo de atestado.

    • Se essa palavra-chave não for especificada, as enclaves seguras serão desabilitadas na conexão.
    • Se você estiver usando o SQL Server com enclaves VBS (segurança baseada em virtualização) e o HGS (Serviço Guardião de Host), o valor dessa palavra-chave deverá ser HGS.
    • Se você estiver usando o Banco de Dados SQL do Azure com enclaves Intel SGX e o Atestado do Microsoft Azure, o valor dessa palavra-chave deverá ser AAS.
    • Se você estiver usando o Banco de Dados SQL do Azure ou SQL Server com enclaves VBS e quiser renunciar ao atestado, o valor dessa palavra-chave deverá ser None. Requer a versão 4.1 ou superior.

    Observação

    'None' (sem atestado) é a única opção atualmente com suporte para enclaves VBS no Banco de Dados SQL do Azure.

  • Enclave Attestation URL – especifica uma URL de atestado (um ponto de extremidade de serviço de atestado). Você precisará obter uma URL de atestado para seu ambiente do administrador de serviços de atestado.

Para obter um tutorial passo a passo, confira Tutorial: como desenvolver um aplicativo .NET usando o Always Encrypted com enclaves seguros.

Recuperação e modificação de dados em colunas criptografadas

Depois de habilitar o Always Encrypted para consultas de aplicativo, será possível usar as APIs padrão do SqlClient (veja Como recuperar e modificar dados no ADO.NET) ou as APIs do Provedor de Dados do Microsoft .NET para SQL Server, definidas no Microsoft.Data.SqlClient Namespace para recuperar ou modificar dados em colunas de banco de dados criptografadas. Se o seu aplicativo tiver as permissões de banco de dados necessárias e puder acessar a chave mestra da coluna, o Provedor de Dados do Microsoft .NET para SQL Server criptografará todos os parâmetros de consulta direcionados às colunas criptografadas. Além disso, ele descriptografará os dados recuperados de colunas criptografadas, retornando valores de texto não criptografados dos tipos .NET, correspondentes aos tipos de dados do SQL Server definidos para as colunas no esquema de banco de dados. Se o Sempre Criptografado não estiver habilitado, as consultas com parâmetros que se destinam a colunas criptografadas falharão. As consultas ainda podem recuperar dados de colunas criptografadas, desde que a consulta não tenha parâmetros que se destinem a colunas criptografadas. No entanto, o Provedor de Dados do Microsoft .NET para SQL Server não tentará descriptografar nenhum valor recuperado de colunas criptografadas e o aplicativo receberá os dados binários criptografados (como matrizes de bytes).

A tabela abaixo resume o comportamento das consultas dependendo se Always Encrypted está habilitado ou não:

Característica da consulta O Always Encrypted está habilitado e o aplicativo poderá acessar as chaves e os metadados de chave O Always Encrypted está habilitado e o aplicativo não poderá acessar as chaves nem os metadados de chave O Sempre Criptografado está desabilitado
Consultas com parâmetros que se destinam a colunas criptografadas. Os valores de parâmetro são criptografados de modo transparente. Erro Erro
Consultas que recuperam dados de colunas criptografadas sem parâmetros que se destinam a colunas criptografadas. Os resultados das colunas criptografadas são descriptografados de modo transparente. O aplicativo recebe valores de texto não criptografados dos tipos de dados do .NET correspondentes aos tipos do SQL Server configurados para as colunas criptografadas. Erro Os resultados das colunas criptografadas não são descriptografados. O aplicativo recebe valores criptografados como matrizes de bytes (byte[]).

Os exemplos a seguir ilustram como recuperar e modificar dados em colunas criptografadas. Os exemplos pressupõem a tabela de destino com o esquema abaixo. As colunas SSN e BirthDate são criptografadas.

CREATE TABLE [dbo].[Patients]([PatientId] [int] IDENTITY(1,1),
 [SSN] [char](11) COLLATE Latin1_General_BIN2
 ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC,
 ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
 COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL,
 [FirstName] [nvarchar](50) NULL,
 [LastName] [nvarchar](50) NULL,
 [BirthDate] [date]
 ENCRYPTED WITH (ENCRYPTION_TYPE = RANDOMIZED,
 ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
 COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL
 PRIMARY KEY CLUSTERED ([PatientId] ASC) ON [PRIMARY])
 GO

Inserindo exemplo de dados

Este exemplo insere uma linha na tabela Pacientes. Observe o seguintes detalhes:

  • Não há nada específico de criptografia no código de exemplo. O Provedor de Dados do Microsoft .NET para SQL Server detecta e criptografa automaticamente os parâmetros paramSSN e paramBirthdate direcionados às colunas criptografadas. Esse comportamento torna a criptografia transparente para o aplicativo.
  • Os valores inseridos nas colunas de banco de dados, incluindo as colunas criptografadas, são passados como objetos SqlParameter . Embora o uso de SqlParameter seja opcional ao enviar valores para colunas não criptografadas (mesmo que seja altamente recomendável, pois ajuda a prevenir a injeção de SQL), ele é necessário para valores que se destinam a colunas criptografadas. Caso os valores inseridos nas colunas SSN ou BirthDate sejam passados como literais e inseridos na instrução da consulta, ela falhará, pois o Provedor de Dados do Microsoft .NET para SQL Server não conseguiria determinar os valores nas colunas criptografadas de destino e, portanto, não os criptografaria. Como resultado, o servidor os rejeitaria como incompatíveis com as colunas criptografadas.
  • O tipo de dados do parâmetro que se destina à coluna SSN é definido como uma cadeia de caracteres ANSI (não Unicode), que é mapeada para o tipo de dados char/varchar do SQL Server. Se o tipo do parâmetro fosse definido como uma cadeia de caracteres Unicode (String), que é mapeada para nchar/nvarchar, a consulta falharia, já que o Always Encrypted não dá suporte a conversões de valores nchar/nvarchar criptografados em valores char/varchar criptografados. Veja Mapeamentos de tipos de dados do SQL Server para obter informações sobre os mapeamentos de tipos de dados.
  • O tipo de dados do parâmetro inserido na coluna BirthDate é definido explicitamente para o tipo de dados do SQL Server de destino usando a propriedade SqlParameter.SqlDbType, em vez de depender do mapeamento implícito de tipos .NET para tipos de dados do SQL Server aplicados ao usar a propriedade SqlParameter.DbType. Por padrão, a Estrutura DateTime é mapeada para o tipo de dados datetime do SQL Server. Como o tipo de dados da coluna BirthDate é data e o Always Encrypted não dá suporte a uma conversão de valores de datetime criptografados em valores de data criptografados, o uso do mapeamento padrão resultará em um erro.
string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";

using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"INSERT INTO [dbo].[Patients] ([SSN], [FirstName], [LastName], [BirthDate]) VALUES (@SSN, @FirstName, @LastName, @BirthDate);";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = "795-73-9838";
    paramSSN.Size = 11;
    cmd.Parameters.Add(paramSSN);

    SqlParameter paramFirstName = cmd.CreateParameter();
    paramFirstName.ParameterName = @"@FirstName";
    paramFirstName.DbType = DbType.String;
    paramFirstName.Direction = ParameterDirection.Input;
    paramFirstName.Value = "Catherine";
    paramFirstName.Size = 50;
    cmd.Parameters.Add(paramFirstName);

    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);

    SqlParameter paramBirthdate = cmd.CreateParameter();
    paramBirthdate.ParameterName = @"@BirthDate";
    paramBirthdate.SqlDbType = SqlDbType.Date;
    paramBirthdate.Direction = ParameterDirection.Input;
    paramBirthdate.Value = new DateTime(1996, 09, 10);
    cmd.Parameters.Add(paramBirthdate);

    cmd.ExecuteNonQuery();
}

Exemplo de recuperação de dados de texto não criptografado

O exemplo a seguir demonstra a filtragem de dados com base em valores criptografados e a recuperação de dados de texto não criptografado de colunas criptografadas.

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true; Column Encryption Setting=enabled";
using (SqlConnection connection = new SqlConnection(builder.ConnectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN=@SSN";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = "795-73-9838";
    paramSSN.Size = 11;
    cmd.Parameters.Add(paramSSN);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", reader[0], reader[1], reader[2], ((DateTime)reader[3]).ToShortDateString());
            }
        }
    }
}

Observação

  • O valor usado na cláusula WHERE a ser filtrado na coluna SSN precisa ser passado com o SqlParameter para que o Provedor de Dados do Microsoft .NET para SQL Server possa criptografá-lo de maneira transparente antes de enviá-lo ao banco de dados.

  • Todos os valores impressos pelo programa estarão no texto não criptografado, já que o Provedor de Dados do Microsoft .NET para SQL Server descriptografará de maneira transparente os dados recuperados das colunas SSN e BirthDate.

  • Consultas poderão executar comparações de igualdade em colunas se forem criptografados usando a criptografia determinística.

Exemplo de recuperação de dados criptografados

Se o Always Encrypted não estiver habilitado, uma consulta ainda poderá recuperar dados de colunas criptografadas, desde que a consulta não tenha parâmetros que se destinam a colunas criptografadas.

O exemplo a seguir demonstra como recuperar dados criptografados binários de colunas criptografadas.

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand cmd = connection.CreateCommand())
{
    connection.Open();
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [LastName]=@LastName";

    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", BitConverter.ToString((byte[])reader[0]), reader[1], reader[2], BitConverter.ToString((byte[])reader[3]));
            }
        }
    }
}

Observação

  • Como o Always Encrypted não está habilitado na cadeia de conexão, a consulta retornará valores criptografados de SSN e BirthDate como matrizes de bytes (o programa converte os valores em cadeias de caracteres).

  • Uma consulta que recupera dados de colunas criptografadas com o Sempre Criptografado desabilitado pode ter parâmetros, desde que nenhum dos parâmetros se destinem a uma coluna criptografada. A consulta acima filtra por LastName, que não é criptografado no banco de dados. Se a consulta filtrar por SSN ou BirthDate, ela falhará.

Evitando problemas comuns ao consultar colunas criptografadas

Esta seção descreve as categorias comuns de erros ao consultar colunas criptografadas de aplicativos .NET e algumas diretrizes sobre como evitá-los.

Erros de conversão de tipo de dados sem suporte

O Always Encrypted dá suporte a algumas conversões de tipos de dados criptografados. Confira Always Encrypted para obter uma lista detalhada de conversões de tipo com suporte. Faça o seguinte para evitar erros de conversão de tipo de dados:

  • Defina os tipos de parâmetros direcionados às colunas criptografadas para que o tipo de dados do SQL Server do parâmetro seja exatamente o mesmo que o tipo da coluna de destino. Ou para que uma conversão do tipo de dados do SQL Server do parâmetro seja compatível com o tipo de destino da coluna. É possível impor o mapeamento desejado de tipos de dados .NET em tipos de dados específicos do SQL Server usando a Propriedade SqlParameter.SqlDbType.
  • Verifique se a precisão e escala dos parâmetros que se destinam a colunas dos tipos de dados decimais e numéricos do SQL Server são iguais à precisão e escala configuradas para a coluna de destino.
  • Verifique se a precisão dos parâmetros que são direcionados a colunas datetime2, datetimeoffset ou a tipos de dados temporais do SQL Server não é maior do que a precisão da coluna de destino (em consultas que modificam os valores na coluna de destino).

Erros devido à passagem de texto sem formatação em vez de valores criptografados

Qualquer valor que se destina a uma coluna criptografada precisa ser criptografado no aplicativo. Uma tentativa de inserir/modificar ou de filtrar por um valor de texto não criptografado em uma coluna criptografada resultará em um erro semelhante a este:

Microsoft.Data.SqlClient.SqlException (0x80131904): Operand type clash: varchar is incompatible with varchar(8000) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'Clinic') collation_name = 'SQL_Latin1_General_CP1_CI_AS'

Para evitar esses tipos de erros, garanta que:

  • O Always Encrypted é habilitado para as consultas de aplicativo que se destinam a colunas criptografadas (na cadeia de conexão ou no objeto SqlCommand de uma consulta específica).
  • SqlParameter é usado para enviar dados que se destinam a colunas criptografadas. O exemplo a seguir mostra uma consulta filtrada incorretamente por um literal ou uma constante em uma coluna criptografada (SSN) em vez de passar o literal em um objeto SqlParameter.
using (SqlCommand cmd = connection.CreateCommand())
{
    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE SSN = '795-73-9838'";
    cmd.ExecuteNonQuery();
}

Como trabalhar com repositórios de chaves mestras de coluna

Para criptografar um valor de parâmetro ou descriptografar dados nos resultados da consulta, o Provedor de Dados do Microsoft .NET para SQL Server deverá obter uma chave de criptografia da coluna configurada para a coluna de destino. As chaves de criptografia de coluna são armazenadas em formato criptografado nos metadados do banco de dados. Cada chave de criptografia de coluna tem uma chave mestra de coluna correspondente que foi usada para criptografar a chave de criptografia de coluna. Os metadados do banco de dados não armazenam as chaves mestras de coluna, eles contêm apenas as informações sobre um repositório de chaves que contém uma chave mestra de coluna específica e a localização da chave no repositório de chaves.

Para obter um valor de texto não criptografado de uma chave de criptografia de coluna, o Provedor de Dados do Microsoft .NET para SQL Server primeiro obtém os metadados sobre a chave de criptografia de coluna e sua chave mestra de coluna correspondente. Em seguida, ele usa as informações nos metadados para entrar em contato com o repositório de chaves que contém a chave mestra de coluna e para descriptografar a chave de criptografia da coluna criptografada. O Provedor de Dados do Microsoft .NET para SQL Server se comunica com um repositório de chaves usando um provedor de repositório de chaves mestras de coluna, uma instância de uma classe derivada da classe SqlColumnEncryptionKeyStoreProvider.

O processo para obter uma chave de criptografia de coluna:

  1. Caso o Always Encrypted esteja habilitado para uma consulta, o Provedor de Dados do Microsoft .NET para SQL Server chamará sys.sp_describe_parameter_encryption de maneira transparente para recuperar os metadados de criptografia para parâmetros direcionados às colunas criptografadas, caso a consulta tenha parâmetros. Para dados criptografados contidos nos resultados de uma consulta, o SQL Server anexa metadados de criptografia automaticamente. As informações sobre a chave mestra de coluna incluem:

    • O nome de um provedor de repositório de chaves que encapsula um repositório de chaves que contém a chave mestra de coluna.
    • O caminho de chave que especifica o local da chave mestra da coluna no repositório de chaves.

    As informações sobre a chave de criptografia de coluna incluem:

    • O valor criptografado de uma chave de criptografia de coluna.
    • O nome do algoritmo que foi usado para criptografar a chave de criptografia de coluna.
  2. O Provedor de Dados do Microsoft .NET para SQL Server usa o nome do provedor de repositório de chaves mestras de coluna para pesquisar o objeto do provedor, que é uma instância de uma classe derivada da classe SqlColumnEncryptionKeyStoreProvider em uma estrutura de dados interna.

  3. Para descriptografar a chave de criptografia da coluna, o Provedor de Dados do Microsoft .NET para SQL Server chama o método SqlColumnEncryptionKeyStoreProvider.DecryptColumnEncryptionKey(), passando o caminho da chave mestra da coluna, o valor criptografado da chave de criptografia da coluna e o nome do algoritmo de criptografia, usado para gerar a chave de criptografia da coluna criptografada.

Usando provedores internos de repositórios de chaves mestras de coluna

O Provedor de Dados do Microsoft .NET para SQL Server é fornecido com os provedores internos de repositório de chaves mestras de coluna a seguir, que são pré-registrados com nomes do provedor específico (usados para pesquisar o provedor). Esses provedores de repositórios de chaves internos têm suporte apenas no Windows.

Classe Descrição Nome (de pesquisa) do provedor Plataforma
Classe SqlColumnEncryptionCertificateStoreProvider Um provedor para o Repositório de Certificados do Windows. MSSQL_CERTIFICATE_STORE Windows
Classe SqlColumnEncryptionCngProvider Um provedor de um repositório de chaves que dá suporte à API de Criptografia da Microsoft: API Next Generation (CNG). Normalmente, um repositório desse tipo é um módulo de segurança de hardware – um dispositivo físico que protege e gerencia chaves digitais e fornece processamento de criptografia. MSSQL_CNG_STORE Windows
Classe SqlColumnEncryptionCspProvider Um provedor de um repositório de chaves que dá suporte à CAPI (Cryptography API) da Microsoft. Normalmente, um repositório desse tipo é um módulo de segurança de hardware – um dispositivo físico que protege e gerencia chaves digitais e fornece processamento de criptografia. MSSQL_CSP_PROVIDER Windows

Não é necessário fazer alterações de código no aplicativo para usar esses provedores, mas observe os seguintes detalhes:

  • Você (ou seu DBA) precisa verificar se o nome do provedor, configurado nos metadados da chave mestra de coluna, está correto e se o caminho da chave mestra de coluna está em conformidade com o formato do caminho da chave válido para determinado provedor. É recomendável configurar as chaves usando ferramentas como o SQL Server Management Studio, que gera automaticamente os nomes de provedor válidos e os caminhos de chaves ao emitir a instrução CREATE COLUMN MASTER KEY (Transact-SQL). Para obter mais informações, consulte Configurando o Always Encrypted usando o SQL Server Management Studio e Configurar o Always Encrypted usando o PowerShell.
  • Verifique se seu aplicativo pode acessar a chave no repositório de chaves. Esse processo pode envolver a concessão de acesso para o aplicativo à chave e/ou ao repositório de chaves, dependendo do repositório de chaves, ou a execução de outras etapas de configuração específicas do repositório de chaves. Por exemplo, para acessar um repositório de chaves que implementa o CNG ou a CAPI (como um módulo de segurança de hardware), você precisa verificar se uma biblioteca que implementa o CNG ou a CAPI de seu repositório está instalada no computador do aplicativo. Para obter detalhes, confira Criar e armazenar chaves mestras de coluna para Always Encrypted.

Como usar o provedor do Azure Key Vault

O Cofre de Chaves do Azure é uma opção conveniente para armazenar e gerenciar chaves mestras de coluna do Always Encrypted (especialmente se seus aplicativos estiverem hospedados no Azure). O Provedor de Dados do Microsoft .NET para SQL Server não inclui um provedor interno de repositório de chaves mestras de coluna para o Azure Key Vault, mas está disponível como um pacote NuGet (Microsoft.Data.SqLClient.AlwaysEncrypted.AzureKeyVaultProvider) que você pode integrar ao seu aplicativo com facilidade. Para obter detalhes, veja Always Encrypted – Proteger dados confidenciais no Banco de Dados SQL com a criptografia de dados e armazenar as chaves de criptografia no Cofre de Chaves do Azure.

Classe Descrição Nome (de pesquisa) do provedor Plataforma
Classe SqlColumnEncryptionAzureKeyVaultProvider Provedor do Azure Key Vault. AZURE_KEY_VAULT Windows, Linux, macOS

Capacidade de suporte do .NET

Versão Versão do Microsoft.Data.SqlClient Plataformas do .NET
3.0.0 3.0.0+ .NET Framework 4.6.1+, .NET Core 2.1+, .NET Standard 2.0+
2.0.0 1.1.3+
2.1.0+
.NET Framework 4.6.1+, .NET Core 2.1+
.NET Standard 2.0+
1.2.0 1.0.19269.1+
2.1.0+
.NET Framework 4.6+, .NET Core 2.1+
.NET Standard 2.0+
1.1.0 1.0.19269.1+ .NET Framework 4.6+, .NET Core 2.1+
1.0.0 1.0.19269.1+ .NET Framework 4.6+, .NET Core 2.1+

Na v3.0.0 e posteriores, o Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider dá suporte aos recursos de cache de chave de criptografia de coluna ao registrar o provedor usando as APIs SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection ou SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand.

Começando com a v2.0.0, o Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider dá suporte às novas APIs Azure.Core e Azure.Identity para realizar a autenticação com o Azure Key Vault. Uma instância da implementação TokenCredential agora pode ser passada para construtores SqlColumnEncryptionAzureKeyVaultProvider para inicializar o objeto do provedor do Azure Key Vault.

Observação

O Microsoft.Data.SqLClient.AlwaysEncrypted.AzureKeyVaultProviderdá suporte aos Cofres e aos HSMs Gerenciados no Azure Key Vault.

Para obter exemplos que demonstram a criptografia/descriptografia com o Azure Key Vault, confira o Azure Key Vault trabalhando com o Always Encrypted e o Azure Key Vault trabalhando com o Always Encrypted com o enclaves seguros.

Implementando um provedor personalizado de repositórios de chaves mestras de coluna

Caso queira armazenar as chaves mestras da coluna em um repositório de chaves não compatível com um provedor existente, você pode implementar um provedor personalizado ao estender a classe SqlColumnEncryptionKeyStoreProvider e registrar o provedor usando um dos seguintes métodos:

public class MyCustomKeyStoreProvider : SqlColumnEncryptionKeyStoreProvider
{
    public const string ProviderName = "MY_CUSTOM_STORE";

    public override byte[] EncryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] columnEncryptionKey)
    {
        // Logic for encrypting a column encrypted key.
    }
    public override byte[] DecryptColumnEncryptionKey(string masterKeyPath, string encryptionAlgorithm, byte[] EncryptedColumnEncryptionKey)
    {
        // Logic for decrypting a column encrypted key.
    }
}  
class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> providers =
            new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        providers.Add(MyCustomKeyStoreProvider.ProviderName, new MyCustomKeyStoreProvider());
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(providers);
        // ...
    }
}

Precedência de cache de chave de criptografia de coluna

Esta seção se aplica à versão 3.0 e posterior do Provedor de Dados Microsoft .NET para SQL Server.

As CEKs (chaves de criptografia de coluna) descriptografadas por provedores de repositório de chaves personalizados registrados em uma conexão ou instância de comando não serão armazenadas em cache pelo Provedor de Dados do Microsoft .NET para SQL Server. Provedores de repositório de chaves personalizados devem implementar um mecanismo próprio de cache de CEK.

Na v3.0.0 e posteriores do Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider, cada instância do Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider tem uma implementação própria de cache de CEK. Quando registradas em uma conexão ou instância de comando, as CEKs descriptografadas por uma instância do Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider serão limpas quando essa instância sair do escopo:

class Program
{
    static void Main()
    {
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            using (SqlCommand command = connection.CreateCommand())
            {
                Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
                SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider();
                customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
                command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customKeyStoreProviders);
                // Perform database operation using Azure Key Vault Provider
                // Any decrypted column encryption keys will be cached
            } // Column encryption key cache of "azureKeyVaultProvider" is cleared when "azureKeyVaultProvider" goes out of scope
        }
    }
}

Observação

O cache de CEK implementado por provedores de repositório de chaves personalizados será desabilitado pelo driver se a instância do provedor de repositório de chaves for registrada globalmente no driver usando o método SqlConnection.RegisterColumnEncryptionKeyStoreProviders. Qualquer implementação de cache CEK deverá referenciar o valor de SqlColumnEncryptionKeyStoreProvider.ColumnEncryptionKeyCacheTtl antes de armazenar uma CEK em cache e não a armazenar em cache se o valor for zero. Isso evitará o armazenamento em cache duplicado e a possível confusão do usuário quando ele estiver tentando configurar o armazenamento de chaves em cache.

Como registrar um provedor de repositório de chave mestra de coluna personalizado

Esta seção se aplica à versão 3.0 e posteriores do provedor.

Provedores de repositórios de chaves mestras personalizadas podem ser registrados com o driver em três camadas diferentes. A precedência dos três registros é a seguinte:

  • O registro por comando será verificado se não estiver vazio.
  • Se o registro por comando estiver vazio, o registro por conexão será verificado se não estiver vazio.
  • Se o registro por conexão estiver vazio, o registro global será verificado.

Uma vez que um provedor de repositórios de chaves for encontrado em um nível de registro, o driver NÃO fará fallback para os outros registros para pesquisar um provedor. Se os provedores forem registrados, mas o provedor adequado não for encontrado em um nível, uma exceção será lançada contendo apenas os provedores registrados no registro que tiver sido verificado.

Os provedores internos de repositórios de chaves mestras de coluna que estão disponíveis para o Repositório de Certificados do Windows, a CNG Store e a CSP estão pré-registrados.

Os três níveis de registro dão suporte a diferentes cenários ao consultar dados criptografados. O método apropriado poderá ser usado para garantir que usuários de um aplicativo possam acessar os dados de texto sem formatação se puderem fornecer a chave mestra de coluna necessária, autenticando no repositório de chaves que contém a chave mestra de coluna.

Aplicativos que compartilham uma instância de SqlConnection entre vários usuários deverão usar SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand. Cada usuário precisa registrar um provedor de repositório de chaves em uma instância do SqlCommand antes de executar uma consulta para acessar uma coluna criptografada. Se o provedor do repositório de chaves for capaz de acessar a chave mestra de coluna necessária no repositório de chaves usando as credenciais fornecidas pelo usuário, a consulta terá sucesso.

Aplicativos que criam uma instância de SqlConnection para cada usuário deverão usar SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnConnection. Provedores de repositório de chaves registrados com esse método podem ser usados pela conexão para qualquer consulta que acesse dados criptografados.

Provedores de repositório de chaves registrados usando SqlConnection.RegisterColumnEncryptionKeyStoreProviders usarão a identidade fornecida pelo aplicativo ao autenticar no repositório de chaves.

O seguinte exemplo mostra a precedência de provedores de repositório de chave mestra de coluna personalizada registrados em uma instância de conexão:

class Program
{
    static void Main()
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        MyCustomKeyStoreProvider myProvider = new MyCustomKeyStoreProvider();
        customKeyStoreProviders.Add("MY_CUSTOM_STORE", myProvider);
        // Registers the provider globally
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            customKeyStoreProviders.Clear();
            SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider();
            customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
            // Registers the provider on the connection
            // These providers will take precedence over globally registered providers
            connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);
        }
    }
}

O seguinte exemplo mostra a precedência de provedores de repositório de chave mestra de coluna personalizada registrados em uma instância de comando:

class Program
{
    static void Main()
    {
        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customKeyStoreProviders = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
        MyCustomKeyStoreProvider firstProvider = new MyCustomKeyStoreProvider();
        customKeyStoreProviders.Add("FIRST_CUSTOM_STORE", firstProvider);
        // Registers the provider globally
        SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customKeyStoreProviders);

        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            customKeyStoreProviders.Clear();
            MyCustomKeyStoreProvider secondProvider = new MyCustomKeyStoreProvider();
            customKeyStoreProviders.Add("SECOND_CUSTOM_STORE", secondProvider);
            // Registers the provider on the connection
            connection.RegisterColumnEncryptionKeyStoreProvidersOnConnection(customKeyStoreProviders);

            using (SqlCommand command = connection.CreateCommand())
            {
                customKeyStoreProviders.Clear();
                SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider();
                customKeyStoreProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);
                // Registers the provider on the command
                // These providers will take precedence over connection-level providers and globally registered providers
                command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customKeyStoreProviders);
            }
        }
    }
}

Usando provedores de repositórios de chaves mestras de coluna para o provisionamento programático de chaves

Ao acessar as colunas criptografadas, o Provedor de Dados do Microsoft .NET para SQL Server localiza e chama de maneira transparente o provedor de repositório de chaves mestras de coluna adequado para descriptografar as chaves de criptografia da coluna. Normalmente, o código normal do aplicativo não chama diretamente os provedores de repositórios de chaves mestras de coluna. No entanto, é possível criar uma instância e chamar um provedor de maneira explícita para provisionar programaticamente e gerenciar chaves do Always Encrypted: para gerar uma chave de criptografia de coluna criptografada e descriptografar uma chave de criptografia de coluna (por exemplo, como a rotação de chave mestra de coluna da parte). Para obter mais informações, confira Visão geral do gerenciamento de chaves do Always Encrypted. A implementação de suas próprias ferramentas de gerenciamento de chaves poderá ser necessária apenas se você usar um provedor personalizado de repositórios de chaves. Ao usar as chaves armazenadas em repositórios de chaves, para as quais os provedores internos existem, ou no Azure Key Vault, é possível usar as ferramentas existentes, como SQL Server Management Studio ou PowerShell, para gerenciar e provisionar as chaves. O exemplo abaixo ilustra como gerar uma chave de criptografia de coluna e como usar a Classe SqlColumnEncryptionCertificateStoreProvider para criptografar a chave com um certificado.

using System.Security.Cryptography;
static void Main(string[] args)
{
    byte[] EncryptedColumnEncryptionKey = GetEncryptedColumnEncryptonKey();
    Console.WriteLine("0x" + BitConverter.ToString(EncryptedColumnEncryptionKey).Replace("-", ""));
    Console.ReadKey();
}

static byte[]  GetEncryptedColumnEncryptonKey()
{
    int cekLength = 32;
    String certificateStoreLocation = "CurrentUser";
    String certificateThumbprint = "698C7F8E21B2158E9AED4978ADB147CF66574180";
    // Generate the plaintext column encryption key.
    byte[] columnEncryptionKey = new byte[cekLength];
    RNGCryptoServiceProvider rngCsp = new RNGCryptoServiceProvider();
    rngCsp.GetBytes(columnEncryptionKey);

    // Encrypt the column encryption key with a certificate.
    string keyPath = String.Format(@"{0}/My/{1}", certificateStoreLocation, certificateThumbprint);
    SqlColumnEncryptionCertificateStoreProvider provider = new SqlColumnEncryptionCertificateStoreProvider();
    return provider.EncryptColumnEncryptionKey(keyPath, @"RSA_OAEP", columnEncryptionKey);
}

Controle do impacto sobre o desempenho do Always Encrypted

Como o Always Encrypted é uma tecnologia de criptografia do cliente, a maior parte da sobrecarga de desempenho será observada no lado dele, não no banco de dados. Além do custo das operações de criptografia e descriptografia, outras fontes de sobrecarga de desempenho no lado do cliente são:

  • Viagens de ida e volta adicionais ao banco de dados para recuperar metadados dos parâmetros de consulta.
  • Chamadas a um repositório de chaves mestras de coluna para acessar uma chave mestra de coluna.

Esta seção descreve as otimizações de desempenho internas do Provedor de Dados do Microsoft .NET para SQL Server e como é possível controlar o impacto dos dois fatores acima no desempenho.

Controlando as viagens de ida e volta para recuperar metadados dos parâmetros de consulta

Caso o Always Encrypted esteja habilitado para uma conexão, por padrão, o Provedor de Dados do Microsoft .NET para SQL Server chamará sys.sp_describe_parameter_encryption para cada consulta parametrizada, passando a instrução de consulta (sem nenhum valor de parâmetro) para o SQL Server. O sys.sp_describe_parameter_encryption analisa a instrução de consulta para descobrir se os parâmetros precisam ser criptografados e, em caso afirmativo, para cada um, ele retorna as informações relacionadas à criptografia que permitirão ao Provedor de Dados do Microsoft .NET para SQL Server criptografar os valores de parâmetro. O comportamento acima garante um alto nível de transparência para o aplicativo cliente. O aplicativo e o desenvolvedor de aplicativos não precisam saber quais consultas acessam as colunas criptografadas, desde que os valores direcionados às colunas sejam passados ao Provedor de Dados do Microsoft .NET para SQL Server em objetos SqlParameter.

Cache de metadados de consulta

O Provedor de Dados do Microsoft .NET para SQL Server armazena em cache os resultados de sys.sp_describe_parameter_encryption para cada instrução de consulta. Portanto, se a mesma instrução de consulta for executada várias vezes, o driver chamará sys.sp_describe_parameter_encryption apenas uma vez. O caching de metadados de criptografia para instruções de consulta reduz consideravelmente o custo de desempenho da busca de metadados do banco de dados. O caching está habilitado por padrão. É possível desabilitar o cache de metadados do parâmetro configurando a propriedade SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled como falsa, mas isso não é recomendado, exceto em casos raros, como o descrito abaixo:

Considere um banco de dados que tem dois esquemas diferentes: s1 e s2. Cada esquema contém uma tabela com o mesmo nome: t. As definições das tabelas s1.t e s2.t são idênticas, exceto pelas propriedades relacionadas à criptografia: uma coluna, chamada c, não é criptografada em s1.t, mas é criptografada em s2.t. O banco de dados tem dois usuários: u1 e u2. O esquema padrão para os usuários u1 é s1. O esquema padrão para u2 é s2. Um aplicativo .NET abre duas conexões com o banco de dados, representando o usuário u1 em uma conexão e o usuário u2 em outra. O aplicativo envia uma consulta com um parâmetro direcionado à coluna c pela conexão do usuário u1 (a consulta não especifica o esquema, portanto, o esquema padrão do usuário será considerado). Em seguida, o aplicativo envia a mesma consulta pela conexão do usuário u2. Caso o cache de metadados de consulta esteja habilitado, após a primeira consulta, o cache será preenchido com metadados indicando que a coluna c, destino do parâmetro de consulta, não é criptografada. Como a segunda consulta tem a instrução de consulta idêntica, as informações armazenadas no cache serão usadas. Como resultado, o driver enviará a consulta sem criptografar o parâmetro (o que é incorreto, já que a coluna de destino, s2.t.c, é criptografada), causando a perda do valor de texto sem formatação do parâmetro para o servidor. O servidor detectará a incompatibilidade e forçará o driver a atualizar o cache, para que o aplicativo reenvie a consulta de forma transparente com o valor do parâmetro criptografado corretamente. Nesse caso, o caching deve ser desabilitado para prevenir a perda de valores confidenciais para o servidor.

Configurando o Always Encrypted no nível da consulta

Para controlar o impacto no desempenho da recuperação de metadados de criptografia para consultas parametrizadas, é possível habilitar o Always Encrypted para consultas individuais, em vez de configurá-lo para a conexão. Assim, você pode garantir que sys.sp_describe_parameter_encryption é invocado apenas para consultas que você sabe que têm parâmetros que se destinam a colunas criptografadas. Observe, no entanto, que ao fazer isso você reduz a transparência da criptografia: caso altere as propriedades de criptografia de suas colunas de banco de dados, talvez seja necessário alterar o código do seu aplicativo para alinhá-lo às alterações de esquema.

Observação

Configurar o Always Encrypted no nível de consulta limita o benefício de desempenho do cache de metadados de criptografia de parâmetro.

Para controlar o comportamento do Always Encrypted em relação a consultas individuais, você precisa usar este construtor de SqlCommand e SqlCommandColumnEncryptionSetting. Veja algumas diretrizes úteis:

  • Se a maioria das consultas executadas por um aplicativo cliente acessar colunas criptografadas:
    • Defina a palavra-chave da cadeia de conexão de Configuração da Criptografia de Coluna como Habilitado.
    • Defina SqlCommandColumnEncryptionSetting como Desabilitado para consultas individuais que não acessam nenhuma coluna criptografada. Essa configuração desabilitará a chamada a sys.sp_describe_parameter_encryption, além de ser uma tentativa de descriptografar os valores no conjunto de resultados.
    • Defina SqlCommandColumnEncryptionSetting.ResultSetOny como ResultSetOnly para consultas individuais que não têm parâmetros que exijam criptografia, mas que recuperam dados de colunas criptografadas. Essa configuração desabilitará a chamada a sys.sp_describe_parameter_encryption e a criptografia de parâmetros. A consulta poderá descriptografar os resultados das colunas de criptografia.
  • Se a maioria das consultas executadas por um aplicativo cliente não acessar colunas criptografadas:
    • Defina a palavra-chave da cadeia de conexão de Configuração da Criptografia de Coluna como Desabilitado.
    • Defina SqlCommandColumnEncryptionSetting.Enabled como Habilitado para consultas individuais que têm parâmetros que precisam ser criptografados. Esta configuração habilitará a chamada a sys.sp_describe_parameter_encryption e a descriptografia de todos os resultados de consulta recuperados de colunas criptografadas.
    • Defina SqlCommandColumnEncryptionSetting.ResultSetOnly como ResultSetOnly para consultas que não têm parâmetros que exijam criptografia, mas que recuperam dados de colunas criptografadas. Essa configuração desabilitará a chamada a sys.sp_describe_parameter_encryption e a criptografia de parâmetros. A consulta poderá descriptografar os resultados das colunas de criptografia.

No exemplo abaixo, o Sempre Criptografado está desabilitado para a conexão de banco de dados. A consulta emitida pelo aplicativo tem um parâmetro que se destina à coluna LastName não criptografada. A consulta recupera dados das colunas SSN e BirthDate que são criptografadas. Nesse caso, não é necessário chamar sys.sp_describe_parameter_encryption para recuperar os metadados de criptografia. No entanto, a descriptografia dos resultados da consulta precisa estar habilitada para que o aplicativo possa receber valores de texto não criptografado das duas colunas criptografadas. A configuração SqlCommandColumnEncryptionSettingResultSetOnly é usada para garantir isso.

string connectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";
using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlCommand cmd = new SqlCommand(@"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [LastName]=@LastName",
connection, null, SqlCommandColumnEncryptionSetting.ResultSetOnly))
{
    connection.Open();
    SqlParameter paramLastName = cmd.CreateParameter();
    paramLastName.ParameterName = @"@LastName";
    paramLastName.DbType = DbType.String;
    paramLastName.Direction = ParameterDirection.Input;
    paramLastName.Value = "Abel";
    paramLastName.Size = 50;
    cmd.Parameters.Add(paramLastName);
    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                Console.WriteLine(@"{0}, {1}, {2}, {3}", reader[0], reader[1], reader[2], ((DateTime)reader[3]).ToShortDateString());
            }
        }
    }
}

Cache de chaves de criptografia de coluna

Para reduzir o número de chamadas a um repositório de chaves mestras de coluna para descriptografar as chaves de criptografia de coluna, o Provedor de Dados do Microsoft .NET para SQL Server armazena em cache as chaves de criptografia da coluna de texto não criptografado na memória. Após o provedor receber o valor da chave de criptografia de coluna criptografado dos metadados do banco de dados, o driver primeiro tentará encontrar a chave de criptografia de coluna de texto não criptografado correspondente ao valor da chave criptografado. O driver chamará o repositório de chaves que contém a chave mestra da coluna apenas se não conseguir encontrar o valor da chave de criptografia de coluna criptografado no cache.

Por motivos de segurança, as entradas de cache serão removidas após um intervalo de vida útil configurável. O valor de vida útil padrão é de 2 horas. Caso tenha requisitos de segurança mais rígidos sobre quanto tempo as chaves de criptografia de coluna podem ser armazenadas em cache em um texto não criptografado no aplicativo, será possível alterá-las usando a propriedade SqlConnection.ColumnEncryptionKeyCacheTtl.

Os provedores de repositório de chaves personalizados registrados que usam SqlConnection.RegisterColumnEncryptionKeyStoreProvidersOnConnection e SqlCommand.RegisterColumnEncryptionKeyStoreProvidersOnCommand não terão suas chaves de criptografia de coluna descriptografadas armazenadas em cache pelo Provedor de Dados Microsoft .NET para SQL Server. Em vez disso, os provedores de repositório de chaves personalizadas devem implementar um mecanismo próprio de cache. Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProviderv3.0.0 e posteriores vem com sua própria implementação de cache.

Para dar suporte a cenários em que diferentes usuários do mesmo aplicativo podem executar várias consultas, os provedores de armazenamento de chaves personalizadas podem ser mapeados para um usuário e registrados em uma conexão ou instância de comando específica desse usuário. O exemplo a seguir mostra como uma instância do Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider pode ser reutilizada em diferentes objetos SqlCommand para o mesmo usuário. Seu cache de chave de criptografia de coluna persistirá em várias consultas, reduzindo o número de viagens de ida e volta ao repositório de chaves:

using Microsoft.Data.SqlClient;
using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider;
using System.Collections.Generic;

class Program
{
    // Maps a SqlColumnEncryptionAzureKeyVaultProvider to some object that represents a user
    static Dictionary<object, SqlColumnEncryptionAzureKeyVaultProvider> providerByUser = new();

    void ExecuteSelectQuery(object user, SqlConnection connection)
    {
        // Check if the user already has a SqlColumnEncryptionAzureKeyVaultProvider
        SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = providerByUser[user];
        if (azureKeyVaultProvider is null)
        {
            // Create a new SqlColumnEncryptionAzureKeyVaultProvider with the user's credentials and save it for future use
            azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider();
            providerByUser[user] = azureKeyVaultProvider;
        }

        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customProviders = new();
        customProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);

        using SqlCommand command = new("SELECT * FROM Customers", connection);
        command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customProviders);
        // Perform database operations
        // Any decrypted column encryption keys will be cached by azureKeyVaultProvider
    }

    void ExecuteUpdateQuery(object user, SqlConnection connection)
    {
        // Check if the user already has a SqlColumnEncryptionAzureKeyVaultProvider
        SqlColumnEncryptionAzureKeyVaultProvider azureKeyVaultProvider = providerByUser[user];
        if (azureKeyVaultProvider is null)
        {
            // Create a new SqlColumnEncryptionAzureKeyVaultProvider with the user's credentials and save it for future use
            azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider();
            providerByUser[user] = azureKeyVaultProvider;
        }

        Dictionary<string, SqlColumnEncryptionKeyStoreProvider> customProviders = new();
        customProviders.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);

        using SqlCommand command = new("UPDATE Customers SET Name = 'NewName' WHERE CustomerId = 1", connection);
        command.RegisterColumnEncryptionKeyStoreProvidersOnCommand(customProviders);
        // Perform database operations
        // Any decrypted column encryption keys will be cached by azureKeyVaultProvider
    }
}

Como habilitar proteção extra para um SQL Server comprometido

Por padrão, o Provedor de Dados do Microsoft .NET para SQL Server depende do sistema de banco de dados (SQL Server ou Banco de Dados SQL do Azure) para fornecer metadados sobre quais colunas no banco de dados são criptografadas e como isso é feito. Os metadados de criptografia habilitam o Provedor de Dados do Microsoft .NET para SQL Server a criptografar parâmetros de consulta e descriptografar os resultados da consulta sem nenhuma entrada do aplicativo, o que reduz consideravelmente o número de alterações necessárias nele. No entanto, caso o processo do SQL Server seja comprometido e um invasor viole os metadados enviados ao Provedor de Dados do Microsoft .NET para SQL Server pelo SQL Server, o invasor poderá roubar informações confidenciais. Esta seção descreve as APIs que ajudam a fornecer um nível extra de proteção contra esse tipo de ataque, à custa da redução da transparência.

Forçando a criptografia de parâmetro

Antes que o Provedor de Dados do Microsoft .NET para SQL Server envie uma consulta parametrizada para o SQL Server, ele solicita ao SQL Server (chamando sys.sp_describe_parameter_encryption) para analisar a instrução da consulta e fornecer informações sobre quais parâmetros devem ser criptografados. Uma instância do SQL Server comprometida poderá confundir o Provedor de Dados do Microsoft .NET para SQL Server enviando metadados e indicando que o parâmetro não tem como destino uma coluna criptografada, apesar de ser criptografada no banco de dados. Como resultado, o Provedor de Dados do Microsoft .NET para SQL Server não criptografará o valor do parâmetro e o enviará como texto não criptografado para a instância do SQL Server comprometida.

Para prevenir um ataque desse tipo, um aplicativo pode definir a Propriedade SqlParameter.ForceColumnEncryption do parâmetro como true. Essa configuração fará com que o Provedor de Dados do Microsoft .NET para SQL Server gere uma exceção, caso os metadados recebidos do servidor indiquem que o parâmetro não precisa ser criptografado.

Embora o uso da propriedade SqlParameter.ForceColumnEncryption ajude a aprimorar a segurança, ele também reduz a transparência da criptografia para o aplicativo cliente. Se você atualizar o esquema de banco de dados para alterar o conjunto de colunas criptografadas, talvez seja necessário fazer alterações no aplicativo também.

O exemplo de código a seguir ilustra o uso da propriedade SqlParameter.ForceColumnEncryption para impedir que os números de seguro social sejam enviados em texto não criptografado para o banco de dados.

using (SqlCommand cmd = _sqlconn.CreateCommand())
{
    // Use parameterized queries to access Always Encrypted data.

    cmd.CommandText = @"SELECT [SSN], [FirstName], [LastName], [BirthDate] FROM [dbo].[Patients] WHERE [SSN] = @SSN;";

    SqlParameter paramSSN = cmd.CreateParameter();
    paramSSN.ParameterName = @"@SSN";
    paramSSN.DbType = DbType.AnsiStringFixedLength;
    paramSSN.Direction = ParameterDirection.Input;
    paramSSN.Value = ssn;
    paramSSN.Size = 11;
    paramSSN.ForceColumnEncryption = true;
    cmd.Parameters.Add(paramSSN);

    using (SqlDataReader reader = cmd.ExecuteReader())
    {
        // Do something.
    }
}

Como configurar caminhos confiáveis da chave mestra da coluna

Os metadados de criptografia que o SQL Server retorna para parâmetros de consulta direcionados às colunas criptografadas e para resultados recuperados das colunas de criptografia incluem o caminho da chave mestra da coluna que identifica o repositório de chaves e a localização da chave no repositório. Caso a instância do SQL Server seja comprometida, ela poderá enviar o caminho da chave, direcionando o Provedor de Dados do Microsoft .NET para SQL Server para a localização controlada por um invasor. Esse processo poderá causar a perda das credenciais do repositório de chaves se o repositório de chaves exigir a autenticação do aplicativo.

Para prevenir esses ataques, o aplicativo poderá especificar a lista de caminhos de chave confiáveis para determinado servidor usando a propriedade SqlConnection.ColumnEncryptionTrustedMasterKeyPaths. Caso o Provedor de Dados do Microsoft .NET para SQL Server receba um caminho de chave que esteja fora da lista de caminhos confiáveis, ele gerará uma exceção.

Embora a configuração de caminhos de chave confiáveis aumente a segurança do seu aplicativo, será necessário alterar o código ou/e a configuração do aplicativo sempre que você girar a chave mestra da coluna (sempre que o caminho dela for alterado).

O seguinte exemplo mostra como configurar caminhos de chave mestra de coluna confiáveis:

// Configure trusted key paths to protect against fake key paths sent by a compromised SQL Server instance
// First, create a list of trusted key paths for your server
List<string> trustedKeyPathList = new List<string>();
trustedKeyPathList.Add("CurrentUser/my/425CFBB9DDDD081BB0061534CE6AB06CB5283F5Ea");

// Register the trusted key path list for your server
SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(serverName, trustedKeyPathList);

Copiando dados criptografados usando SqlBulkCopy

Com SqlBulkCopy, você pode copiar dados, que já estão criptografados e armazenados em uma tabela, em outra tabela, sem descriptografá-los. Para fazer isso:

  • Verifique se a configuração de criptografia da tabela de destino é idêntica à configuração da tabela de origem. Em particular, as duas tabelas devem ter as mesmas colunas criptografadas, e as colunas devem ser criptografadas usando os mesmos tipos de criptografia e as mesmas chaves de criptografia. Se uma das colunas de destino for criptografada de modo diferente da coluna de origem correspondente, você não poderá descriptografar os dados na tabela de destino após a operação de cópia. Os dados serão corrompidos.
  • Configure ambas as conexões de banco de dados para a tabela de origem e a tabela de destino sem o Always Encrypted habilitado.
  • Defina a opção AllowEncryptedValueModifications (veja SqlBulkCopyOptions).

Observação

Tenha cuidado ao especificar AllowEncryptedValueModifications. Essa configuração poderá causar a corrupção do banco de dados, já que o Provedor de Dados do Microsoft .NET para SQL Server não verifica se os dados estão realmente criptografados nem se eles estão criptografados corretamente, usando o mesmo tipo de criptografia, algoritmo e chave que a coluna de destino.

Aqui está um exemplo que copia dados de uma tabela em outra. Pressupõe-se que as colunas SSN e BirthDate estejam criptografadas.

static public void CopyTablesUsingBulk(string sourceTable, string targetTable)
{
    string sourceConnectionString = "Data Source=server63; Initial Catalog=Clinic; Integrated Security=true";
    string targetConnectionString = "Data Source=server64; Initial Catalog=Clinic; Integrated Security=true";
    using (SqlConnection connSource = new SqlConnection(sourceConnectionString))
    {
        connSource.Open();
        using (SqlCommand cmd = new SqlCommand(string.Format("SELECT [PatientID], [SSN], [FirstName], [LastName], [BirthDate] FROM {0}", sourceTable), connSource))
        {
            using (SqlDataReader reader = cmd.ExecuteReader())
            using (SqlBulkCopy copy = new SqlBulkCopy(targetConnectionString, SqlBulkCopyOptions.KeepIdentity | SqlBulkCopyOptions.AllowEncryptedValueModifications))
            {
                copy.EnableStreaming = true;
                copy.DestinationTableName = targetTable;
                copy.WriteToServer(reader);
            }
        }
    }
}

Referência da API para Always Encrypted

Namespace:Microsoft.Data.SqlClient

Assembly: Microsoft.Data.SqlClient.dll

Nome Descrição
Classe SqlColumnEncryptionCertificateStoreProvider Um provedor de repositório de chaves para o Repositório de Certificados do Windows.
Classe SqlColumnEncryptionCngProvider Um provedor de repositório de chaves para a API de Criptografia da Microsoft: Next Generation (CNG).
Classe SqlColumnEncryptionCspProvider Um provedor de repositório de chaves para CSP (Provedores de Serviços de Criptografia) baseados na CAPI da Microsoft.
SqlColumnEncryptionKeyStoreProvider Classe base dos provedores de repositório de chaves.
Enumeração SqlCommandColumnEncryptionSetting Configurações para controlar o comportamento do Always Encrypted para consultas individuais.
SqlConnectionAttestationProtocol Enumeration Especifica um valor para o protocolo de atestado ao usar Always Encrypted com enclaves seguros
Enumeração SqlConnectionColumnEncryptionSetting Configurações para habilitar a criptografia e a descriptografia de uma conexão de banco de dados.
Propriedade SqlConnectionStringBuilder.ColumnEncryptionSetting Obtém e define o Always Encrypted na cadeia de conexão.
Propriedade SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled Habilita e desabilita o caching de metadados de consulta de criptografia.
Propriedade SqlConnection.ColumnEncryptionKeyCacheTtl Obtém e define a vida útil das entradas no cache de chaves de criptografia de coluna.
Propriedade SqlConnection.ColumnEncryptionTrustedMasterKeyPaths Permite que você defina uma lista de caminhos confiáveis de chave para um servidor de banco de dados. Se durante o processamento de uma consulta de aplicativo o driver receber um caminho de chave que não esteja na lista, a consulta falhará. Esta propriedade oferece proteção extra contra ataques de segurança que envolvem um SQL Server comprometido fornecendo falsos caminhos principais, que podem levar a vazar credenciais de repositório de chaves.
Método SqlConnection.RegisterColumnEncryptionKeyStoreProviders Permite que você registre os provedores de repositório de chaves personalizado. É um dicionário que mapeia nomes de provedor de repositório de chaves para implementações do provedor de repositório de chaves.
Construtor SqlCommand (String, SqlConnection, SqlTransaction, SqlCommandColumnEncryptionSetting) Permite controlar o comportamento do Always Encrypted para consultas individuais.
Propriedade SqlParameter.ForceColumnEncryption Impõe a criptografia de um parâmetro. Se o SQL Server informar o driver que o parâmetro não precisa ser criptografado, a consulta que estiver o parâmetro falhará. Essa propriedade oferece proteção extra contra ataques de segurança que envolvem um SQL Server comprometido fornecendo metadados de criptografia incorretos ao cliente, o que pode levar à divulgação de dados.
palavra-chave da cadeia de conexão: Column Encryption Setting=enabled Habilita ou desabilita a funcionalidade Always Encrypted para a conexão.

Confira também