SQL Server 2005 安全性(程序员视角)
本文旨在介绍 SQL Server 中的安全要素,例如:限制对服务器的访问、服务锁定和安全配置以及减少新元素的攻击面。文章将演示创建和管理用户以及保护数据的技术。
本文由 Artur Żarski 撰写,最初发表于 2005 年 9 月的《软件开发人员杂志》。您可以在 SDJ 网站上找到更多文章。
可信计算
可信计算的概念基于三个支柱:安全性、数据机密性和可靠性。这三个支柱可以这样描述:
- 安全性:系统具备安全措施,简化了防范网络攻击的保护。安全功能有助于提高系统和数据的隐私性、完整性和可访问性。
- 数据机密性:用户可以影响与其相关的数据的安全性以及数据的使用方式,还可以验证数据收集、监督和使用相关过程是否符合公认的可靠信息处理策略。
- 可靠性:保证关键系统功能和服务的可访问性,尤其是在对客户组织活动至关重要的操作方面。
近年来,微软推出了“可信计算”倡议,这是一项旨在创建安全计算环境的倡议。为了维持这项倡议,SQL Server 团队为安全性制定了以下假设:
- 定义即安全,
- 编程即安全,
- 通信即安全。
SQL Server 2005 提供了以下新的安全功能:
- 限制对服务器的访问,
- 服务锁定和安全配置,
- 减少新元素的攻击面。
本文旨在从程序员的角度介绍 SQL Server 中重要的安全要素。在此,安全性可以分为两部分:第一部分是数据库的安全性,第二部分是数据本身的安全性。在文章的第一部分,我们将演示创建和管理用户的新技术;在第二部分,我们将展示如何保护我们的数据——例如,通过加密。
用户管理
在 SQL Server 2000 中,要创建用户,必须使用存储过程 sp_addlogin。在新的 SQL 2005 中,用户管理发生了重大变化。非常强调与操作系统及其定义的安全策略更好地集成用户;为此,引入了 CREATE LOGIN
语句。此语句允许管理密码过期和强制执行定义的密码质量规则的高级功能。
以下示例演示了该语句最基本的使用方式——创建具有特定密码的用户,并授予其对默认数据库的访问权限。
CREATE LOGIN ArturZ
WITH PASSWORD = 'password',
DEFAULT_DATABASE= AdventureWorks
下一步是与操作系统策略集成的选项。为此,我们将使用其他关键字扩展我们的语句,从而获得以下形式:
CREATE LOGIN ArturZ
WITH PASSWORD = 'password',
DEFAULT_DATABASE = AdventureWorks,
CHECK_POLICY = ON,
CHECK_EXPIRATION = ON
如果我们在操作系统中定义了适当的策略,我们就可以检查这些设置是否正常工作。让我们使用控制台将最小密码长度设置为 8 个字符。
图 1. 更改密码策略的控制台
为了检查策略是否生效,让我们尝试更改我们用户的密码。
ALTER LOGIN ArturZ WITH PASSWORD = 'az'
这应该会产生以下结果:
Msg 15116, Level 16, State 1, Line 1
Password validation failed. The password does not meet
policy requirements because it is too short.
要创建基于操作系统登录名的数据库用户,以下语句将非常有用:
CREATE LOGIN [server\ArturZ] FROM WINDOWS
WITH DEFAULT_DATABASE = AdventureWorks
数据库架构
架构是一组对象,可用于轻松保护对数据的访问。这使得使用 T-SQL 命令以非常简单的方式将用户分配给架构成为可能。为了使用架构,我们首先必须使用 CREATE SCHEMA
语句创建它们。
CREATE SCHEMA MySchema
在架构内创建表需要使用 schema.table 结构。下面我们展示如何创建我们刚刚创建的架构中的表。
CREATE TABLE MySchema.MyTable (FieldA int, FieldB int)
有一个名为 sys 的特殊架构。它会自动为每个数据库创建,并包含所有系统对象。
更改安全上下文
作为系统设计者,我们经常思考如何让一个普通用户无法执行的存储过程或函数能够被运行。为了解决这个问题,我们创建了一个新的子句 EXECUTE AS
,它允许更改用户上下文。让我们仔细研究以下示例以了解此子句的工作原理。用户 A 拥有 ProcedureA 存储过程的执行权限。该存储过程操作 ObjectA、ObjectB 和 ObjectC 对象。用户 A 仅拥有对 ObjectA 的权限;ObjectB 和 ObjectC 属于用户 B。如果用户 A 调用该存储过程,当它访问用户 A 没有访问权限的对象时,该过程将被中止。为了防止这种情况,可以使用 EXECUTE AS
子句,该子句将在用户 B 的安全上下文中执行该存储过程。
此子句可与存储过程和用户定义函数一起使用。
CREATE PROCEDURE ProcedureA
WITH EXECUTE AS 'B'
AS
…..
该示例展示了如何创建允许任何人以用户 B 的权限调用的存储过程。
此外,可以使用 EXECUTE AS SELF
形式使用此子句。 EXECUTE AS SELF
允许以调用用户的上下文执行存储过程。
对象权限
我们还讨论授予对象权限的可能性。为了让用户能够使用某个对象,他们必须拥有相应的访问权限。我们使用已知的 GRANT
语句授予对对象的访问权限,例如:
GRANT EXECUTE ON procedure TO UserA
我们已经从数据库的先前版本中了解了此语句。新的是授予架构权限的可能性。
GRANT EXECUTE ON Schema::MySchema TO ArturZ
此外,现在可以授予用户特殊权限,允许他们创建 HTTP ENDPOINT
对象,但不能访问其他对象。相应的语句如下所示:
GRANT ALTER ANY http ENDPOINT TO User
利用我们刚刚提到的要素,我们可以集成操作系统和数据库的安全性。使用密码策略可以让数据库用户遵循公司的相应策略。
数据加密
数据加密一直是数据库系统安全性的极其重要的组成部分。许多程序员根据各种数据加密和解密机制开发自己的加密过程和函数。
Microsoft SQL Server 2005 为加密机制提供了非常强大的支持。这大大减轻了程序员的负担,更不用说数据库内置的机制在许多情况下可能比自定义解决方案更有效。新数据库支持对称和非对称加密,以及证书的使用。可用的加密算法有:DES、TRIPLE_DES、RC2、RC4、DESX、AES_128、AES_192 和 AES_256 作为对称加密算法,以及 RSA 算法作为非对称加密算法,密钥长度为 512、1024 或 2048 位。
创建了以下新函数以实现自定义应用程序中的数据加密:EncryptByKey
、DecryptByKey
、EncryptByPassPhrase
、DecryptByPassPhrase
以及 Key_GUID
、Key_ID
。 EncryptByKey
、DecryptByKey
函数用于使用指定密钥进行加密和解密,EncryptByPassPhrase
和 DecryptByPassPhrase
用于使用给定密码短语进行加密和解密。
要处理加密数据,必须采取以下步骤:
- 创建主密钥(可以有多个),
- 如有必要,创建证书,
- 创建对称密钥或非对称密钥(对称密钥可以另外用证书或非对称密钥加密),
- 打开密钥,
- 一旦密钥打开,就可以处理加密数据,
- 关闭密钥。
为了创建密钥,我们使用 CREATE
子句的新元素:
CREATE MASTER KEY
– 创建主密钥,CREATE ASYMMETRIC KEY
– 创建非对称密钥,CREATE SYMMETRIC KEY
– 创建对称密钥,CREATE CERTIFICATE
– 创建证书。
每种特定类型的密钥的语法将在列表 1、2 和 3 中演示。
列表 1. 创建非对称密钥
CREATE ASYMMETRIC KEY Asym_Key_Name
[ AUTHORIZATION database_principal_name ]
{
FROM <Asym_Key_Source>
|
WITH ALGORITHM = { RSA_512 | RSA_1024 | RSA_2048 }
}
[ ENCRYPTION BY PASSWORD = ' password ' ]
<Asym_Key_Source>::=
FILE = ' path_to_strong_name_file '
|
EXECUTABLE FILE = ' path_to_executable_file '
|
ASSEMBLY Assembly_Name
列表 2. 创建对称密钥
CREATE SYMMETRIC KEY key_name [ AUTHORIZATION owner_name ]
WITH < key_options > [ , ... n ]
ENCRYPTION BY < encrypting_mechanism > [ , ... n ]
<encrypting_mechanism >::=
CERTIFICATE Certificate_Name
|
PASSWORD = ' password '
|
SYMMETRIC KEY Symmetric_Key_Name
|
ASYMMETRIC KEY Asym_Key_Name
<Key_Options >::=
DERIVED_FROM = ' pass_phrase '
|
ALGORITHM = < algorithm >
|
IDENTIFIED_BY = ' identity_phrase '
<algorithm >::=
DES | TRIPLE_DES | RC2 | RC4 |
DESX | AES_128 | AES_192 | AES_256
列表 3. 创建主密钥
CREATE MASTER KEY ENCRYPTION BY PASSWORD = ' password '
列表 4. 创建证书
CREATE CERTIFICATE certificate_name [ AUTHORIZATION user_name ]
{
FROM < certificate_source >
|
WITH < cert_options >
}
[ ACTIVE FOR BEGIN_DIALOG = { ON | OFF } ]
<certificate_source >::=
{
FILE = ' path_to_certificate '
|
EXECUTABLE FILE = ' path_to_file '
|
ASSEMBLY assembly_name
}
[ WITH PRIVATE_KEY ( < private_key_options > [ , ...] )
<private_key_options >::=
FILE = ' path_to_private_key '
DECRYPTION_PASSWORD = ' key_password '
ENCRYPTION_PASSWORD = ' password '
<cert_options >::=
{
SUBJECT = ' certificate_subject_name '
[ , START_DATE = ' mm/dd/yyyy ' ]
[ , EXPIRY_DATE = ' mm/dd/yyyy ' ]
[ , ENCRYPTION_PASSWORD = ' password ' ]
}
定义所有必需的元素后,我们就可以开始开发基于加密的解决方案了。为了了解整个过程,让我们在数据库中创建一个表,创建密钥,然后用数据填充表,这些数据将在插入时进行加密。首先,让我们创建一个简单的表:
CREATE TABLE TabEncr (
id int identity (1,1),
NonEncrField varchar(30),
EncrField varchar(30)
)
然后我们将创建一个主密钥。此密钥始终由数据库使用三重 DES 算法加密并存储在 sys.symmetric_keys 表中。同时,数据库的主密钥会用服务密钥加密,并以这种形式存储在 sys.databases 表中。
CREATE MASTER KEY ENCRYPTION BY PASSWORD = 'OurSecretPassword'
现在有必要创建密钥——让我们创建一个对称密钥和一个非对称密钥。有关密钥的信息存储在表 sys.symmetric_keys 和 sys.asymmetric_keys 中。在本例中,对称密钥将与非对称密钥结合使用。
CREATE ASYMMETRIC_KEY asym_Key WITH ALGORITHM = RSA_1024
CREATE SYMMETRIC KEY sym_Key WITH ALGORITHM =
DES ENCRYPTION BY ASYMMETRIC KEY asym_Key
如上所述,我们需要采取的另一个步骤是打开要使用的密钥;这与使用游标非常相似。在本例中,我们将需要指明对称密钥将与非对称密钥一起使用。
OPEN SYMMETRIC KEY sym_Key USING ASYMMETRIC KEY asym_Key
此时,我们已经定义了密钥,并且已经打开了它们,因此它们现在已准备就绪,可以使用了;因此,是时候开始向我们的表中输入数据了。数据将通过 sym_Key
插入表中,或者更确切地说,通过其标识符插入。
首先,我们必须获取密钥的标识符(GUID)。我们将把此标识符存储在一个临时变量中,我们将在查询中使用它。为了加密数据,我们将使用 EncryptByKey
函数。它接受的参数是我们的密钥的标识符和我们要加密的文本。
DECLARE @GUID UNIQUEIDENTIFIER
SET @GUID = (SELECT key_guid FROM sys.symetric_keys
WHERE name = 'sym_Kluczey')
INSERT INTO TabEncr (NonEncrField, EncrField)
VALUES ('Unencrypted data',
EncryptByKey(@GUID, 'Encrypted data'))
为了能够读取数据,我们将使用 DecryptByKey
函数,将其列名作为参数。此函数返回一个 VARBINARY
类型的字符字符串,因此我们将使用 CAST
函数将其转换为 VARCHAR
。
SELECT CAST(DecryptByKey(EncrField) AS VARCHAR(30)) FROM TabEncr
工作完成后,现在我们必须关闭我们的密钥。
CLOSE SYMMETRIC KEY sym_Key
从管理员的角度来看,程序员能够访问密钥并非总是可取的——因此,可以考虑创建一个带有 TRIGGER
的中间表。此触发器会将数据从中间表移动到正确表中,并在过程中对其进行加密,而不会向程序员泄露信息。当然,关于这个主题可能的方法很多,每个人都可以自由地基于上述机制开发自己的方法。