sql >> Base de Datos >  >> RDS >> Database

Los aspectos internos de CON ENCRIPTACIÓN

Es bastante fácil para un administrador de SQL Server recuperar el texto de los procedimientos almacenados, las vistas, las funciones y los disparadores protegidos mediante WITH ENCRYPTION . Se han escrito muchos artículos sobre esto y hay varias herramientas comerciales disponibles. El esquema básico del método común es:

  1. Obtenga el formulario cifrado (A) mediante la conexión de administrador dedicada.
  2. Inicie una transacción.
  3. Reemplace la definición del objeto con texto conocido (B) de al menos la misma longitud que el original.
  4. Obtenga la forma encriptada para el texto conocido (C).
  5. Revertir la transacción para dejar el objeto de destino en su estado inicial.
  6. Obtenga el original sin cifrar aplicando un o exclusivo a cada carácter:A XOR (B XOR C)

Todo eso es bastante sencillo, pero parece un poco mágico:no explica mucho sobre cómo y por qué funciona . Este artículo cubre ese aspecto para aquellos de ustedes que encuentran este tipo de detalles interesantes y proporciona un método alternativo para el descifrado que es más ilustrativo del proceso.

El cifrado de flujo

El algoritmo de cifrado subyacente que usa SQL Server para cifrado de módulo es el cifrado de flujo RC4™. Un resumen del proceso de encriptación es:

  1. Inicialice el cifrado RC4 con una clave criptográfica.
  2. Genera un flujo pseudoaleatorio de bytes.
  3. Combine el texto sin formato del módulo con el flujo de bytes utilizando o exclusivo.

Podemos ver que este proceso ocurre usando un depurador y símbolos públicos. Por ejemplo, el seguimiento de la pila a continuación muestra que SQL Server inicializa la clave RC4 mientras se prepara para cifrar el texto del módulo:

El siguiente muestra SQL Server encriptando el texto usando el flujo de bytes pseudoaleatorio RC4:

Como la mayoría de los cifrados de flujo, el proceso de descifrado es el mismo que el de cifrado, aprovechando el hecho de que o exclusivo es reversible (A XOR B XOR B = A ).

El uso de un cifrado de flujo es la razón exclusive-or se utiliza en el método descrito al principio del artículo. No hay nada intrínsecamente peligroso en el uso exclusivo o, siempre que se utilice un método de cifrado seguro, la clave de inicialización se mantenga en secreto y no se reutilice.

RC4 no es particularmente fuerte, pero ese no es el problema principal aquí. Dicho esto, vale la pena señalar que el cifrado con RC4 se está eliminando gradualmente de SQL Server y está obsoleto (o deshabilitado, según la versión y el nivel de compatibilidad de la base de datos) para operaciones de usuario como la creación de una clave simétrica.

La clave de inicialización RC4

SQL Server utiliza tres piezas de información para generar la clave utilizada para inicializar el cifrado de flujo RC4:

  1. El GUID de la familia de bases de datos.

    Esto se puede obtener más fácilmente consultando sys.database_recovery_status . También es visible en comandos no documentados como DBCC DBINFO y DBCC DBTABLE .

  2. El ID de objeto del módulo de destino.

    Esta es solo la identificación del objeto familiar. Tenga en cuenta que no todos los módulos que permiten el cifrado tienen un ámbito de esquema. Deberá usar vistas de metadatos (sys.triggers o sys.server_triggers ) para obtener el Id. de objeto para DDL y activadores en el ámbito del servidor, en lugar de sys.objects o OBJECT_ID , ya que estos solo funcionan con objetos de ámbito de esquema.

  3. El ID del subobjeto del módulo de destino.

    Este es el número de procedimiento para los procedimientos almacenados numerados. Es 1 para un procedimiento almacenado no numerado y cero en todos los demás casos.

Usando el depurador nuevamente, podemos ver que el GUID de la familia se recupera durante la inicialización de la clave:

El GUID de la familia de la base de datos se escribe identificador único , el ID del objeto es entero y el ID del subobjeto es smallint .

Cada parte de la clave debe convertirse a un formato binario específico. Para el GUID de la familia de base de datos, convertir el identificador único escriba a binary(16) produce la representación binaria correcta. Los dos ID deben convertirse a binario en representación little-endian (el byte menos significativo primero).

Nota: ¡Tenga mucho cuidado de no proporcionar accidentalmente el GUID como una cadena! Debe escribirse identificador único .

El fragmento de código a continuación muestra las operaciones de conversión correctas para algunos valores de muestra:

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)));

El paso final para generar la clave de inicialización RC4 es concatenar los tres valores binarios anteriores en un solo binario (22) y calcular el hash SHA-1 del resultado:

DECLARE 
    @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);

Para los datos de muestra dados anteriormente, la clave de inicialización final es:

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

La contribución del ID de objeto y del subobjeto del módulo de destino al hash SHA-1 es difícil de ver en una sola captura de pantalla del depurador, pero el lector interesado puede consultar el desmontaje de una parte de initspkey a continuación:

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)

El SHAInit y Actualización SHA las llamadas agregan componentes al hash SHA, que finalmente se calcula mediante una llamada a SHAFinal .

El SHAInit la llamada aporta 10h bytes (16 decimales) almacenados en [rsp+40h], que es el GUID familiar . La primera SHAUpdate call agrega 4 bytes (como se indica en el registro r8d), almacenados en [rsp+24h], que es el objeto IDENTIFICACIÓN. La segunda SHAUpdate la llamada agrega 2 bytes, almacenados en [rsp+20h], que es el subobjid .

Las instrucciones finales pasan el hash SHA-1 calculado a la rutina de inicialización de clave RC4 rc4_key . La longitud del hash se almacena en el registro edx:14h (20 decimales) bytes, que es la longitud del hash definida para SHA y SHA-1 (160 bits).

La implementación de RC4

El algoritmo central RC4 es bien conocido y relativamente simple. Sería mejor implementarlo en un lenguaje .Net por razones de eficiencia y rendimiento, pero hay una implementación de T-SQL a continuación.

Estas dos funciones T-SQL implementan el algoritmo de programación de claves RC4 y el generador de números pseudoaleatorios, y fueron escritas originalmente por Peter Larsson, MVP de SQL Server. He realizado algunas modificaciones menores para mejorar un poco el rendimiento y permitir que se codifiquen y decodifiquen binarios de longitud LOB. Esta parte del proceso podría ser reemplazada por cualquier implementación RC4 estándar.

/*
** 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

El texto del módulo encriptado

La forma más fácil para que un administrador de SQL Server obtenga esto es leer el varbinary(max) valor almacenado en imageval columna de sys.sysobjvalues , al que solo se puede acceder a través de la Conexión de administrador dedicada (DAC).

Esta es la misma idea que el método de rutina descrito en la introducción, aunque agregamos un filtro en valclass =1. Esta tabla interna también es un lugar conveniente para obtener el subobjid . De lo contrario, tendríamos que verificar sys.numbered_procedures cuando el objeto de destino es un procedimiento, use 1 para un procedimiento no numerado o cero para cualquier otra cosa, como se describió anteriormente.

Es posible evitar usar el DAC leyendo el imageval de sys.sysobjvalues directamente, usando múltiples DBCC PAGE llamadas Esto implica un poco más de trabajo para ubicar las páginas a partir de los metadatos, siga el imageval cadena LOB y lea los datos binarios de destino de cada página. El último paso es mucho más fácil de realizar en un lenguaje de programación que no sea T-SQL. Tenga en cuenta que DBCC PAGE funcionará, aunque el objeto base normalmente no se pueda leer desde una conexión que no sea DAC. Si la página no está en la memoria, se leerá desde el almacenamiento persistente como de costumbre.

El esfuerzo adicional para evitar el requisito de DAC vale la pena al permitir que varios usuarios usen el proceso de descifrado al mismo tiempo. Usaré el enfoque DAC en este artículo por razones de simplicidad y espacio.

Ejemplo resuelto

El siguiente código crea una función escalar cifrada de prueba:

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;

La implementación completa del descifrado se encuentra a continuación. El único parámetro que debe cambiarse para que funcione con otros objetos es el valor inicial de @objectid establecido en el primer DECLARE declaración.

-- *** 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
        )
    );

Tenga en cuenta la conversión final a nvarchar porque el texto del módulo se escribe como nvarchar(max) .

La salida es:

Conclusión

Las razones por las que el método descrito en la introducción funciona son:

  • SQL Server utiliza el cifrado de flujo RC4 para excluir de forma reversible el texto de origen.
  • La clave RC4 depende únicamente del GUID de la familia de la base de datos, el ID del objeto y el subobjid.
  • Reemplazar temporalmente el texto del módulo significa que se genera la misma clave RC4 (hash SHA-1).
  • Con la misma clave, se genera el mismo flujo RC4, lo que permite el descifrado exclusivo.

Los usuarios que no tienen acceso a las tablas del sistema, los archivos de la base de datos u otro acceso de nivel de administrador no pueden recuperar el texto del módulo encriptado. Dado que el propio SQL Server necesita poder descifrar el módulo, no hay forma de evitar que los usuarios con privilegios adecuados hagan lo mismo.