SQL Server的數據加密簡介

背景

有時候,我們還真的會碰到這樣的需求:防止開發人員獲取到敏感數據。也許你覺得很簡單,把開發和運營分開不就可以了嗎?是的,如果公司有專門的運營團隊的話,但對於很多小公司來說,幾個人的開發團隊就兼顧了需求分析、設計、開發、測試、調試、部署和運營了,數據庫密碼知道,程序代碼全有,怎麼辦?——必須對數據庫裏的數據進行加密,這是唯一的辦法。

也許你還是不明白,什麼東西需要瞞着我們了不起的程序員,好吧,我直說了:工資!假如你的公司讓你做一個工資系統,你會不會有這方面的顧慮,一旦工資信息被公開,後果必定是很嚴重的,也許老闆對你很信任,認爲讓你知道沒什麼問題,但其他開發人員呢?後來接手你的工作的人呢?所以必須考慮這個問題。而且,還外帶一個需求:員工自己可以用自己的“薪資查看密碼”來查看自己的工資(只能看自己的),每個人自己的“薪資查看密碼”都不一樣。另外不需要描述的隱藏需求還有:將來必定是要對薪資做統計做報表的。

相關代碼

SQL Server(2005及之後的版本)提供了內置的加密機制,加密方式有兩大類,一類是對稱加密,另一類則是非對稱加密。

SQL Server的對稱加密示例代碼:

 

--創建一個對稱密鑰,其實只需要創建一次,不用每次都創建,這個對稱密鑰密碼爲123456(嗯,大多數人認爲的密碼),密碼是nvarchar類型的
CREATE SYMMETRIC KEY my_symetric_key WITH ALGORITHM = DESX ENCRYPTION BY PASSWORD = N'123456';

--使用一個對稱密鑰前必須打開它,而且要提供創建它時所使用的密碼,密碼不對的話就會打開失敗
OPEN SYMMETRIC KEY my_symetric_key DECRYPTION BY PASSWORD = N'123456';

--只能加密字符串,如果要加密數字,就用CONVERT函數先把數字轉爲字符串
DECLARE @strClearText NVARCHAR(100);
SET @strClearText = N'3000.00';

--密文類型爲VARBINARY,用
DECLARE @strCipherText VARBINARY(MAX);
SET @strCipherText = EncryptByKey(Key_GUID('my_symetric_key'), @strClearText);

--顯示密文(密文其實爲二進制格式,你會看到其HEX文本)
SELECT @strCipherText AS [密文];

--解密不需要提供密鑰名稱,SQL Server會根據當前上下文去尋找打開的對稱密鑰
DECLARE @strDecrypted VARBINARY(MAX);
SET @strDecrypted = DecryptByKey(@strCipherText);

--顯示出解密後的明文
SELECT Convert(NVARCHAR(100), @strDecrypted) AS [解密後的明文]

--關閉這個密鑰
CLOSE SYMMETRIC KEY my_symetric_key;

--以後還需要用這個密鑰的話就不用刪掉它
DROP SYMMETRIC KEY my_symetric_key;

 

SQL Server的非對稱加密示例代碼:

 

--創建一個非對稱密鑰(不用每次都創建),這個對稱密鑰密碼爲123456,使用RSA512算法,另外還有RSA1024和RSA2048,強度更高,可加密內容更長,密鑰生成速度也會慢不少,RSA512這裏足夠用了
CREATE ASYMMETRIC KEY my_asymetric_key WITH ALGORITHM = RSA_512 ENCRYPTION BY PASSWORD = N'123456';

DECLARE @strClearText NVARCHAR(100);
SET @strClearText = N'3000.00';

--加密,和對稱加密不一樣,不需要提供密碼,也不需要打開密鑰
DECLARE @strCipherText VARBINARY(MAX);
SET @strCipherText = EncryptByAsymKey(AsymKey_ID('my_asymetric_key'), @strClearText);

--顯示密文
SELECT @strCipherText AS [密文];

--解密,必須提供生成密鑰時候的密碼,密碼不正確的話就會出錯
--密鑰選擇不正確的話會得到NULL結果
DECLARE @strDecrypted VARBINARY(MAX);
SET @strDecrypted = DecryptByAsymKey(AsymKey_ID('my_asymetric_key'), @strCipherText, N'123456');

--顯示出解密後的明文
SELECT Convert(NVARCHAR(100), @strDecrypted) AS [解密後的明文]

--以後還需要用這個密鑰的話就不用刪掉它
DROP ASYMMETRIC KEY my_Asymetric_key;

 

另外可能用得到的一些語句有:

--查看所有對稱密鑰
SELECT * FROM sys.symmetric_keys;

--查看所有非對稱密鑰
SELECT * FROM sys.asymmetric_keys;

例子

可能你還想說:其實這些加密程序也能做,爲什麼要用DBMS的功能來做?——方便。前面也提到了,工資這個東西將來一定要做統計,做報表的,如果用DBMS的功能來做,一個報表也許也就是一個連表查詢的SELECT語句,但用程序來做這種“連表查詢”的功能恐怕就很麻煩了。

在應付這次需求上面,我認爲比較適合用非對稱加密,即:誰都可以加密,但只有知道私鑰的人才能解密。例如我是工資管理員,我要給員工007設置工資爲3000,我就用007的公鑰對“3000”進行加密好了,這樣,007能夠用自己的私鑰解密出自己的工資了,每個員工都有不同的公私鑰,都只能查看自己的工資,那問題來了,對於我這個管理員來說,要查看所有員工的工資,豈不是要知道他們全部的私鑰纔行?這樣豈不是很麻煩?是的,我這次是用了一點“數據冗餘”來解決這個麻煩,即:用兩列來保存工資信息,其中一列是真正的工資加密信息(amount),另一列是給員工自己查看的工資信息(amount_view),amount_view是用amount生成的,amount的內容使用工資管理員的公鑰進行加密,而amount_view的內容則使用員工的各自的公鑰進行加密。

現在我們來實踐一下:

 

--創建一個員工表
CREATE TABLE hr_emp(
emp_no nvarchar(20) PRIMARY KEY,
name_c nvarchar(20) NOT NULL,
has_salary_pwd bit NOT NULL DEFAULT(0),
);

--創建一個工資表
CREATE TABLE hr_salary(
sal_id int PRIMARY key IDENTITY(1,1) NOT NULL,
emp_no nvarchar(20) NOT NULL,
type nvarchar(15) NOT NULL,
amount varbinary(max) NOT NULL,
amount_view varbinary(max) NOT NULL
);

--增加一個外鍵約束
ALTER TABLE hr_salary ADD CONSTRAINT fk_salary_emp_ref_emp FOREIGN KEY (emp_no) REFERENCES hr_emp(emp_no);

--創建一個非對稱密鑰
CREATE ASYMMETRIC KEY salary_mgr_key
WITH ALGORITHM = RSA_512
ENCRYPTION BY PASSWORD = N'123456';

--初始化一些數據
insert into hr_emp (emp_no, name_c) values ('0008', '張三');
insert into hr_emp (emp_no, name_c) values ('0053', '李四');
insert into hr_emp (emp_no, name_c) values ('0055', '王五');
insert into hr_emp (emp_no, name_c) values ('0058', '趙六');

insert into hr_salary (emp_no, type, amount, amount_view) values('0008', 'Cash', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),8000.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0053', 'Cash', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),4000.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0055', 'Cash', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),3000.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0058', 'Cash', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),4500.00)), 0);

insert into hr_salary (emp_no, type, amount, amount_view) values('0008', 'Allowance', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),1234.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0053', 'Allowance', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),800.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0055', 'Allowance', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),765.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0058', 'Allowance', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),980.00)), 0);

insert into hr_salary (emp_no, type, amount, amount_view) values('0008', 'Deduct', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),0.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0053', 'Deduct', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),-440.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0055', 'Deduct', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),0.00)), 0);
insert into hr_salary (emp_no, type, amount, amount_view) values('0058', 'Deduct', EncryptByAsymKey(AsymKey_ID('salary_mgr_key'), Convert(nvarchar(100),0.00)), 0);

複製代碼

現在來看hr_salary表中的內容的話,發現amount列是加密的,沒有密碼就沒法知道其中的內容:

OK,我們現在來解密:

select emp_no , type , Convert ( decimal( 16 ,2 ), Convert( nvarchar (100 ), DecryptByAsymKey (AsymKey_ID ( 'salary_mgr_key'), amount, N'123456')))as amount from hr_salary;

結果出來了,解密成功。

統計各個員工工資總數,並把中文名帶出來:

select e.emp_no, e.name_c, s.amount from hr_emp e left join
(select emp_no, sum(Convert(decimal(16,2),Convert(nvarchar(100), DecryptByAsymKey(AsymKey_ID('salary_mgr_key'), amount, N'123456')))) as amount from hr_salary group by emp_no) s on e.emp_no=s.emp_no;

沒有壓力,對吧。

注意事項

至於amount_view這列的處理,大家想想也知道,方法其實前面都給出了,用CREATE ASYMMETRIC KEY語句來給每個用戶創建密鑰,密鑰名稱可以使用“ak+工號”這種規則,密碼可以用隨機生成,將密碼告訴用戶,讓他們自己記住,這樣就OK了。只是處理的時候必須記得,amount發生變化的時候,amount_view也要跟着發生變化。

用戶提供的密碼是否正確可以這樣驗證:

SELECT count(DECRYPTBYASYMKEY(AsymKey_ID('salary_mgr_key'), '', N'123456')) FROM sys.asymmetric_keys WHERE name='salary_mgr_key';

若結果爲1則正確,若結果爲0或出現異常則不正確。

修改密碼是很麻煩的事情,相當於重新創建一對非對稱密鑰,你得把整個加密好的數據用舊的密鑰解密好,再用新的密鑰加密。

最後還必須強調一點:整個服務器程序都不要保存密碼,否則前功盡棄,密碼必須由用戶在使用的時候提供,並且只暫存於Session中,Session丟失的話必須要求用戶重新輸入密碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章