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:
- Získejte zašifrovaný formulář (A) pomocí připojení Dedicated Administrator Connection.
- Zahajte transakci.
- Nahraďte definici objektu známým textem (B), který má alespoň stejnou délku jako originál.
- Získejte zašifrovanou formu známého textu (C).
- Vraťte transakci zpět a ponechte cílový objekt v původním stavu.
- 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:
- Inicializujte šifru RC4 pomocí kryptografického klíče.
- Vygenerujte pseudonáhodný proud bajtů.
- 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:
- 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 DBINFOaDBCC DBTABLE. - 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. - 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 https://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éž.