Quản trị viên SQL Server khá dễ dàng khôi phục văn bản của các thủ tục, dạng xem, chức năng và trình kích hoạt được lưu trữ được bảo vệ bằng cách sử dụng WITH ENCRYPTION
. Nhiều bài báo đã được viết về điều này và một số công cụ thương mại có sẵn. Sơ lược cơ bản của phương pháp chung là:
- Lấy biểu mẫu được mã hóa (A) bằng Kết nối Quản trị viên Chuyên dụng.
- Bắt đầu giao dịch.
- Thay thế định nghĩa đối tượng bằng văn bản đã biết (B) có độ dài ít nhất bằng với văn bản gốc.
- Lấy biểu mẫu được mã hóa cho văn bản đã biết (C).
- Quay lại giao dịch để giữ nguyên đối tượng mục tiêu ở trạng thái ban đầu.
- Lấy bản gốc không được mã hóa bằng cách áp dụng một ký tự riêng-hoặc cho từng ký tự:
A XOR (B XOR C)
Đó là tất cả khá đơn giản, nhưng có vẻ hơi giống phép thuật:Nó không giải thích nhiều về cách thức và lý do tại sao nó hoạt động . Bài viết này đề cập đến khía cạnh đó đối với những người bạn thấy những loại chi tiết này thú vị và cung cấp một phương pháp thay thế để giải mã mang tính minh họa rõ hơn về quy trình.
Mật mã luồng
Thuật toán mã hóa cơ bản SQL Server sử dụng để mã hóa mô-đun là mật mã dòng RC4 ™. Sơ lược về quy trình mã hóa là:
- Khởi tạo mật mã RC4 bằng khóa mật mã.
- Tạo một dòng byte giả ngẫu nhiên.
- Kết hợp văn bản thuần túy của mô-đun với luồng byte bằng cách sử dụng độc quyền-hoặc.
Chúng ta có thể thấy quá trình này xảy ra bằng cách sử dụng trình gỡ lỗi và các ký hiệu công khai. Ví dụ:dấu vết ngăn xếp bên dưới cho thấy Máy chủ SQL đang khởi tạo khóa RC4 trong khi chuẩn bị mã hóa văn bản mô-đun:
Phần tiếp theo này hiển thị SQL Server mã hóa văn bản bằng luồng byte giả ngẫu nhiên RC4:
Giống như hầu hết các mật mã luồng, quá trình giải mã cũng giống như mã hóa, sử dụng thực tế là độc quyền-hoặc có thể đảo ngược (A XOR B XOR B = A
).
Việc sử dụng mật mã luồng là lý do độc quyền-hoặc được sử dụng trong phương pháp được mô tả ở đầu bài viết. Vốn dĩ không có gì không an toàn khi sử dụng độc quyền-hoặc, với điều kiện là sử dụng phương pháp mã hóa an toàn, khóa khởi tạo được giữ bí mật và khóa không được sử dụng lại.
RC4 không phải là đặc biệt mạnh, nhưng đó không phải là vấn đề chính ở đây. Điều đó nói rằng, cần lưu ý rằng mã hóa sử dụng RC4 đang dần bị xóa khỏi SQL Server và không được dùng nữa (hoặc bị vô hiệu hóa, tùy thuộc vào phiên bản và mức độ tương thích cơ sở dữ liệu) cho các hoạt động của người dùng như tạo khóa đối xứng.
Khóa khởi tạo RC4
SQL Server sử dụng ba phần thông tin để tạo khóa được sử dụng để khởi tạo mật mã dòng RC4:
- Họ cơ sở dữ liệu GUID.
Bạn có thể lấy điều này dễ dàng nhất bằng cách truy vấn sys.database_recovery_status . Nó cũng hiển thị trong các lệnh không có tài liệu như
DBCC DBINFO
vàDBCC DBTABLE
. - ID đối tượng của mô-đun đích.
Đây chỉ là ID đối tượng quen thuộc. Lưu ý rằng không phải tất cả các mô-đun cho phép mã hóa đều thuộc phạm vi lược đồ. Bạn sẽ cần sử dụng chế độ xem siêu dữ liệu ( sys.triggers hoặc sys.server_triggers ) để lấy ID đối tượng cho trình kích hoạt DDL và phạm vi máy chủ, thay vì sys.objects hoặc
OBJECT_ID
, vì chúng chỉ hoạt động với các đối tượng trong phạm vi lược đồ. - ID đối tượng phụ của mô-đun đích.
Đây là số thủ tục cho các thủ tục được lưu trữ được đánh số. Nó là 1 cho một thủ tục được lưu trữ không đánh số và bằng 0 trong tất cả các trường hợp khác.
Sử dụng lại trình gỡ lỗi, chúng ta có thể thấy họ GUID được truy xuất trong quá trình khởi tạo khóa:
Họ cơ sở dữ liệu GUID được nhập là uniqueidentifier , ID đối tượng là số nguyên và ID đối tượng phụ là smallint .
Mỗi phần của khóa phải được chuyển đổi sang một định dạng nhị phân cụ thể. Đối với GUID họ cơ sở dữ liệu, chuyển đổi uniqueidentifier nhập vào nhị phân (16) tạo ra biểu diễn nhị phân chính xác. Hai ID phải được chuyển đổi thành nhị phân trong biểu diễn little-endian (byte đầu tiên ít quan trọng nhất).
Lưu ý: Hãy rất cẩn thận để không vô tình cung cấp GUID dưới dạng một chuỗi! Nó phải được nhập uniqueidentifier .
Đoạn mã bên dưới hiển thị các hoạt động chuyển đổi chính xác cho một số giá trị mẫu:
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)));
Bước cuối cùng để tạo khóa khởi tạo RC4 là nối ba giá trị nhị phân ở trên thành một nhị phân duy nhất (22) và tính hàm băm SHA-1 của kết quả:
DECLARE @RC4key binary(20) = HASHBYTES('SHA1', @family_guid + @objid + @subobjid);
Đối với dữ liệu mẫu được cung cấp ở trên, khóa khởi tạo cuối cùng là:
0x6C914908E041A08DD8766A0CFEDC113585D69AF8
Khó có thể nhìn thấy sự đóng góp của ID đối tượng của mô-đun đích và ID đối tượng phụ vào hàm băm SHA-1 trong một ảnh chụp màn hình trình gỡ lỗi duy nhất, nhưng người đọc quan tâm có thể tham khảo cách tháo gỡ một phần của initspkey dưới đây:
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 và SHAUpdate lệnh gọi thêm các thành phần vào hàm băm SHA, cuối cùng được tính toán bằng lệnh gọi tới SHAFinal .
SHAInit cuộc gọi đóng góp 10h byte (16 thập phân) được lưu trữ ở [rsp + 40h], là HƯỚNG DẪN gia đình . SHAUpdate đầu tiên lệnh gọi thêm 4 byte (như được chỉ ra trong thanh ghi r8d), được lưu trữ tại [rsp + 24h], là đối tượng TÔI. SHAUpdate thứ hai lệnh gọi thêm 2 byte, được lưu trữ tại [rsp + 20h], là subobjid .
Các hướng dẫn cuối cùng chuyển hàm băm SHA-1 được tính toán tới quy trình khởi tạo khóa RC4 rc4_key . Độ dài của băm được lưu trữ trong thanh ghi edx:14h (20 thập phân) byte, là độ dài băm được xác định cho SHA và SHA-1 (160 bit).
Triển khai RC4
Thuật toán RC4 cốt lõi nổi tiếng và tương đối đơn giản. Nó sẽ được triển khai tốt hơn bằng ngôn ngữ .Net vì lý do hiệu quả và hiệu suất, nhưng có một triển khai T-SQL bên dưới.
Hai hàm T-SQL này triển khai thuật toán lập lịch khóa RC4 và trình tạo số giả ngẫu nhiên, và ban đầu được viết bởi SQL Server MVP Peter Larsson. Tôi đã thực hiện một số sửa đổi nhỏ để cải thiện một chút hiệu suất và cho phép mã hóa và giải mã các tệp nhị phân có độ dài LOB. Phần này của quy trình có thể được thay thế bằng bất kỳ triển khai RC4 tiêu chuẩn nà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
Văn bản mô-đun được mã hóa
Cách dễ nhất để quản trị viên SQL Server có được điều này là đọc varbinary (max) giá trị được lưu trữ trong imageval cột sys.sysobjvalues , chỉ có thể truy cập thông qua Kết nối quản trị viên chuyên dụng (DAC).
Đây là ý tưởng giống với phương pháp thông thường được mô tả trong phần giới thiệu, mặc dù chúng tôi thêm bộ lọc trên valclass =1. Bảng nội bộ này cũng là một nơi thuận tiện để lấy subobjid . Nếu không, chúng tôi sẽ cần kiểm tra sys.numbered_procedures khi đối tượng đích là một thủ tục, hãy sử dụng 1 cho một thủ tục không được đánh số hoặc bằng 0 cho bất kỳ thứ gì khác, như đã mô tả trước đây.
Có thể tránh sử dụng DAC bằng cách đọc imageval từ sys.sysobjvalues trực tiếp, sử dụng nhiều DBCC PAGE
cuộc gọi. Điều này liên quan đến công việc nhiều hơn một chút để xác định vị trí các trang từ siêu dữ liệu, hãy làm theo imageval Chuỗi LOB và đọc dữ liệu nhị phân mục tiêu từ mỗi trang. Bước thứ hai dễ thực hiện hơn rất nhiều bằng một ngôn ngữ lập trình khác ngoài T-SQL. Lưu ý rằng DBCC PAGE
sẽ hoạt động, mặc dù đối tượng cơ sở thường không thể đọc được từ kết nối không phải DAC. Nếu trang không có trong bộ nhớ, nó sẽ được đọc từ bộ nhớ liên tục như bình thường.
Nỗ lực bổ sung để tránh yêu cầu DAC được đền đáp bằng cách cho phép nhiều người dùng sử dụng đồng thời quá trình giải mã. Tôi sẽ sử dụng phương pháp DAC trong bài viết này vì lý do đơn giản và không gian.
Ví dụ về Công việc
Đoạn mã sau tạo một hàm vô hướng được mã hóa thử nghiệm:
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;
Dưới đây là phần triển khai giải mã hoàn chỉnh. Tham số duy nhất cần thay đổi để hoạt động cho các đối tượng khác là giá trị ban đầu của @objectid
đặt trong DECLARE
đầu tiên tuyên bố.
-- *** 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 ) );
Lưu ý chuyển đổi cuối cùng thành nvarchar vì văn bản mô-đun được nhập là nvarchar (max) .
Đầu ra là:
Kết luận
Các lý do mà phương pháp được mô tả trong phần giới thiệu hoạt động là:
- SQL Server sử dụng mật mã dòng RC4 để đảo ngược loại trừ-hoặc văn bản nguồn.
- Khóa RC4 chỉ phụ thuộc vào hướng dẫn họ cơ sở dữ liệu, id đối tượng và subobjid.
- Tạm thời thay thế văn bản mô-đun có nghĩa là khóa RC4 tương tự (SHA-1 được băm) được tạo.
- Với cùng một khóa, cùng một luồng RC4 được tạo, cho phép giải mã hoặc độc quyền.
Người dùng không có quyền truy cập vào bảng hệ thống, tệp cơ sở dữ liệu hoặc quyền truy cập cấp quản trị khác, không thể truy xuất văn bản mô-đun được mã hóa. Vì bản thân SQL Server cần có khả năng giải mã mô-đun, nên không có cách nào để ngăn người dùng có đặc quyền phù hợp làm điều tương tự.