你的文件真的没被篡改?TOS 流式安全传输方案解析

基础存储服务存储云存储
序言

在对象存储系统中,保障数据不丢不错,是系统的底线。

而要保障数据不丢不错,非常重要的一环,是要保障网络传输过程中的数据安全。

网络传输过程中的常见问题分为三类:

  • 数据不完整。对象存储系统中,数据包含了文件内容和文件属性。

    • 文件内容通过HTTP Body传输,当数据不完整时,本来应该传输100MB的数据,但实际只传输了80MB,用户读取时会发现数据少了一部分。
    • 文件属性通过HTTP Header传输,包含了Acl、加密算法、Content-Type等属性。当出现丢失时,用户会发现文件的元数据少了一部分。
  • 数据非故意的损坏。在网络传输过程中,网卡作为传输设备,传输过程中因为线缆、接口、内部器件等问题,可能出现位翻转的情况,数据的内容会由0变为1,或者由1变为0,导致虽然文件长度是正确的,但是文件内容出错。

  • 中间人攻击:指攻击者恶意拦截请求,并且篡改数据内容的行为,同样会导致数据的丢失或者损坏。

一旦在数据传输过程中,数据发生异常或损坏,对于企业都是致命的。这种风险可能直接导致业务中断、客户信任崩塌甚至法律纠纷,在数字化转型加速的今天,数据完整性已成为企业生命线。

例如在某次自动驾驶AI训练中,训练集群接收来自数据中心的训练数据,在网络传输过程中,由于交换机固件缺陷,发生了间歇性位翻转错误,导致接收到的点云数据中的障碍物高度参数出现偏差。最终在模型验证时,将实际高度1.8米的限高杆识别为2.3米,对于AI模型的实际应用引入了潜在的高危风险。

picture.image

在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签名构造示意图

picture.image

2、 TOS V4签名验证示意图

picture.image

原理

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的校验结果来拦截请求,造成了带宽和性能上的浪费。
TOS推出流式安全传输方案

边传边验:多分块链式签名验证机制

将请求的HTTP Body拆分为多个固定长度或者可变长度的分块进行上传,每个分块都拥有独立的分块签名,本分块签名中会包含分块数据的Checksum信息以及前一个分块的签名,服务端在接受数据时,会边接收数据边验证签名的正确性。

示意图

1、多分块链式签名构造示意图

picture.image

2、多分块链式签名验证示意图

picture.image

原理

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、尾部校验流程意图

picture.image

原理

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签名
多分块链式签名
尾部校验
多分块链式签名 + 尾部校验
AWS S3 SDK兼容性

TOS兼容S3 Multiple Chunks签名的访问请求。

同时也兼容S3 Multiple Chunks Including Trailing Headers的访问请求。

目前AWS S3 SDK默认采用Multiple Chunks签名的访问请求,国内云厂商暂时只有TOS公开支持。

最佳实践建议

协议层建议采用HTTPS的方式进行传输。

应用层的保护措施,建议“多分块链式签名”和“尾部校验”中至少选择一种,或者两种同时使用进行上传。

附录一:CanonicalRequest参数说明
  • HTTPMethod:HTTP 请求的 Method,如 PUT、GET、HEAD、DELETE 等。

  • CanonicalURI :UriEncode( <PATH> )

  • 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
附录二:StringToSign参数说明
参数说明
Algorithm指代签名的算法,目前仅支持 HMAC-SHA256 的签名算法。
RequestDate指代请求 UTC 时间,请使用如下格式:yyyyMMddTHHmmssZ。
CredentialScope指代凭证范围值,格式为:yyyyMMdd/region/tos/request。
CanonicalRequest指代规范化请求的结果。
0
0
0
0
关于作者
所属团队号:
关于作者

文章

0

获赞

0

收藏

0

所属团队号:
相关资源
字节跳动 NoSQL 的实践与探索
随着 NoSQL 的蓬勃发展越来越多的数据存储在了 NoSQL 系统中,并且 NoSQL 和 RDBMS 的界限越来越模糊,各种不同的专用 NoSQL 系统不停涌现,各具特色,形态不一。本次主要分享字节跳动内部和火山引擎 NoSQL 的实践,希望能够给大家一定的启发。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论