在对象存储系统中,保障数据不丢不错,是系统的底线。
而要保障数据不丢不错,非常重要的一环,是要保障网络传输过程中的数据安全。
网络传输过程中的常见问题分为三类:
-
数据不完整。对象存储系统中,数据包含了文件内容和文件属性。
- 文件内容通过HTTP Body传输,当数据不完整时,本来应该传输100MB的数据,但实际只传输了80MB,用户读取时会发现数据少了一部分。
- 文件属性通过HTTP Header传输,包含了Acl、加密算法、Content-Type等属性。当出现丢失时,用户会发现文件的元数据少了一部分。
-
数据非故意的损坏。在网络传输过程中,网卡作为传输设备,传输过程中因为线缆、接口、内部器件等问题,可能出现位翻转的情况,数据的内容会由0变为1,或者由1变为0,导致虽然文件长度是正确的,但是文件内容出错。
-
中间人攻击:指攻击者恶意拦截请求,并且篡改数据内容的行为,同样会导致数据的丢失或者损坏。
一旦在数据传输过程中,数据发生异常或损坏,对于企业都是致命的。这种风险可能直接导致业务中断、客户信任崩塌甚至法律纠纷,在数字化转型加速的今天,数据完整性已成为企业生命线。
例如在某次自动驾驶AI训练中,训练集群接收来自数据中心的训练数据,在网络传输过程中,由于交换机固件缺陷,发生了间歇性位翻转错误,导致接收到的点云数据中的障碍物高度参数出现偏差。最终在模型验证时,将实际高度1.8米的限高杆识别为2.3米,对于AI模型的实际应用引入了潜在的高危风险。
在TOS产品基于对象的访问场景中,很多用户会大量使用公网上传或者分享文件,而公网环境普遍存在网络质量差,网络攻击频繁等问题,遇到网络传输问题的概率也会变大。
那么如何在复杂的网络环境下,保证数据传输过程中的完整性,避免传输过程中数据损坏,TOS围绕此问题,提供组合应对措施。
协议层:TLS加密通信机制
TLS是业界非常通用的加密通信手段,TOS客户端支持通过HTTPS访问服务端,来保证网络传输过程中的安全性。
原理
1、建立连接: 客户端向服务端发起HTTPS请求,并发送支持的加密算法列表和随机数。
2、服务器响应:服务端返回选择的加密算法、随机数以及SSL证书。
3、验证证书:客户端验证证书的有效性,确保服务端身份可信。
4、密钥交换:客户端生成预主密钥,用服务端公钥加密后发送;服务端用私钥解密获取预主密钥。
5、生成会话密钥: 双方结合预主密钥和之前的随机数,生成相同的对称会话密钥,用于后续通信加密。
6、加密通信:客户端和服务器交换加密确认消息,验证握手完整性,之后所有HTTP数据均用会话密钥加密传输。
7、结束连接:数据传输完成后,安全连接关闭。
技术优势
- HTTP Header和HTTP Body都进行加密传输,解决绝大多数情况下的数据传输安全问题。
- 对称加密和非对称加密结合,既保证了主密钥的安全交换性,又保证了后续通信的效率。
- 证书机制可以确保服务器身份可信,防止中间人攻击。
应用层:TOS V4签名验证机制
将请求的HTTP Header的关键字段、HTTP Body的Checksum、用户的AccessKey和SecretKey,基于TOS V4的算法进行算签,将得到的签名携带在HTTP Header中,服务端收到请求后,会验证签名的正确性,避免请求被篡改。
示意图
1、 TOS V4签名构造示意图
2、 TOS V4签名验证示意图
原理
1、构造规范化请求: 客户端将待发送HTTP请求的Method、URI、Query、Headers、HTTP Body的Checksum通过固定的顺序进行排列,得到CanonicalRequest字符串。
CanonicalRequest =
HTTPMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HashedPayload
参数说明见附录一。
2、创建签名字符串(StringToSign): 将请求时间、地域、服务名称、CanonicalRequest的Hash值等信息拼接成一个字符串。
StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest))
参数说明见附录二。
3、计算签名密钥(SigningKey): 客户端基于自身账号的私钥(SecretKey),结合当前时间、地域、服务名称等信息,计算出一个临时的签名SigningKey。
SecretKey = *Secret Access Key*
DateKey = HMAC-SHA256(SecretKey, Date)
DateRegionKey = HMAC-SHA256(DateKey, Region)
DateRegionServiceKey = HMAC-SHA256(DateRegionKey, "tos")
SigningKey = HMAC-SHA256(DateRegionServiceKey, "request")
4、计算签名: 客户端将CanonicalRequest和SigningKey通过公开的算法计算出签名Signature。
Signature = HexEncode(HMAC-SHA256(SigningKey, StringToSign))
5、构建Authorization Header: 客户端将账号的公钥(AccessKey)、构造规范化请求的构造范围、Signature字符串,添加在请求的Header中。
Authorization: TOS4-HMAC-SHA256 Credential={AccessKeyId}/{CredentialScope},SignedHeaders={SignedHeaders}, Signature={Signature}
6、服务端验证签名: 服务端通过请求的公钥(AccessKey)从IAM获取到私钥(SecretKey),然后用和客户端一样的计算方式,计算出服务端签名,并和请求的Authorization Header进行对比。当签名完全匹配时,才继续进行服务。
技术优势
- 基于公钥(AccessKey)能够验证请求者的身份,并允许对其进行精细化的权限控制,比如BucketPolicy、Acl等。
- 能通过签名中的地域和服务信息,约束请求的使用范围,限制请求用于某些其他的地方。
- 能够有效防止请求被篡改。
- 部分客户出于某些考虑,无法使用HTTPS传输,只使用TOS V4签名,在此场景下,为了保证传输安全,必须在签名中携带有效数据的Checksum。而为了获取有效数据的Checksum,客户端需要先将全部数据读入内存,提前计算出Checksum,在发送请求时,又要再次把文件读入内存进行发送,反复的内存拷贝会带来额外的性能开销,导致客户端性能降低。
- 进行未知长度的数据上传时,客户无法提前预知完整数据的Checksum,只能边读边算,这种场景下,就无法提前携带Checksum进行签名,无法保障数据传输的安全性。
- 客户上传大文件时,如果刚开始上传就发生的数据异常,也需要传输全部数据后,服务端再根据Checksum的校验结果来拦截请求,造成了带宽和性能上的浪费。
边传边验:多分块链式签名验证机制
将请求的HTTP Body拆分为多个固定长度或者可变长度的分块进行上传,每个分块都拥有独立的分块签名,本分块签名中会包含分块数据的Checksum信息以及前一个分块的签名,服务端在接受数据时,会边接收数据边验证签名的正确性。
示意图
1、多分块链式签名构造示意图
2、多分块链式签名验证示意图
原理
1、构造规范化请求: 客户端将待发送HTTP请求的Method、URI、Query、Headers通过固定的顺序进行排列,得到CanonicalRequest字符串。
CanonicalRequest =
HTTPMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HashedPayload
参数说明见附录一。
2、创建签名字符串(StringToSign): 将请求时间、地域、服务名称、CanonicalRequest的Hash值等信息拼接成一个字符串。
StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest))
参数说明见附录二。
3、计算签名密钥(SigningKey): 客户端基于自身账号的私钥(SecretKey),结合当前时间、地域、服务名称等信息,计算出一个临时的签名SigningKey。
SecretKey = *Secret Access Key*
DateKey = HMAC-SHA256(SecretKey, Date)
DateRegionKey = HMAC-SHA256(DateKey, Region)
DateRegionServiceKey = HMAC-SHA256(DateRegionKey, "tos")
SigningKey = HMAC-SHA256(DateRegionServiceKey, "request")
4、计算种子签名: 客户端将CanonicalRequest和SigningKey通过公开的算法计算出种子签名SeedSignature。
SeedSignature = HexEncode(HMAC-SHA256(SigningKey, StringToSign))
5、构建Authorization Header: 客户端将账号的公钥(AccessKey)、构造规范化请求的构造范围、SeedSignature字符串,添加在请求的Header中。
Authorization: TOS4-HMAC-SHA256 Credential={AccessKeyId}/{CredentialScope},SignedHeaders={SignedHeaders}, Signature={SeedSignature}
6、服务端验证种子签名: 通过公开算法,计算签名并和Header中的种子签名对比。
7、构建第一个分块的签名并发送数据: 将SeedSignature、本分块数据的Checksum、当前时间、地域、服务名称,使用公开算法,计算出ChunkSignature1,并且携带在分块数据的HTTP Body开头。
7.1、创建分块待签名字符串 (StringToSign): 签名字符串由签名算法、请求日期、信任状、前一个签名字符串、空行哈希值、分块内容哈希值组成。
StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + PreviousSignature + '\n' + Hash("") + '\n' + Hash(ChunkData)
- PreviousSignature指前一个分块的签名。如果当前分块是第一个分块,那么PreviousSignature指第4步得到的SeedSignature。
- Hash("")是固定值:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
7.2、计算签名字符串: 计算得到ChunkSignature1。
计算方式同第3、4步骤。
7.3、构建分块的负载: 将签名内容拼接到有效数据前方。
string(IntHexBase(ChunkSize)) + ";chunk-signature=" + ChunkSignature + \r\n + ChunkData + \r\n
- IntHexBase() 是将整数转化为十六进制数的函数。例如,若分块大小为65536,则十六进制字符串为“10000“。
- ChunkSize指分块有效数据大小,不包含签名和格式数据。
- ChunkSignature指7.2步骤得到的签名字符串。
- ChunkData指分块有效数据。
8、服务端接收数据并验证分块签名: 通过公开算法,计算签名并和ChunkSignature1进行比对。
9、构建第二个分块的签名并发送数据: 将ChunkSignature1、本分块数据的Checksum、当前时间、地域、服务名称,使用公开算法,计算出ChunkSignature2,并且携带在分块数据的HTTP Body开头。
详细计算方式同第7步。
10、服务端接收数据并验证分块签名: 通过公开算法,计算签名并和ChunkSignature2进行比对。
......
11、构建第N个分块的签名并发送数据: 将ChunkSignatureN-1、本分块数据的Checksum、当前时间、地域、服务名称,使用公开算法,计算出ChunkSignatureN,并且携带在分块数据的HTTP Body开头。
详细计算方式同第7步。
12、服务端接收数据并验证分块签名: 通过公开算法,计算签名并和ChunkSignatureN进行比对,请求完成。
技术优势
- 通过流式上传的方式,能够避免客户端需要先读入所有数据,提前计算Checksum之后,再进行上传,导致的性能浪费。
- 对于未知长度的数据,可以不用预知数据的Checksum,可以边传输边计算分块Checksum,从而保证传输安全。
- 有效抗击中间人攻击,使其无法单独篡改某个分块而不破坏签名链。
- 服务端逐块验证签名,无需等待全部传输完成才发现传输异常,能提前识别异常,节省客户端开销。
端到端完整性防护:尾部校验机制
在多分块上传格式的基础上,支持在数据的末尾,通过Trailing Header的形式,携带完整数据的Checksum,用于服务端的数据完整性和正确性校验。
示意图
1、尾部校验流程意图
原理
1、构造规范化请求: 客户端将待发送HTTP请求的Method、URI、Query、Headers、HTTP Body的Checksum通过固定的顺序进行排列,得到CanonicalRequest字符串。
CanonicalRequest =
HTTPMethod + '\n' +
CanonicalURI + '\n' +
CanonicalQueryString + '\n' +
CanonicalHeaders + '\n' +
SignedHeaders + '\n' +
HashedPayload
参数说明见附录一。
2、创建签名字符串(StringToSign): 将请求时间、地域、服务名称、CanonicalRequest的Hash值等信息拼接成一个字符串。
StringToSign = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HexEncode(Hash(CanonicalRequest))
参数说明见附录二。
3、计算签名密钥(SigningKey): 客户端基于自身账号的私钥(SecretKey),结合当前时间、地域、服务名称等信息,计算出一个临时的签名SigningKey。
SecretKey = *Secret Access Key*
DateKey = HMAC-SHA256(SecretKey, Date)
DateRegionKey = HMAC-SHA256(DateKey, Region)
DateRegionServiceKey = HMAC-SHA256(DateRegionKey, "tos")
SigningKey = HMAC-SHA256(DateRegionServiceKey, "request")
4、计算签名: 客户端将CanonicalRequest和SigningKey通过公开的算法计算出签名Signature。
Signature = HexEncode(HMAC-SHA256(SigningKey, StringToSign))
5、构建Authorization Header: 客户端将账号的公钥(AccessKey)、构造规范化请求的构造范围、Signature字符串,添加在请求的Header中。
Authorization: TOS4-HMAC-SHA256 Credential={AccessKeyId}/{CredentialScope},SignedHeaders={SignedHeaders}, Signature={Signature}
6、服务端验证签名: 通过公开算法,计算签名并和Header中的签名对比。
7、发送第一个分块数据: 发送数据的同时,在客户端本地流式计算本分块的Checksum,缓存在客户端内存中。
8、发送第二个分块数据: 发送数据的同时,在客户端本地流式计算本分块的Checksum,缓存在客户端内存中。
......
9、发送第N个分块数据: 发送数据的同时,在客户端本地流式计算本分块的Checksum,缓存在客户端内存中。
10、发送Trailing Header: 将完整数据的Checksum以固定的格式追加到有效数据末尾。
x-tos-hash-crc64ecma:Base64EncodeChecksum
- x-tos-hash-crc64ecma:TOS支持Crc64ECMA、Crc32、SHA256、SHA1等Checksum算法。这里示例为Crc64ECMA。
- Base64EncodeChecksum:将完整数据的Checksum,进行Base64编码后得到的定长数据。
11、服务端验证Trailing Checksum: 在接收分块数据时,流式计算Checksum,并和Trailing Header的Checksum进行比对,验证通过后继续服务。
技术优势
- 通过流式计算Checksum的方式,避免客户端需要先读入所有数据,提前计算Checksum之后,再进行上传,导致的性能浪费。
- 对于未知长度的数据,可以不用预知数据的Checksum,可以边传输边计算分块Checksum,从而保证传输安全。
- 能够端到端验证数据的完整性和正确性。
- 支持和多分块链式签名同时使用,同时使用时可以结合二者的优点,既能逐块验证数据正确性,提前发现数据错误,又能端到端完整验证数据正确性。同时多分块链式签名中使用HMAC-SHA256算法计算分块Checksum,Trailing Checksum使用Crc64算法计算Checksum,使用不同的算法可以降低Hash碰撞概率。
方案 | 传输过程防篡改 | 端到端Checksum校验 | 流式Checksum校验 | 客户端内存占用优化 |
---|---|---|---|---|
HTTPS | ✅ | ❌ | ❌ | ❌ |
TOS V4签名 | ✅ | ✅ | ❌ | ❌ |
多分块链式签名 | ✅ | ❌ | ✅ | ✅ |
尾部校验 | ✅ | ✅ | ❌ | ✅ |
多分块链式签名 + 尾部校验 | ✅ | ✅ | ✅ | ✅ |
TOS兼容S3 Multiple Chunks签名的访问请求。
同时也兼容S3 Multiple Chunks Including Trailing Headers的访问请求。
目前AWS S3 SDK默认采用Multiple Chunks签名的访问请求,国内云厂商暂时只有TOS公开支持。
协议层建议采用HTTPS的方式进行传输。
应用层的保护措施,建议“多分块链式签名”和“尾部校验”中至少选择一种,或者两种同时使用进行上传。
-
HTTPMethod:HTTP 请求的 Method,如 PUT、GET、HEAD、DELETE 等。
-
CanonicalURI :UriEncode( <PATH> )
- 如果 URL 中的 PATH 为空,则 CanonicalURI为/。
- 如果完整路径为 https://bucketname.tos-cn-beijing.volces.com/object,则CanonicalURI为/object。
-
CanonicalQueryString:请求中 Query 参数的编码格式:
-
UriEncode( ) + '=' + UriEncode( ) + '&' + UriEncode( ) + '=' + UriEncode( ) + '&' +
-
...
-
UriEncode( ) + '=' + UriEncode( )
- 请求中的 Query 参数编码后按照 ASCII 字节顺序进行上述格式处理。
- 所有 Query 参数必须参与计算。
- 如果没有 Query 参数,则 CanonicalQueryString 为空字符串。
-
-
CanonicalHeaders :请求中 Header 的编码格式:
Lowercase( ) + ':' + Trim( ) + '\n'
Lowercase( ) + ':' + Trim( ) + '\n'
...
Lowercase( ) +':' + Trim( ) + '\n'
- HeaderName 按字典序排列。
- CanonicalHeaders 不必包含全部头域。
- 如果请求中存在 Content-Type 头域,则 CanonicalHeaders 必须包含该头域。
- CanonicalHeaders 必须包含 host 头域。
- 使用临时 AK/SK 鉴权时,必须携带 x-tos-security-token,CanonicalHeaders 必须包含该头域。
- CanonicalHeaders 必须包含所有 x-tos-* 的头域。
-
SignedHeaders:指明参与签名的 Header 有哪些:
Lowercase( ) + ';' + Lowercase( ) + ... + Lowercase( )
- CanonicalHeaders 中的 HeaderName 按字典序排列。
- SignedHeaders 里的 HeaderName 必须在请求的头域中。
-
HashedPayload
- TOS V4签名携带Checksum方式:Hex(SHA256Hash()
- 多分块链式签名:STREAMING-TOS4-HMAC-SHA256-PAYLOAD
- 尾部校验:STREAMING-UNSIGNED-PAYLOAD-TRAILER
- 多分块链式签名 + 尾部校验:STREAMING-TOS4-HMAC-SHA256-PAYLOAD-TRAILER
参数 | 说明 |
---|---|
Algorithm | 指代签名的算法,目前仅支持 HMAC-SHA256 的签名算法。 |
RequestDate | 指代请求 UTC 时间,请使用如下格式:yyyyMMddTHHmmssZ。 |
CredentialScope | 指代凭证范围值,格式为:yyyyMMdd/region/tos/request。 |
CanonicalRequest | 指代规范化请求的结果。 |