浅谈网站用户密码安全

浅谈网站用户密码安全

转载自:http://blog.sina.com.cn/s/blog_624f972301013113.html

前段时间,不少重量级网站纷纷爆出密码泄露问题,搞得人心惶惶,很多密码万年不换的用户也被迫更换新密码,

那么,是为什么黑客可以如此轻易的获得用户密码呢?俗话说,道高一尺,魔高一丈,作为一个DBA,我们有什么手段可以提高用户信息的安全性呢?
信息泄露通常的原因之一是由于前端程序员忽略了对用户输入的检查,导致了SQL注入问题,造成数据库内容泄露。

程序员的水平参差不齐,也没有做充分的code review,所以这样的问题很难彻底避免,国内一些著名的购物网站,技术网站都存在这样的问题。
保证用户信息安全的第一道防线就是:密码加密。

现代加密算法主要分为三种:哈希算法(也叫杂凑算法,散列算法,单向加密算法),对称算法,非对称算法(也叫公开密钥算法),

详情请浏览《密码学》或类似书籍。
通常我们使用哈希算法加密密钥,哈希算法的特点是无法解密,所以即使数据库内容泄露,也很难反推出用户的密码。在

数据库中只存放用哈希算法加密后的密文,在比较时把用户输入的密码加密后与密文进行比较,就可以验证用户输入的密码是否正确。

SQL Server 2005起内置哈希函数,支持MD2 | MD4 | MD5 | SHA | SHA1算法。

由于MD和SHA的碰撞已经比较容易被找到(简单的说,就是能根据密文找到“密码”),所以建议使用SHA1算法。

除了哈希算法,非对称算法也是一个加密密钥的好主意,只需要丢弃私钥,也就变成了单向的加密,

但非对称算法的开销比哈希算法要大得多,所以在实际应用中使用得不多。
示例如下:

  1 浅谈网站用户密码安全
  2 
  3 
  4 存储密钥:
  5 USE [tempdb]
  6 GO
  7 CREATE TABLE UserLogin
  8 (
  9     UserName NVARCHAR(50) PRIMARY KEY,
 10     Password VARBINARY(256)
 11 )
 12 GO
 13 INSERT INTO UserLogin
 14 VALUES('Username', CONVERT(VARBINARY(256), PWDENCRYPT('pass@word')))
 15 INSERT INTO UserLogin
 16 VALUES('Username1', CONVERT(VARBINARY(256), PWDENCRYPT('pass@word')))
 17 这时候我们 SELECT * FROM UserLogin 会发现,两次加密的密文不一样,这就是salt的功效了。
 18 验证用户登录:
 19 CREATE PROCEDURE up_UserLogin
 20 (
 21     @UserName NVARCHAR(50),
 22     @Password VARCHAR(100)
 23 )
 24 AS
 25 DECLARE @ret INT
 26 SELECT  @ret = PWDCOMPARE(@Password,Password)  FROM UserLogin
 27 WHERE UserName = @UserName
 28  
 29 IF @ret = 1
 30     --正确登录
 31     RETURN 0
 32 ELSE
 33     --错误登录,可能用户不存在或密码错误
 34     RETURN 1
 35 GO
 36 DECLARE @ret INT
 37 EXEC @ret = up_UserLogin 'Username', 'pass@word'
 38 SELECT @ret
 39          由于PWDENCRYPT和PWDCOMPARE这两个函数是SQL Server未公开的函数,可能在某一个版本就废弃了,所以不建议使用在可能会升级的生产系统中。下面是另一个使用salt的示例:
 40 CREATE TABLE UserLogin
 41 (
 42     UserName NVARCHAR(50) PRIMARY KEY,
 43     Password BINARY(20),
 44     SignupTime DATETIME
 45 )
 46 GO
 47 INSERT INTO UserLogin
 48 VALUES('Username', HASHBYTES('SHA1', CONVERT(VARCHAR(50), HASHBYTES('MD5', N'Username'), 2) + 'pass@word' +  CONVERT(VARCHAR(50), HASHBYTES('MD5', CONVERT(NVARCHAR(50), GETDATE(), 121)), 2)), GETDATE())
 49 SELECT * FROM UserLogin
 50 GO
 51 CREATE PROCEDURE up_UserLogin
 52 (
 53     @UserName NVARCHAR(50),
 54     @Password VARCHAR(100)
 55 )
 56 AS
 57 DECLARE @ret INT, @CiperText BINARY(20), @SignupTime DATETIME
 58 SELECT @CiperText = Password, @UserName = UserName, @SignupTime = SignupTime
 59 FROM UserLogin
 60 WHERE UserName = @UserName
 61  
 62 IF @CiperText IS NOT NULL AND @CiperText = HASHBYTES('SHA1', CONVERT(VARCHAR(50), HASHBYTES('MD5', @UserName), 2) + @Password +  CONVERT(VARCHAR(50), HASHBYTES('MD5', CONVERT(NVARCHAR(50), @SignupTime, 121)), 2))
 63     --正确登录
 64     RETURN 0
 65 ELSE
 66     --错误登录,可能用户不存在或密码错误
 67     RETURN 1
 68 GO
 69 DECLARE @ret INT
 70 EXEC @ret = up_UserLogin 'username', 'pass@word'
 71 SELECT @ret
 72  
 73 本示例使用了用户名和注册时间的MD5值作为salt。
 74  
 75 使用哈希算法加密后的密码是相对安全了,但是我们要保密的不只是用户密码而已,为了防备SQL注入攻击,最常见的方法就是强制参数校验。Developer那边我们DBA管不了,那么数据库这边总是我们说了算吧?建议禁止sa账号的使用,严格限制db_owner的使用,程序用的账号建议拒绝db_reader/db_writer权限,只允许执行存储过程的权限,所有的存储过程经过DBA审阅后加密创建,严格控制动态SQL语句的使用。这样就可以防备使用SQL注入工具攻击数据库的低级黑客了。当然,这样肯定是给开发带来一点不便的,不过为了安全,另外还可以提高数据库访问的效率,还是值得的。要注意的一点是,控制数据库的读写权限可以很方便的用db_reader/db_writer,但是没有一个角色具有全部存储过程的执行权限的。一般来说,要给账号赋予全部存储过程的执行权限,有两个方案,一个是DBA在创建账号后人工给账号授权,另一个是在创建存储过程时自动授权。下面这段触发器的代码会在创建存储过程时给角色db_procexecutor授予执行权限。
 76 CREATE TRIGGER TR_DDL_AUTO_GRANT_NEW_PROC_TO_db_procexecutor
 77 ON DATABASE
 78 FOR CREATE_PROCEDURE
 79 AS
 80    DECLARE @PROC_NAME NVARCHAR(300), @SQL NVARCHAR(4000)
 81    SET @PROC_NAME = QUOTENAME(EVENTDATA().value('(/EVENT_INSTANCE/SchemaName)[1]', 'sysname')) + '.' + QUOTENAME(EVENTDATA().value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname'));
 82    SET @SQL = N'GRANT EXECUTE ON ' + @PROC_NAME + ' TO [db_procexecutor]';
 83    EXEC (@SQL)
 84 GO
 85 这点小伎俩是防不了真正的黑客的,不过我们可以在里面加一些Honeycombs来提高一点安全性,里面放上一些名字看上去很诱人的存储过程,比如GetUserInfo之类的,一执行马上给DBA发个短信。
 86 信息泄露的另一个原因是员工被收买,事实上,这是一个简单得多的途径,防备这个的方法通常是加密。SQL Server支持对称加密和非对称加密,欲知详情,请翻阅联机丛书。不过这两种加密是要修改应用程序的,好处是可以自由控制要加密的内容,而不用全部加密,缺点是加密后的数据无法使用索引做范围查找或模糊查找。SQL Server还支持TDE(透明加密),不需要修改程序即可实现加密,索引什么的都没有问题,缺点是只能全库加密。当然,加密也只能防备直接拷贝数据库,多收买几个人就可以搞定了:D
 87 下面是使用对称加密算法加密表中一个字段的示例:
 88 CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Pass@word3';
 89 GO
 90 CREATE CERTIFICATE DatabaseCert
 91     AUTHORIZATION dbo
 92     WITH SUBJECT = 'Database Certificate',
 93     EXPIRY_DATE = '12/31/2012';--mm/dd/yyyy
 94 GO
 95 CREATE SYMMETRIC KEY SymmetricKey WITH ALGORITHM = TRIPLE_DES
 96     ENCRYPTION BY CERTIFICATE DatabaseCert;
 97 GO
 98 --以上代码为创建证书和对称密钥,为指定用户授予权限的代码略。除SQL授权外,也可用密码等手段保护证书和密钥,或者把密钥直接存储在数据库之外。
 99 CREATE TABLE test_encrypted
100 (
101     id int primary key,
102     name_encrypted varbinary(128)
103 )
104 GO
105 --创建VIEW,以简化对加密数据的查询,直接查询视图即可得到解密后的数据
106 CREATE VIEW dbo.test
107 WITH SCHEMABINDING
108 AS
109 SELECT   id, CONVERT(varchar(10), DecryptByKeyAutoCert(cert_ID('DatabaseCertNoEKM'), NULL, name_encrypted)) AS name
110 FROM      dbo.test_encrypted
111 GO
112 --创建INSTEAD OF触发器,以简化对加密数据的增删改,直接对视图操作,数据库中存储的即为加密后的数据。
113 CREATE TRIGGER [dbo].[test_insert]
114 ON [dbo].[test]
115 INSTEAD OF INSERT
116 AS
117 BEGIN
118     SET NOCOUNT ON;
119     OPEN SYMMETRIC KEY SymmetricKey
120     DECRYPTION BY CERTIFICATE DatabaseCert;
121  
122     INSERT INTO dbo.test_encrypted (name_encrypted)
123        SELECT EncryptByKey(Key_GUID('SymmetricKey'), name)
124        FROM inserted
125 END
126 GO
127 CREATE TRIGGER [dbo].[test_update]
128 ON [dbo].[test]
129 INSTEAD OF UPDATE
130 AS
131 BEGIN
132     SET NOCOUNT ON;
133     OPEN SYMMETRIC KEY SymmetricKey
134     DECRYPTION BY CERTIFICATE DatabaseCert;
135  
136     UPDATE  dbo.test_encrypted SET name_encrypted = EncryptByKey(Key_GUID('SymmetricKey'), inserted.name)
137     FROM inserted INNER JOIN deleted ON inserted.id = deleted.id
138     WHERE dbo.test_encrypted.id = deleted.id;
139 END
140 GO
141 CREATE TRIGGER [dbo].[test_delete]
142 ON [dbo].[test]
143 INSTEAD OF DELETE
144 AS
145 BEGIN
146     SET NOCOUNT ON;
147     DELETE  dbo.test_encrypted
148     FROM deleted WHERE dbo.test_encrypted.id = deleted.id;
149 END
150 GO
151 这种方法能够简化对加密的使用,使得应用程序基本无须修改,即可享受加密带来的好处。不过INSTEAD OF触发器有个缺点,基表存在IDENTITY,TIMESTAMP,计算列等无需赋值字段时,INSERT可以无需为该字段赋值,但使用INSTEAD OF触发器后,必须为这些列提供值。有人想出了一个巧妙的办法,绕开了这个限制,不过这是别人的商业机密,未得授权不便透露。
152 以下是TDE的示例,TDE的使用要简单得多:
153 USE master;
154 GO
155 CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'Pass@word2';
156 GO
157 CREATE CERTIFICATE ServerCert
158     AUTHORIZATION EncryptAdmin
159     WITH SUBJECT = 'Server Certificate',
160     EXPIRY_DATE = '12/31/2012';--mm/dd/yyyy
161 GO
162 USE Test_DB;
163 GO
164 CREATE DATABASE ENCRYPTION KEY
165 WITH ALGORITHM = AES_128
166 ENCRYPTION BY SERVER CERTIFICATE ServerCert;
167 GO
168 ALTER DATABASE Test_DB
169 SET ENCRYPTION ON;
170 GO
171 注意使用TDE后,要注意备份并妥善保管服务器证书,否则数据库服务器崩溃后,数据库中的数据将无法恢复。当然,也要注意证书的安全,否则加密形同虚设。
172  
173 注:本文所介绍的示例代码可在SQL Server中运行,其中加密算法需要SQL Server 2005或以上版本,TDE需要SQL Server 2008 Datacenter/Enterprise 或以上版本。示例代码不包含错误处理,授权等内容,用于生产系统前请自行补充该部分内容。任何建议或指正,敬请联系lvke1978#hotmail
174  
175 参考资料:
176 [1] SQL Server Books Online
177 [2] Bruce Schneier, “Applied Cryptography Second Edition: protocols, algorithms, and source code in C”, John Wiley & Sons, Inc. 1996
178 [3] Kevin Kenan, “数据库加密——最后的防线”, 李彦智,马超,林滨 译,电子工业出版社,2006

 

 

posted @ 2013-12-20 21:43  桦仔  阅读(352)  评论(0编辑  收藏  举报