防止开发人员获取到敏感数据(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的内容则使用员工的各自的公钥进行加密。spa

如今咱们来实践一下:设计

--建立一个员工表
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丢失的话必需要求用户从新输入密码。

相关文章
相关标签/搜索