Database
 sql >> Base de Dados >  >> RDS >> Database

Os Internos de COM ENCRIPTAÇÃO


É muito fácil para um administrador do SQL Server recuperar o texto de stored procedures, views, funções e triggers protegidos usando WITH ENCRYPTION . Muitos artigos foram escritos sobre isso e várias ferramentas comerciais estão disponíveis. O esboço básico do método comum é:
  1. Obtenha o formulário criptografado (A) usando a Conexão de Administrador Dedicado.
  2. Inicie uma transação.
  3. Substitua a definição do objeto por um texto conhecido (B) de pelo menos o mesmo comprimento que o original.
  4. Obtenha o formulário criptografado para o texto conhecido (C).
  5. Reverta a transação para deixar o objeto de destino em seu estado inicial.
  6. Obtenha o original não criptografado aplicando um exclusivo-ou a cada caractere:A XOR (B XOR C)

Isso tudo é bastante simples, mas parece um pouco mágico:não explica muito sobre como e por que funciona . Este artigo aborda esse aspecto para aqueles que acham esses tipos de detalhes interessantes e fornece um método alternativo para descriptografia que é mais ilustrativo do processo.

A cifra de fluxo


O algoritmo de criptografia subjacente que o SQL Server usa para criptografia de módulo é a cifra de fluxo RC4™. Um esboço do processo de criptografia é:
  1. Inicie a cifra RC4 com uma chave criptográfica.
  2. Gere um fluxo pseudo-aleatório de bytes.
  3. Combine o texto simples do módulo com o fluxo de bytes usando ou exclusivo.

Podemos ver esse processo ocorrendo usando um depurador e símbolos públicos. Por exemplo, o rastreamento de pilha abaixo mostra o SQL Server inicializando a chave RC4 enquanto se prepara para criptografar o texto do módulo:



Este próximo mostra o SQL Server criptografando o texto usando o fluxo de bytes pseudoaleatórios RC4:



Como a maioria das cifras de fluxo, o processo de descriptografia é o mesmo que a criptografia, fazendo uso do fato de que exclusivo-ou é reversível (A XOR B XOR B = A ).

O uso de uma cifra de fluxo é o motivo exclusivo-ou é usado no método descrito no início do artigo. Não há nada inerentemente inseguro em usar o exclusivo-ou, desde que um método de criptografia seguro seja usado, a chave de inicialização seja mantida em segredo e a chave não seja reutilizada.

RC4 não é particularmente forte, mas esse não é o principal problema aqui. Dito isso, vale a pena notar que a criptografia usando RC4 está sendo gradualmente removida do SQL Server e está obsoleta (ou desabilitada, dependendo da versão e do nível de compatibilidade do banco de dados) para operações do usuário, como a criação de uma chave simétrica.

A chave de inicialização RC4


O SQL Server usa três informações para gerar a chave usada para inicializar a cifra de fluxo RC4:
  1. O GUID da família de banco de dados.
    Isso pode ser obtido mais facilmente consultando sys.database_recovery_status . Também é visível em comandos não documentados como DBCC DBINFO e DBCC DBTABLE .
  2. O ID do objeto do módulo de destino.
    Este é apenas o ID do objeto familiar. Observe que nem todos os módulos que permitem criptografia têm escopo de esquema. Você precisará usar visualizações de metadados (sys.triggers ou sys.server_triggers ) para obter o ID do objeto para DDL e acionadores com escopo de servidor, em vez de sys.objects ou OBJECT_ID , já que eles só funcionam com objetos com escopo de esquema.
  3. O ID do subobjeto do módulo de destino.
    Este é o número do procedimento para procedimentos armazenados numerados. É 1 para um procedimento armazenado não numerado e zero em todos os outros casos.

Usando o depurador novamente, podemos ver o GUID da família sendo recuperado durante a inicialização da chave:



O GUID da família de banco de dados é digitado identificador único , o ID do objeto é inteiro , e o ID do subobjeto é pequeno .

Cada parte da chave deve ser convertido para um formato binário específico. Para o GUID da família de banco de dados, convertendo o identificador único digite para binário(16) produz a representação binária correta. Os dois IDs devem ser convertidos em binário em representação little-endian (byte menos significativo primeiro).

Observação: Tenha muito cuidado para não fornecer acidentalmente o GUID como uma string! Deve ser digitado identificador único .

O snippet de código abaixo mostra as operações de conversão corretas para alguns valores de exemplo:
DECLARE 
    @family_guid binary(16) = CONVERT(binary(16), {guid 'B1FC892E-5824-4FD3-AC48-FBCD91D57763'}),
    @objid binary(4) = CONVERT(binary(4), REVERSE(CONVERT(binary(4), 800266156))),
    @subobjid binary(2) = CONVERT(binary(2), REVERSE(CONVERT(binary(2), 0)));

A etapa final para gerar a chave de inicialização RC4 é concatenar os três valores binários acima em um único binário(22) e calcular o hash SHA-1 do resultado:
DECLARE 
    @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);

Para os dados de amostra fornecidos acima, a chave de inicialização final é:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8

A contribuição do ID do objeto e do ID do subobjeto do módulo de destino para o hash SHA-1 é difícil de ver em uma única captura de tela do depurador, mas o leitor interessado pode consultar a desmontagem de uma parte do initspkey abaixo de:
call    sqllang!A_SHAInit
lea     rdx,[rsp+40h]
lea     rcx,[rsp+50h]
mov     r8d,10h
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+24h]
lea     rcx,[rsp+50h]
mov     r8d,4
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+20h]
lea     rcx,[rsp+50h]
mov     r8d,2
call    sqllang!A_SHAUpdate
lea     rdx,[rsp+0D0h]
lea     rcx,[rsp+50h]
call    sqllang!A_SHAFinal
lea     r8,[rsp+0D0h]
mov     edx,14h
mov     rcx,rbx
call    sqllang!rc4_key (00007fff`89672090)

O SHAInit e SHAUpdate chamadas adicionam componentes ao hash SHA, que é eventualmente calculado por uma chamada para SHAFinal .

O SHAInit chamada contribui com 10h bytes (16 decimal) armazenados em [rsp+40h], que é o GUID da família . A primeira SHAUpdate chamada adiciona 4 bytes (como indicado no registrador r8d), armazenado em [rsp+24h], que é o objeto EU IRIA. A segunda SHAUpdate chamada adiciona 2 bytes, armazenados em [rsp+20h], que é o subobjid .

As instruções finais passam o hash SHA-1 calculado para a rotina de inicialização da chave RC4 rc4_key . O comprimento do hash é armazenado no registrador edx:14h (20 decimais) bytes, que é o comprimento do hash definido para SHA e SHA-1 (160 bits).

A implementação do RC4


O algoritmo principal do RC4 é bem conhecido e relativamente simples. Seria melhor implementado em uma linguagem .Net por motivos de eficiência e desempenho, mas há uma implementação T-SQL abaixo.

Essas duas funções T-SQL implementam o algoritmo de agendamento de chaves RC4 e o gerador de números pseudoaleatórios e foram originalmente escritas pelo MVP do SQL Server Peter Larsson. Fiz algumas pequenas modificações para melhorar um pouco o desempenho e permitir que binários de comprimento LOB sejam codificados e decodificados. Esta parte do processo pode ser substituída por qualquer implementação RC4 padrão.
/*
** RC4 functions
** Based on http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=76258
** by Peter Larsson (SwePeso)
*/
IF OBJECT_ID(N'dbo.fnEncDecRc4', N'FN') IS NOT NULL
    DROP FUNCTION dbo.fnEncDecRc4;
GO
IF OBJECT_ID(N'dbo.fnInitRc4', N'TF') IS NOT NULL
    DROP FUNCTION dbo.fnInitRc4;
GO
CREATE FUNCTION dbo.fnInitRc4
    (@Pwd varbinary(256))
RETURNS @Box table
    (
        i tinyint PRIMARY KEY, 
        v tinyint NOT NULL
    )
WITH SCHEMABINDING
AS
BEGIN
    DECLARE @Key table
    (
        i tinyint PRIMARY KEY,
        v tinyint NOT NULL
    );
 
    DECLARE
        @Index smallint = 0,
        @PwdLen tinyint = DATALENGTH(@Pwd);
 
    WHILE @Index <= 255
    BEGIN
        INSERT @Key
            (i, v)
        VALUES
            (@Index, CONVERT(tinyint, SUBSTRING(@Pwd, @Index % @PwdLen + 1, 1)));
 
        INSERT @Box (i, v)
        VALUES (@Index, @Index);
 
        SET @Index += 1;
    END;
 
    DECLARE
        @t tinyint = NULL,
        @b smallint = 0;
 
    SET @Index = 0;
 
    WHILE @Index <= 255
    BEGIN
        SELECT @b = (@b + b.v + k.v) % 256
        FROM @Box AS b
        JOIN @Key AS k
            ON k.i = b.i
        WHERE b.i = @Index;
 
        SELECT @t = b.v
        FROM @Box AS b
        WHERE b.i = @Index;
 
        UPDATE b1
        SET b1.v = (SELECT b2.v FROM @Box AS b2 WHERE b2.i = @b)
        FROM @Box AS b1
        WHERE b1.i = @Index;
 
        UPDATE @Box
        SET v = @t
        WHERE i = @b;
 
        SET @Index += 1;
    END;
 
    RETURN;
END;
GO
CREATE FUNCTION dbo.fnEncDecRc4
(
    @Pwd varbinary(256),
    @Text varbinary(MAX)
)
RETURNS varbinary(MAX)
WITH 
    SCHEMABINDING, 
    RETURNS NULL ON NULL INPUT
AS
BEGIN
    DECLARE @Box AS table 
    (
        i tinyint PRIMARY KEY, 
        v tinyint NOT NULL
    );
 
    INSERT @Box
        (i, v)
    SELECT
        FIR.i, FIR.v
    FROM dbo.fnInitRc4(@Pwd) AS FIR;
 
    DECLARE
        @Index integer = 1,
        @i smallint = 0,
        @j smallint = 0,
        @t tinyint = NULL,
        @k smallint = NULL,
        @CipherBy tinyint = NULL,
        @Cipher varbinary(MAX) = 0x;
 
    WHILE @Index <= DATALENGTH(@Text)
    BEGIN
        SET @i = (@i + 1) % 256;
 
        SELECT
            @j = (@j + b.v) % 256,
            @t = b.v
        FROM @Box AS b
        WHERE b.i = @i;
 
        UPDATE b
        SET b.v = (SELECT w.v FROM @Box AS w WHERE w.i = @j)
        FROM @Box AS b
        WHERE b.i = @i;
 
        UPDATE @Box
        SET v = @t
        WHERE i = @j;
 
        SELECT @k = b.v
        FROM @Box AS b
        WHERE b.i = @i;
 
        SELECT @k = (@k + b.v) % 256
        FROM @Box AS b
        WHERE b.i = @j;
 
        SELECT @k = b.v
        FROM @Box AS b
        WHERE b.i = @k;
 
        SELECT
            @CipherBy = CONVERT(tinyint, SUBSTRING(@Text, @Index, 1)) ^ @k,
            @Cipher = @Cipher + CONVERT(binary(1), @CipherBy);
 
        SET @Index += 1;
    END;
 
    RETURN @Cipher;
END;
GO

O texto do módulo criptografado


A maneira mais fácil para um administrador do SQL Server obter isso é ler o varbinary(max) valor armazenado no imageval coluna de sys.sysobjvalues , que só é acessível por meio da Conexão de Administrador Dedicado (DAC).

Esta é a mesma ideia do método de rotina descrito na introdução, embora adicionemos um filtro em valclass =1. Esta tabela interna também é um local conveniente para obter o subobjid . Caso contrário, precisaríamos verificar sys.numbered_procedures quando o objeto de destino for um procedimento, use 1 para um procedimento não numerado ou zero para qualquer outra coisa, conforme descrito anteriormente.

É possível evitar usar o DAC lendo o imageval de sys.sysobjvalues diretamente, usando várias DBCC PAGE chamadas. Isso envolve um pouco mais de trabalho para localizar as páginas dos metadados, siga o imageval cadeia LOB e leia os dados binários de destino de cada página. A última etapa é muito mais fácil de fazer em uma linguagem de programação diferente do T-SQL. Observe que DBCC PAGE funcionará, mesmo que o objeto base não seja normalmente legível a partir de uma conexão não DAC. Se a página não estiver na memória, ela será lida do armazenamento persistente normalmente.

O esforço extra para evitar o requisito de DAC compensa ao permitir que vários usuários usem o processo de descriptografia simultaneamente. Usarei a abordagem DAC neste artigo por motivos de simplicidade e espaço.

Exemplo trabalhado


O código a seguir cria uma função escalar criptografada de teste:
CREATE FUNCTION dbo.FS()
RETURNS varchar(255)
WITH ENCRYPTION, SCHEMABINDING AS
BEGIN
    RETURN 
    (
        SELECT 'My code is so awesome is needs to be encrypted!'
    );
END;

A implementação completa de descriptografia está abaixo. O único parâmetro que precisa ser alterado para funcionar com outros objetos é o valor inicial de @objectid definido no primeiro DECLARE demonstração.
-- *** DAC connection required! ***
-- Make sure the target database is the context
USE Sandpit;
 
DECLARE
    -- Note: OBJECT_ID only works for schema-scoped objects
    @objectid integer = OBJECT_ID(N'dbo.FS', N'FN'),
    @family_guid binary(16),
    @objid binary(4),
    @subobjid binary(2),
    @imageval varbinary(MAX),
    @RC4key binary(20);
 
-- Find the database family GUID
SELECT @family_guid = CONVERT(binary(16), DRS.family_guid)
FROM sys.database_recovery_status AS DRS
WHERE DRS.database_id = DB_ID();
 
-- Convert object ID to little-endian binary(4)
SET @objid = CONVERT(binary(4), REVERSE(CONVERT(binary(4), @objectid)));
 
SELECT
    -- Read the encrypted value
    @imageval = SOV.imageval,
    -- Get the subobjid and convert to little-endian binary
    @subobjid = CONVERT(binary(2), REVERSE(CONVERT(binary(2), SOV.subobjid)))
FROM sys.sysobjvalues AS SOV
WHERE 
    SOV.[objid] = @objectid
    AND SOV.valclass = 1;
 
-- Compute the RC4 initialization key
SET @RC4key = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
 
-- Apply the standard RC4 algorithm and
-- convert the result back to nvarchar
PRINT CONVERT
    (
        nvarchar(MAX),
        dbo.fnEncDecRc4
        (
            @RC4key,
            @imageval
        )
    );

Observe a conversão final para nvarchar porque o texto do módulo é digitado como nvarchar(max) .

A saída é:


Conclusão


As razões pelas quais o método descrito na introdução funciona são:
  • O SQL Server usa a cifra de fluxo RC4 para reversivelmente exclusivo ou o texto de origem.
  • A chave RC4 depende apenas do guid da família de banco de dados, id do objeto e subobjid.
  • Substituir temporariamente o texto do módulo significa que a mesma chave RC4 (com hash SHA-1) é gerada.
  • Com a mesma chave, o mesmo fluxo RC4 é gerado, permitindo a descriptografia ou exclusividade.

Os usuários que não têm acesso a tabelas do sistema, arquivos de banco de dados ou outro acesso em nível de administrador não podem recuperar o texto do módulo criptografado. Como o próprio SQL Server precisa ser capaz de descriptografar o módulo, não há como impedir que usuários com privilégios adequados façam o mesmo.