存储底线--数据安全及主流方法

在英文里,有两个单词都表示“安全”,一个是Safety,另一个是Security。

所谓Safety,是指数据不丢失的意思。这是目前云存储解决的主要问题,我们可以把数据放在云端,然后由所有的终端设备通过云端来共享。从专业角度来说,这又叫可用性,意思是,数据总是可用的,基本不会丢失。

所谓Security,是指数据的隐私和不泄露。

高可用方案

在Google I/O大会上对目前主要的高可用方案做过一个比较,如图1所示。

存储底线--数据安全及主流方法

1)备份

备份应该是常见也是最容易想到的方案,备份的优点是延迟低、吞吐量高,但缺点是可能会导致数据丢失,而且故障切换(Failover)要接受一定的停机时间(Downtime)。

对于一个系统来说,进行数据备份是非常有必要的,但是,现实是就算所有的备份都可用,也不可避免地会发生数据丢失的情况,主要原因如下。

· 数据通常是按照一定周期进行备份的,所以,如果发生数据丢失的情况,即使从最近的备份进行恢复,从备份时间到故障发生时产生的数据也已经无法挽回。

· 备份的数据可能会有版本不兼容的问题。例如,在不同的备份之间,数据的组织结构会发生改变。

· 某些公司所谓的灾备中心根本就没有真正在工作,比如2014年发生的“宁夏银行7月发生数据库故障业务中断37小时”事件。

即使拥有设计完美的备份系统,也可能会发生数据丢失的情况,掉电、磁盘损坏、系统中病毒等都可能成为数据丢失的原因。

2)两阶段提交协议

如图1所示,与备份的方案相比,两阶段提交协议(Two Phase Commitment Protocol,2PC)具有较高的延迟和较低的吞吐量,但是不会发生数据丢失的情况,并且可以做到实时切换。

两阶段提交协议主要基于分布式一致性算法,能够保证多台服务器上的操作或全部成功,或全部失败。

在分布式系统中,各个节点在物理上相互独立,它们是通过网络进行沟通和协调的。虽然事务机制可以保证每个独立节点上的数据操作满足原子性、一致性、隔离性、持久性(Atomicity,Consistency,Isolation,Durability,ACID),但是,每个节点却无法准确地知道其他节点中的事务执行情况。所以从理论上来说,两个节点并没有办法达到一致的状态。我们要想让分布式部署的多个节点中的数据保持一致性,就需要保证所有节点上的数据写操作,或者全部被执行,或者全部都不被执行。但是,一个节点在执行本地事务的时候无法知道其他机器中本地事务的执行结果,所以它也就无法判断本次事务应该是提交还是回滚。所以,常规的解决办法是引入一个“协调者”来统一调度所有分布式节点的执行。

基于这个思想,两阶段提交协议算法可以概括为参与者将操作的成功与否通知协调者,再由协调者根据所有参与者的反馈情况,来决定每个参与者的操作是否要提交。

· 第一阶段:事务协调者给每个参与者发送Prepare消息,参与者或直接返回失败(如权限验证失败),或在本地执行事务但不提交,然后向协调者发送反馈。

· 第二阶段:如果协调者收到了任何一个参与者的失败消息或超时消息,则直接给每个参与者发送回滚消息,否则发送提交消息。然后参与者根据协调者的指令进行提交或回滚。

两阶段提交协议关注的是分布式事务的原子性,这个分布式事务提交之后,数据自然就会保持一致。

3)Paxos算法

Paxos算法是Leslie Lamport(LaTeX中的“La”,目前在微软工作,微软的Azure也使用了Paxos算法)于1990年提出的一种基于消息传递且具有高度容错特性的一致性算法。

与两阶段提交协议不同,在分布式系统中,Paxos算法用于保证同一份数据中多个副本之间的数据一致性。

在分布式系统中,位于多个节点的客户端对同一份数据进行操作时,存在并发操作的同步问题。如果是单机系统,则可以利用加锁的方式,来限制哪个客户端先操作,哪个客户端后操作。但是对于分布式系统,由于有多个副本的存在,如果申请锁然后等待所有副本更新完毕再释放,那么需要有一个节点来负责锁的分配,这个节点又会出现单点故障的问题,还会影响性能,出现死锁等其他问题。如果部署多个分配锁的节点,又会出现分布式锁管理的需求问题。

Paxos算法通过采用选举的方式,利用少数服从多数的思想解决了这个问题。如果有2N+1个节点,只要有N个以上节点同意了某个决定,就认为系统达到了一致,不会再改变。这样的话,客户端并不需要与所有服务器通信,可以只选择与大部分服务器通信,也无须所有的服务器都处于工作状态,只需要保证半数以上的服务器正常工作,整个过程就能持续下去,容错性相当好。

 

 可靠性

可靠性与可用性都是我们耳熟能详的衡量指标。与可用性不同,可靠性是指系统可以无故障地持续运行,是根据时间间隔来进行定义的。

所以谈到可靠性的时候一定要有一个时间。比如一个系统每1s内都有1ms的时间不能正常工作,那么它的可用性是99.9%,而1小时内不出现故障的可靠性是0。

也就是说,提高可靠性需要减少系统出现故障的次数,提高可用性则需要减少从故障中恢复的时间。通常,可靠性提高的同时,可用性也会得到提高。可靠性可以使用MTBF来衡量,在现实生活中,很多硬件厂商的产品上都会标注MTBF。

1. 磁盘阵列

磁盘阵列由多个独立的高性能磁盘驱动器组成,能够提供比单个磁盘更高的存储性能和数据冗余。

磁盘阵列主要基于3种关键的技术:镜像、数据条带(Data Stripping)和数据校验(Data Parity)。

· 镜像:数据在多个磁盘上存在副本,一方面提高了系统的可靠性,另一方面可以通过从多个副本并发读取数据的方式来提高读性能。但是由于副本的存在,要确保数据正确地写到多个磁盘需要消耗更多的时间。

· 数据条带:将数据分片保存在多个不同的磁盘,这些数据分片共同组成一个完整的数据副本。数据条带具有更高的并发粒度,当访问数据时,可以对位于不同磁盘上的数据同时进行读/写操作,从而极大地提升了I/O的性能。

· 数据校验:利用冗余数据进行数据错误检测和修复。冗余数据通常采用海明码、异或操作等算法来计算获得。数据校验功能在很大程度上提高了磁盘阵列的可靠性、鲁棒性和容错能力。不过,由于需要从多处读取数据并进行计算和对比,所以数据校验也会影响系统性能。

磁盘阵列的两个关键目标分别是提高数据的可靠性和I/O性能。对于计算机系统来说,整个磁盘阵列,就像一个单独的磁盘,可以通过把相同的数据同时写入多个磁盘,来保证单块磁盘出现故障时不会导致数据丢失的问题。

基于上述3种技术,可以把磁盘阵列分为不同的等级。有些磁盘阵列等级,允许更多的磁盘同时发生故障,比如RAID6允许两块磁盘同时基于上述3种技术,可以把磁盘阵列分为不同的等级。有些磁盘阵列等级,允许更多的磁盘同时发生故障,比如RAID6允许两块磁盘同时损坏。在这样的冗余机制下,可以直接使用新磁盘替换故障磁盘,磁盘阵列会自动根据剩余磁盘中的数据和校验数据重建丢失的数据,以保证数据的一致性和完整性。显然,磁盘阵列减少了全体磁盘总的可用存储空间,通过牺牲空间来换取更高的可靠性和性能。

2.纠删码

我们知道硬盘经常会出现损坏的情况,损坏了数据就会丢失。我们为了解决这个问题,从软件层面上通常是在分布式系统中存放数据的多个副本。如果其中一个硬盘损坏,上面数据被抹掉,那么其他硬盘上的副本可以接管服务,同时通过复制恢复数据,如Ceph默认存放了3倍的数据。如果我们要存放大量的冷数据,这样做就需要考虑成本的问题。

恰巧纠删码有恢复丢失数据的作用。可以将数据先分成k块,然后纠删编码成n块,存放到n个不同的硬盘上。当硬盘出现故障时,可以通过其他硬盘上的数据恢复出原始的k块数据,注意这里实际上只存放了n/k倍的数据。

如图5-3所示,从纠删码基本的形态来看,它是n个数据加m个校验的结构,其中数据和校验的n和m值都能够按照一定的规则设定。在1~m个数据块(数据或校验都行)损坏的情况下,整体数据仍然可以通过计算剩余数据块上的数据来得出,整体数据不会丢失,存储仍然可用。

存储底线--数据安全及主流方法

 

纠删码能够基于更少的冗余设备,提供和副本近似的可靠性。但是纠删码也带来了计算量和网络负载的额外负担。当磁盘出现故障时,数据的重建过程非常消耗CPU资源,网络负载也会有数倍甚至数十倍的增加。所以,往往需要在存储资源利用率和数据的重建代价之间做一个平衡。

但是,在实际的生产环境里,单个磁盘发生故障的概率远大于多个磁盘同时发生故障的概率。基于这种情况,一种新的思路是将磁盘分组,把单个磁盘故障的影响范围限制在各个组内,在组内的磁盘出现故障时,重建数据只需要读取组内的磁盘,所以需要的网络流量更少,从而减少对全局的影响。

于是LRC(Locally Repairable Codes)算法被提出,核心思想是除了全局的冗余块,还将数据块进行分组,为每组数据块增加冗余。在这种情况下,当一个数据块出现故障时,只需要读取组内的数据块和冗余块即可,这大大加快了数据重建的速度。

 

数据完整性

我们知道数据在传输过程中,可能会发生出错的情况,甚至会发生信息被非法篡改的情况,如修改、复制、插入、删除等。而所谓的数据完整性就是要确保接收到的数据和从数据源发出的数据完全一致,即数据在传输和存储的过程中没有被篡改。

通常采用数据校验技术来保证数据的完整性,也就是在发送方使用一定的算法对原始数据计算出一个校验值,接收方使用同样的算法对接收到的数据计算一次校验值,如果一致,则说明数据是完整的。

1)奇偶校验

所谓奇偶校验(Parity Check)就是在发送的每字节后都加上一位校验位,使得每字节中1的个数为奇数或偶数。比如要发送数据0x0a,二进制数表示为0000 1010,采用奇校验,则在它后面补上一个1,数据变为0000 1010 1,其中1的个数为奇数个(3个),采用偶校验,则补上一个0,数据变为0000 1010 0,其中1的个数为偶数个(2个)。接收方通过计算收到的数据中1的个数是否满足奇偶性来确定是否有错。

奇偶校验实现比较简单,而且可以很容易用硬件来实现,所以也被广泛地采用。但是奇偶校验对错误检测成功的概率大约只有50%。另外,每传输一字节都要附加一位校验位,极大地增加了网络负载,因此在高速的数据通信中比较少用。

2)MD5、SHA和MAC等摘要算法

摘要算法又称为哈希算法、散列算法,它通过对所有数据提取指纹信息的方式来实现数据签名、数据完整性校验等操作,具有不可逆性,常被用于数据量比较大的场合。比如在网上进行大文件的传输时,经常会用MD5算法产生一个与文件匹配的、存储MD5值的文本文件(后缀名为.md5或.md5sum),用户接收到该文件后,可以通过相应的工具基于这个MD5文件来检查文件的完整性,绝大多数开源组织都是以这种方式来校验数据完整性的。

3)累加和校验

累加和校验是另一种常见的校验方式。累加和校验的实现方式有很多种,最常用的方式是在一次通信过程中,在数据包的最后加入一字节的校验数据,这一字节的内容为之前所有数据包中数据的按字节累加和(忽略进位)。比如要传输的数据为6、23、4,加上一字节校验和后的数据包为6、23、4、33,这里33即为前3字节的校验和。接收方收到全部数据后对前3个数据进行同样的累加计算,如果累加和与最后一字节相同的话就认为传输的数据没有问题。

累加和校验的检错能力比较一般,但由于实现起来非常简单,所以也被广泛地采用。

4)CRC

与累加和校验类似,CRC在数据传输的形式上也可以表示为“通信数据”加上“校验字节”(也可能是多字节)的形式。CRC算法的基本思想是将传输的数据当作一个位数很长的数,将这个数除以另一个数,得到的余数作为校验数据附加到原始数据后面。

基于应用环境与习惯的不同,CRC又可分为CRC12、CRC16、CRC32等,最常见的是CRC32,它产生一个4字节的校验值。在包括WinRAR、WinZip等在内的很多压缩软件中,都是以CRC32作为文件校验算法的。

 

 访问控制

访问控制(Access Control)通常用于控制用户对服务器、目录、文件等网络资源的访问,从而保障数据资源在合理、合法的范围内得以有效使用和管理。为了达到这样的目的,访问控制需要识别和确认访问系统的用户,并决定该用户可以对某一系统资源进行何种类型的访问。

1)DAC

在操作系统出现之初,资源比较充足,所有的用户可以相安无事的共享系统资源。但是随着系统用户的增加,资源的并发访问必然会导致竞争的问题,为了能够占用更多的系统资源,就有可能出现某些用户擅自修改其他用户数据,或者强行终止其他用户的应用的情况。

所以在操作系统里就有了用户身份的概念,并定义了不同的用户身份对各种资源的访问权限,使用系统资源时,通过用户的身份来确认访问的合法性。这种资源管理的机制称为自主访问控制(Discretionary Access Control,DAC)主要包括主体、客体、权限、所有权。主体是用户的身份,客体是资源,由主体自主决定是否将自己的客体访问权限或部分访问权限授予其他主体。也就是说,在DAC下,用户可以根据自己的意愿,有选择地与其他用户共享自己的文件。

DAC相对比较宽松,但却能有效地保护资源而不被非法访问。宽松是因为在保护资源的时候是以个人意志为转移的,有效是因为可以明确指出主体以何种权限来访问某个客体,任何超越规定权限的访问行为都会被访问控制列表判定并阻止。

2)MAC

在DAC的机制中,由于自主性太强,可以说文件资源的安全性在很大的程度上取决于用户个人的意志。尤其是对于root用户而言,无论是权限和所有权的限制,还是文件系统访问控制列表(FACL)的管理控制,都仅仅是能够限制root用户的误操作而已,这无法解决因为SUID(Set User ID)等因素导致的root用户身份被盗用带来的问题。

因此,需要一种行之有效的方法来防止root用户对资源进行误操作和权限滥用。于是,强制访问控制(Mandatory access control,MAC)这一安全概念被提出。MAC最早被应用于军方,通常与DAC结合使用。MAC的相关概念如下。

· 主体:通常是指用户,或由用户发起运行的进程,或用户正在使用的设备。主体主动发起对资源的访问。

· 客体:通常是指信息的载体,或从其他主体或客体接收信息的实体。主体有时也会成为访问或受控的对象,比如一个主体可以向另一个主体授权,一个进程可能控制几个子进程等,这时受控的主体或子进程也通常被认为是一种客体。

MAC将访问控制规则“强加”给访问主体,即系统强制主体服从访问控制策略。MAC的主要作用对象是所有主体及其所操作的客体(进程、文件等)。MAC为这些主体及其所操作的客体提供安全标记,并基于这些标记来实施MAC,比如通过比较主体和客体的安全标记来判断一个主体是否能够访问某个客体。

MAC一般与DAC结合使用,也就是说,一个主体只有通过了DAC与MAC的双重过滤之后,才能真正访问某个客体。一方面,用户可以利用DAC来防范其他用户对那些所有权归属于自己的客体进行攻击;另一方面,由于用户不能直接改变MAC安全标记,所以MAC提供了一个更强的安全保护层以防止其他用户偶然或故意地滥用DAC。

3)RBAC

DAC与MAC实现起来比较复杂,而且DAC安全性太弱,MAC安全性又太强,因此基于角色的访问控制(Role-Based Access Control,RBAC)被提出,并在主体与客体之间引入了角色的概念,主体基于不同的角色对客体进行访问。

RBAC的灵感来源于操作系统的GBAC(GROUP-Based Access Control)。简单来说,就是一个“用户—角色—权限”的授权模型:一个用户可以拥有若干个角色,每一个角色又拥有若干个权限,用户与角色之间,角色与权限之间,一般都是多对多的关系。

角色可以理解为权限的载体,是一定数量的权限的集合。例如,对于一个论坛,版主、系统管理员都是角色,版主拥有管理版内帖子的权限,要给某一个用户授予某些权限,只需要把相应的角色赋予该用户即可。

RBAC能够支持最小权限原则、责任分离原则和数据抽象原则等。基于最小权限原则,可以将其角色配置成完成任务需要的最小权限集。通过调用相互独立互斥的角色共同完成特殊任务,可以实现责任分离原则,比如核对账目等。基于数据抽象原则,可以通过权限的抽象控制一些操作,比如财务操作里的借款、存款等抽象权限。

 

加密与解密

数据的加密与解密是一件很严肃的事情,如果没有被严肃对待,就会发生“严肃”的后果。例如,2011年的“**** 600万用户密码泄露”事件,用户的密码被不加“掩饰”地明文存储。

一般来说,用户的口令都是以MD5编码加密放在数据库里的,当用户登录的时候,会把用户输入的密码执行MD5后再和数据库进行对比,判断用户身份是否合法,这种加密算法称为哈希。因为从理论上来说,MD5是不可逆的,所以即使数据库丢失了,由于数据库里的密码都是密文,根本无法判断用户的原始密码,所以后果也不会太严重。

但是,总有部分别有用心的人会去收集常用的密码,然后执行MD5或SHA1并做成一个数据字典,从而可以对泄露的数据库中的密码进行对比,如果我们的原始密码很不幸地被包含在这个数据字典中,那么在很短的时间内就能被匹配出来。

图3中表明了我们精心设计的密码大概需要多少时间会被**,第1列是口令长度,第2列是全小写的口令,第3列是有大写字母的口令,第4列是加上了数字和其他字符的口令。

存储底线--数据安全及主流方法

从图3中可以看到,密码口令最好设置为8个字符以上的长度,而且一定要有小写字符和数字,最好再加上其他字符,这样被**的时间可能需要463年,相对比较安全。

密码只是我们个人隐私的一部分,扩展来说,我们希望受到保护的数据都应该被严肃对待。

对数据进行保护最好的方式就是加密。在之前进行加密操作通常意味着要损耗一定的性能,但是随着存储加密技术的发展,加密对性能的影响也变得越来越小,对企业来说,已经完全可以对数据进行全面的加密了。

比如,文件级加密(File-based Encryption,FBE)是针对每一个文件单独加密,甚至可以针对不同用户使用不同的**进行划分,这就使得系统不需要一刀切地把所有文件都加密了。文件加密可以保护特定的文件,这样不太重要的文件就不会浪费加密与解密必要的额外资源了。操作系统层面往往就有文件加密机制,比如微软的加密文件系统(EFS)。

作为FBE的延伸,文件夹级加密可以对整个文件夹里面的内容进行加密,比如Linux的用户主目录。需要注意的是,很多文件夹加密的方案并不是把整个文件夹加密成一个对象,而是逐个加密文件夹里面的文件。

加密的算法也有对称与非对称之分,可逆与不可逆之分。不可逆加密算法的特征是加密过程中不需要使用**,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密,比如我们常说的MD5。

所谓对称加密,是指通信双方在加密与解密过程中使用它们共享的单一**,比如DES(Data Encryption Standard)及AES。因为对称加密要求分享信息的各个个体之间分享**,所以安全性相对来说比较低,比如,当有多个人使用同一个**进行密文传输时,只要其中一个人的**被盗了,那么整个的加密信息都将被**。

这就需要做到即使一个人的**被盗了,仍然能够保证密文不被**。解决这个问题的办法是,每个人都会生成一个“私钥—公钥”对,私钥由每个人自行保存,公钥可以随便分享,同时,使用私钥加密的信息,只能由该私钥对应的公钥才能解密,使用公钥加密的信息,也只能由该公钥对应的私钥才能解密。这就是非对称加密,代表算法是RSA。