sql >> Databáze >  >> RDS >> Database

Vnitřnosti WITH ENCRYPTION

Pro administrátora SQL Serveru je velmi snadné obnovit text uložených procedur, pohledů, funkcí a spouštěčů chráněných pomocí WITH ENCRYPTION . O tom bylo napsáno mnoho článků a je k dispozici několik komerčních nástrojů. Základní náčrt běžné metody je:

  1. Získejte zašifrovaný formulář (A) pomocí připojení Dedicated Administrator Connection.
  2. Zahajte transakci.
  3. Nahraďte definici objektu známým textem (B), který má alespoň stejnou délku jako originál.
  4. Získejte zašifrovanou formu známého textu (C).
  5. Vraťte transakci zpět a ponechte cílový objekt v původním stavu.
  6. Získejte nezašifrovaný originál použitím exkluzivního znaku nebo na každý znak:A XOR (B XOR C)

To vše je docela jednoduché, ale vypadá to trochu jako kouzlo:nevysvětluje to moc o tom, jak a proč to funguje . Tento článek pokrývá tento aspekt pro ty z vás, kteří považují tyto druhy detailů za zajímavé, a poskytuje alternativní metodu dešifrování, která tento proces více ilustruje.

Streamová šifra

Základní šifrovací algoritmus SQL Server používá pro šifrování modulu je proudová šifra RC4™. Nástin procesu šifrování je:

  1. Inicializujte šifru RC4 pomocí kryptografického klíče.
  2. Vygenerujte pseudonáhodný proud bajtů.
  3. Zkombinujte prostý text modulu s byte streamem pomocí exkluzivního-nebo.

Tento proces můžeme vidět pomocí debuggeru a veřejných symbolů. Například trasování zásobníku níže ukazuje, že SQL Server inicializuje klíč RC4 při přípravě na šifrování textu modulu:

Tento další ukazuje, jak SQL Server šifruje text pomocí pseudonáhodného byte streamu RC4:

Stejně jako většina proudových šifer je proces dešifrování stejný jako šifrování, přičemž se využívá skutečnosti, že výhradní nebo je reverzibilní (A XOR B XOR B = A ).

Důvodem exclusive-or je použití proudové šifry se používá v metodě popsané na začátku článku. Na použití výhradního nebo za předpokladu, že je použita zabezpečená metoda šifrování, je inicializační klíč udržován v tajnosti a klíč není znovu použit, není nic nebezpečného.

RC4 není nijak zvlášť silný, ale to zde není hlavní problém. Nicméně stojí za zmínku, že šifrování pomocí RC4 je postupně odstraňováno z SQL Serveru a je zastaralé (nebo zakázáno, v závislosti na verzi a úrovni kompatibility databáze) pro uživatelské operace, jako je vytváření symetrického klíče.

Inicializační klíč RC4

SQL Server používá ke generování klíče použitého k inicializaci proudové šifry RC4 tři informace:

  1. Identifikátor GUID databázové rodiny.

    To lze nejsnáze získat dotazem na sys.database_recovery_status . Je také vidět v nezdokumentovaných příkazech, jako je DBCC DBINFO a DBCC DBTABLE .

  2. ID objektu cílového modulu.

    Toto je pouze známé ID objektu. Všimněte si, že ne všechny moduly, které umožňují šifrování, jsou omezeny na schéma. Budete muset použít zobrazení metadat (sys.triggers nebo sys.server_triggers ), abyste získali ID objektu pro DDL a spouštěče v rozsahu serveru, nikoli sys.objects nebo OBJECT_ID , protože tyto fungují pouze s objekty v rozsahu schématu.

  3. ID dílčího objektu cílového modulu.

    Toto je číslo procedury pro číslované uložené procedury. Je 1 pro nečíslovanou uloženou proceduru a nula ve všech ostatních případech.

Opětovným použitím ladicího programu můžeme vidět, jak se GUID rodiny načítá během inicializace klíče:

Typ GUID databázové rodiny je uniqueidentifier , ID objektu je celé číslo a ID dílčího objektu je smallint .

Každá část klíče musí převést do určitého binárního formátu. Pro GUID databázové rodiny převod uniqueidentifier zadejte do binary(16) vytváří správnou binární reprezentaci. Tato dvě ID musí být převedena na binární v reprezentaci little-endian (nejméně významný bajt jako první).

Poznámka: Buďte velmi opatrní, abyste omylem nezadali GUID jako řetězec! Musí být zadán uniqueidentifier .

Níže uvedený fragment kódu ukazuje správné převodní operace pro některé vzorové hodnoty:

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

Posledním krokem k vygenerování inicializačního klíče RC4 je zřetězení tří výše uvedených binárních hodnot do jediné binární (22) a výpočet SHA-1 hash výsledku:

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

Pro ukázková data uvedená výše je konečný inicializační klíč:

0x6C914908E041A08DD8766A0CFEDC113585D69AF8

Příspěvek ID objektu cílového modulu a ID dílčího objektu k hash SHA-1 je těžké vidět na jediném snímku obrazovky ladicího programu, ale čtenář, který má zájem, se může odkázat na rozebrání části initspkey. níže:

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)

SHAInit a SHAUpdate volání přidávají komponenty do hash SHA, který je nakonec vypočítán voláním SHAFinal .

SHAInit volání přispívá 10h bajtů (16 dekadických) uložených na [rsp+40h], což je family GUID . První SHAUpdate volání přidá 4 bajty (jak je uvedeno v registru r8d), uložené na [rsp+24h], což je objekt ID. Druhá SHAUpdate volání přidá 2 bajty, uložené na [rsp+20h], což je subobjid .

Poslední instrukce předají vypočítaný hash SHA-1 inicializační rutině klíče RC4 rc4_key . Délka hashe je uložena v registru edx:14h (20 dekadických) bajtů, což je definovaná délka hashe pro SHA a SHA-1 (160 bitů).

Implementace RC4

Základní algoritmus RC4 je dobře známý a relativně jednoduchý. Z důvodů efektivity a výkonu by bylo lepší implementovat v jazyce .Net, ale níže je implementace T-SQL.

Tyto dvě funkce T-SQL implementují algoritmus plánování klíčů RC4 a generátor pseudonáhodných čísel a původně je napsal Peter Larsson, MVP SQL Server. Udělal jsem několik drobných úprav, abych trochu zlepšil výkon a umožnil kódování a dekódování binárních souborů délky LOB. Tuto část procesu lze nahradit jakoukoli standardní implementací RC4.

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

Text šifrovaného modulu

Nejjednodušší způsob, jak to administrátor SQL Server získat, je přečíst si varbinary(max) hodnota uložená v imageval sloupec sys.sys.sysobjvalues , která je přístupná pouze prostřednictvím připojení Dedicated Administrator Connection (DAC).

Toto je stejný nápad jako rutinní metoda popsaná v úvodu, i když jsme přidali filtr na valclass =1. Tato interní tabulka je také vhodným místem pro získání subobjid . Jinak bychom museli zkontrolovat sys.numbered_procedures když je cílovým objektem procedura, použijte 1 pro nečíslovanou proceduru nebo nulu pro cokoli jiného, ​​jak bylo popsáno dříve.

Je možné vyhnout se použití DAC přečtením imagevalu z sys.sysobjvalues přímo pomocí více DBCC PAGE hovory. To vyžaduje trochu více práce s vyhledáním stránek z metadat, postupujte podle imageval řetězec LOB a číst cílová binární data z každé stránky. Poslední krok je mnohem snazší provést v jiném programovacím jazyce než T-SQL. Všimněte si, že DBCC PAGE bude fungovat, i když základní objekt není normálně čitelný z připojení bez DAC. Pokud stránka není v paměti, bude načtena z trvalého úložiště jako obvykle.

Zvláštní úsilí vyhnout se požadavku DAC se vyplácí tím, že umožňuje více uživatelům používat proces dešifrování současně. Z důvodu jednoduchosti a prostoru použiji v tomto článku přístup DAC.

Zpracovaný příklad

Následující kód vytvoří testovací šifrovanou skalární funkci:

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;

Kompletní implementace dešifrování je uvedena níže. Jediný parametr, který je třeba změnit, aby fungoval pro jiné objekty, je počáteční hodnota @objectid nastavte v prvním DECLARE prohlášení.

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

Poznamenejte si konečný převod na nvarchar protože text modulu je zadán jako nvarchar(max) .

Výstup je:

Závěr

Důvody, proč metoda popsaná v úvodu funguje, jsou:

  • SQL Server používá proudovou šifru RC4 k reverzibilní exkluzi – neboli zdrojového textu.
  • Klíč RC4 závisí pouze na guid databázové rodiny, id objektu a subobjid.
  • Dočasné nahrazení textu modulu znamená, že se vygeneruje stejný klíč RC4 (s hash SHA-1).
  • Se stejným klíčem se generuje stejný stream RC4, což umožňuje exkluzivní nebo dešifrování.

Uživatelé, kteří nemají přístup k systémovým tabulkám, databázovým souborům nebo jiným přístupům na úrovni správce, nemohou načíst zašifrovaný text modulu. Vzhledem k tomu, že samotný SQL Server musí být schopen modul dešifrovat, neexistuje způsob, jak zabránit vhodně privilegovaným uživatelům, aby udělali totéž.


  1. Jak GROUP BY funguje?

  2. Připravené prohlášení o Postgresql in Rails

  3. Nelze načíst ID posledního vloženého řádku v režimu spánku pomocí Oracle

  4. Jak nainstalovat a nakonfigurovat phpMyAdmin na CentOS 6