SQL Server管理者は、WITH ENCRYPTION
を使用して保護されたストアドプロシージャ、ビュー、関数、およびトリガーのテキストを復元するのは非常に簡単です。 。これについては多くの記事が書かれており、いくつかの商用ツールが利用可能です。一般的な方法の基本的な概要は次のとおりです。
- 専用管理者接続を使用して暗号化されたフォーム(A)を取得します。
- トランザクションを開始します。
- オブジェクト定義を、少なくとも元のテキストと同じ長さの既知のテキスト(B)に置き換えます。
- 既知のテキストの暗号化されたフォームを取得します(C)。
- トランザクションをロールバックして、ターゲットオブジェクトを初期状態のままにします。
- 排他的論理和を各文字に適用して、暗号化されていないオリジナルを取得します:
A XOR (B XOR C)
これはすべて非常に簡単ですが、魔法のように見えます。どのように、なぜ機能するのかについてはあまり説明されていません。 。この記事では、この種の詳細が興味深いと感じた方のためにその側面を取り上げ、プロセスをより詳しく説明する復号化の代替方法を提供します。
ストリーム暗号
SQLServerがモジュール暗号化に使用する基盤となる暗号化アルゴリズム RC4™ストリーム暗号です。暗号化プロセスの概要は次のとおりです。
- 暗号化キーを使用してRC4暗号を初期化します。
- バイトの疑似ランダムストリームを生成します。
- 排他的論理和を使用して、モジュールのプレーンテキストをバイトストリームと結合します。
このプロセスは、デバッガーとパブリックシンボルを使用して発生していることがわかります。たとえば、以下のスタックトレースは、モジュールテキストの暗号化の準備中にSQLServerがRC4キーを初期化することを示しています。
次の例は、SQLServerがRC4疑似ランダムバイトストリームを使用してテキストを暗号化する方法を示しています。
ほとんどのストリーム暗号と同様に、復号化のプロセスは暗号化と同じであり、排他的論理和(A XOR B XOR B = A
)という事実を利用します。 。
ストリーム暗号の使用が排他的論理和の理由です 記事の冒頭で説明した方法で使用されます。排他的論理和を使用することについて本質的に危険なことは何もありません。または、安全な暗号化方式が使用され、初期化キーが秘密にされ、キーが再利用されない場合に限ります。
RC4は特に強力ではありませんが、それはここでの主要な問題ではありません。とはいえ、RC4を使用した暗号化はSQL Serverから徐々に削除されており、対称鍵の作成などのユーザー操作では非推奨になっています(バージョンやデータベースの互換性レベルによっては無効になっています)。
RC4初期化キー
SQL Serverは、3つの情報を使用して、RC4ストリーム暗号の初期化に使用されるキーを生成します。
- データベースファミリGUID。
これは、 sys.database_recovery_statusにクエリを実行することで最も簡単に取得できます。 。
DBCC DBINFO
などの文書化されていないコマンドでも表示されます およびDBCC DBTABLE
。 - ターゲットモジュールのオブジェクトID。
これはおなじみのオブジェクトIDです。暗号化を許可するすべてのモジュールがスキーマスコープであるとは限らないことに注意してください。メタデータビュー( sys.triggers )を使用する必要があります またはsys.server_triggers ) sys.objects ではなく、DDLおよびサーバースコープのトリガーのオブジェクトIDを取得する または
OBJECT_ID
、これらはスキーマスコープのオブジェクトでのみ機能するためです。 - ターゲットモジュールのサブオブジェクトID。
これは、番号付きストアドプロシージャのプロシージャ番号です。番号のないストアドプロシージャの場合は1、それ以外の場合は0です。
デバッガーを再度使用すると、キーの初期化中にファミリーGUIDが取得されていることがわかります。
データベースファミリGUIDは、 uniqueidentifierと入力されます。 、オブジェクトIDは integer 、サブオブジェクトIDは smallint 。
キーの各部分は必須 特定のバイナリ形式に変換されます。データベースファミリGUIDの場合、 uniqueidentifierを変換します binary(16)と入力します 正しいバイナリ表現を生成します。 2つのIDは、リトルエンディアン表現でバイナリに変換する必要があります(最下位バイトが最初)。
注: 誤ってGUIDを文字列として提供しないように十分注意してください。 uniqueidentifierと入力する必要があります 。
以下のコードスニペットは、いくつかのサンプル値の正しい変換操作を示しています。
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)));
RC4初期化キーを生成する最後のステップは、上記の3つのバイナリ値を1つのbinary(22)に連結し、結果のSHA-1ハッシュを計算することです。
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
上記のサンプルデータの場合、最終的な初期化キーは次のとおりです。
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
ターゲットモジュールのオブジェクトIDとサブオブジェクトIDのSHA-1ハッシュへの寄与は、単一のデバッガーのスクリーンショットではわかりにくいですが、興味のある読者は、 initspkeyの一部の分解を参照できます。 以下:
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 およびSHAUpdate 呼び出しは、SHAハッシュにコンポーネントを追加します。これは、最終的に SHAFinalの呼び出しによって計算されます。 。
SHAInit 呼び出しは、ファミリGUIDである[rsp+40h]に格納されている10hバイト(10進数で16)を提供します 。最初のSHAUpdate callは、オブジェクトである[rsp+ 24h]に格納されている4バイトを追加します(r8dレジスタに示されています)。 ID。 2番目のSHAUpdate callは、 subobjidである[rsp+20h]に格納されている2バイトを追加します 。
最後の命令は、計算されたSHA-1ハッシュをRC4キー初期化ルーチン rc4_keyに渡します。 。ハッシュの長さはレジスタedxに格納されます:14h(10進数の20)バイト。これは、SHAおよびSHA-1(160ビット)に対して定義されたハッシュ長です。
RC4の実装
コアRC4アルゴリズムはよく知られており、比較的単純です。効率とパフォーマンスの理由から、.Net言語で実装する方が適切ですが、以下にT-SQLの実装があります。
これらの2つのT-SQL関数は、RC4キースケジューリングアルゴリズムと疑似乱数ジェネレーターを実装し、元々はSQLServerMVPのPeterLarssonによって作成されました。パフォーマンスを少し改善し、LOB長のバイナリをエンコードおよびデコードできるようにするために、いくつかの小さな変更を加えました。プロセスのこの部分は、標準の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
暗号化されたモジュールテキスト
SQL Server管理者がこれを取得する最も簡単な方法は、 varbinary(max)を読み取ることです。 imagevalに保存されている値 sys.sysobjvaluesの列 、専用管理者接続(DAC)を介してのみアクセスできます。
これは、導入部で説明したルーチンメソッドと同じ考え方ですが、 valclassにフィルターを追加します。 =1.この内部テーブルは、 subobjidを取得するのにも便利な場所です。 。それ以外の場合は、 sys.numbered_proceduresを確認する必要があります。 ターゲットオブジェクトがプロシージャの場合、前述のように、番号のないプロシージャには1を使用し、それ以外の場合は0を使用します。
DACの使用を避けることは可能です imagevalを読む sys.sysobjvaluesから 直接、複数のDBCC PAGE
を使用 呼び出します。これには、メタデータからページを見つけるためのもう少し作業が必要です。 imageval に従ってください LOBチェーン、および各ページからターゲットバイナリデータを読み取ります。後者の手順は、T-SQL以外のプログラミング言語で行う方がはるかに簡単です。 DBCC PAGE
に注意してください ベースオブジェクトは通常、DAC以外の接続から読み取ることはできませんが、機能します。ページがメモリにない場合は、通常どおり永続ストレージから読み込まれます。
DAC要件を回避するための余分な努力は、複数のユーザーが復号化プロセスを同時に使用できるようにすることで報われます。単純さとスペースの理由から、この記事ではDACアプローチを使用します。
実例
次のコードは、テスト暗号化スカラー関数を作成します。
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;
完全な復号化の実装は以下のとおりです。他のオブジェクトで機能するために変更する必要がある唯一のパラメーターは、@objectid
の初期値です。 最初のDECLARE
で設定 ステートメント。
-- *** 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 ) );に変換し直します。
nvarcharへの最終的な変換に注意してください モジュールテキストはnvarchar(max)と入力されているため 。
出力は次のとおりです。
結論
紹介で説明した方法が機能する理由は次のとおりです。
- SQL Serverは、RC4ストリーム暗号を使用して、可逆的に排他的、またはソーステキストを使用します。
- RC4キーは、データベースファミリのGUID、オブジェクトID、およびサブオブジェクトにのみ依存します。
- モジュールテキストを一時的に置き換えると、同じ(SHA-1ハッシュ)RC4キーが生成されます。
- 同じキーを使用して、同じRC4ストリームが生成され、排他的論理和または復号化が可能になります。
システムテーブル、データベースファイル、またはその他の管理者レベルのアクセス権を持たないユーザーは、暗号化されたモジュールテキストを取得できません。 SQL Server自体がモジュールを復号化できる必要があるため、適切な特権を持つユーザーが同じことを行うのを防ぐ方法はありません。