Cloudflare对象存储R2试用与签名计算
2023-5-28 01:11:17 Author: 墨雪飘影(查看原文) 阅读量:47 收藏

1、各个对象存储价格
最近有不少文件,本地存了好多,还有些可能需要发出去或者在其他地方使用,准备用网盘,但是网盘速度实在是慢,开会员也用不了几次,不是很频繁,正好赶上阿里云核心产品降价(腾讯云也跟进了),所以考虑用对象存储,看了几个厂商的。

阿里云(部分)

腾讯云(部分)

百度云(部分)

华为云(部分)

天翼云(部分)

京东云(部分)

国内的云厂商价格都差不多,主要是流量贵,买资源包也不在我的接受范围,所以看了下其他的(亚马逊是下载慢所以没有考虑,它家的EC2还是用过的)。
找到个Cloudflare的对象存储R2,按操作计费(其他厂商也有),分为A、B类操作,存储免费10G、流量免费、A类操作免费额度100W次/月、B类操作免费额度1000W次/月。

Cloudflare R2

据说速度也可以,我还没具体测。

A、B操作

A可以看成写入,B读取。实际上浮动很大,我不知道为什么,我没公开存储桶,结果莫名其妙出现1000多次B类操作,不过额度够多,一般来说不会达到付费门槛。
注意:绑定自定义域名必须在cloudflare托管DNS解析。
R2的API是和S3(亚马逊对象存储)兼容的,所以可以用S3Browser客户端,签名算法也是AWS Signature(好像国内几家都是用的这个)。
用签名算法计算一个预签名地址就可以不用绑定自定义域名、不公开存储桶来访问文件。
2、签名计算

V4 签名计算过程

https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sig-v4-header-based-auth.html

这个过程其实就是把请求的headers和请求参数等组合起来做哈希。
签名计算过程伪代码:https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/create-signed-request.html
import base64import datetime,hashlib,hmacimport jsonfrom hashlib import sha256
import requests

class SignUtil: def __header(self,account_id,bucket_name,key_name,domain): host = "https://{}.r2.cloudflarestorage.com".format(account_id) CanonicalUri = '/{}/{}'.format(bucket_name, key_name) if domain != None: host = domain CanonicalUri = '/{}'.format(key_name) t = datetime.datetime.utcnow() amzdate = t.strftime('%Y%m%dT%H%M%SZ') datestamp = t.strftime('%Y%m%d') return host,CanonicalUri,amzdate,datestamp
def __sign(self,key, data): return hmac.new(key, data.encode(), digestmod=sha256).digest()
def __getSignature(self,datestamp,amzdate,region,canonical_request,access_key_secret): algorithm = 'AWS4-HMAC-SHA256' credential_scope = datestamp + '/' + region + '/' + 's3' + '/' + 'aws4_request' string_to_sign = algorithm + '\n' + amzdate + '\n' + credential_scope + '\n' + hashlib.sha256( canonical_request.encode('utf-8')).hexdigest() print('加密字符串:\n' + string_to_sign) kDate = self.__sign(("AWS4" + access_key_secret).encode(), datestamp) kRegion = self.__sign(kDate, region) kService = self.__sign(kRegion, 's3') kSigning = self.__sign(kService, "aws4_request") signature = self.__sign(kSigning, string_to_sign) return signature.hex()     def getSign(self,account_id,access_key_id,access_key_secret,region,bucket_name,key_name,expires=3600,isurl=True,domain=None): host, CanonicalUri, amzdate, datestamp=self.__header(account_id,bucket_name,key_name,domain)
HTTPMethod = 'GET'
if isurl: CanonicalQueryString = 'X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential='+access_key_id+'%2F' + datestamp + '%2F'+region+'%2Fs3%2Faws4_request&X-Amz-Date=' + amzdate + '&X-Amz-Expires='+str(expires)+'&X-Amz-SignedHeaders=host' CanonicalHeaders = 'host:'+ host.replace("https://","") + '\n' SignedHeaders = 'host' canonical_request = HTTPMethod + '\n' + CanonicalUri + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + 'UNSIGNED-PAYLOAD'
print("准备加密请求:" + canonical_request) headersSign = hashlib.sha256((canonical_request).encode('utf-8')).hexdigest() print("请求签名:" + headersSign) signature=self.__getSignature(datestamp,amzdate,region,canonical_request,access_key_secret) signtext = 'X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=' + access_key_id + '/' + datestamp + '/'+region+'/s3/aws4_request' + '&X-Amz-Date=' + amzdate + '&X-Amz-Expires='+str(expires)+'&X-Amz-SignedHeaders=' + SignedHeaders + '&X-Amz-Signature=' + signature return host+CanonicalUri+'?'+signtext else: CanonicalQueryString = '' CanonicalHeaders = 'host:'+host.replace("https://","") + '\n' + 'x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n' + 'x-amz-date:' + amzdate + '\n' SignedHeaders = 'host;x-amz-content-sha256;x-amz-date' canonical_request = HTTPMethod + '\n' + CanonicalUri + '\n' + CanonicalQueryString + '\n' + CanonicalHeaders + '\n' + SignedHeaders + '\n' + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' print("准备加密请求:" + canonical_request) headersSign = hashlib.sha256((canonical_request).encode('utf-8')).hexdigest() print("请求签名:" + headersSign) signature = self.__getSignature(datestamp, amzdate, region, canonical_request, access_key_secret) auth = 'AWS4-HMAC-SHA256 Credential=' + access_key_id + '/' + datestamp + '/'+region+'/s3/aws4_request' + ',SignedHeaders=host;x-amz-content-sha256;x-amz-date' + ', Signature=' + signature headers = { "Authorization": auth, "host": host.replace("https://",""), "x-amz-content-sha256": 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', "x-amz-date": amzdate } return headers
其中x-amz-content-sha256是V4必须得参数,值就是body的sha256,如果没有body就用空字符的sha256值或者“UNSIGNED-PAYLOAD
把他们拼起来就可以计算签名了,上面的请求可以换成PUT,在PUT请求下可以直接上传文件到存储桶。
如果是POST请求就需要传policy。

policy签名计算

https://docs.aws.amazon.com/zh_cn/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html

其实签名计算可以用亚马逊的SDK,Python的叫boto3,但是这个组件初始化太慢,初始化要几秒钟,可以在启动程序的时候初始化,后面计算签名直接调用,速度就会快很多,我是想试试自己算签名,所以自己实现了一遍,10G够临时存取文件了(或者多开点账号)。

上面是我自己演示用的(http://dc.shikangsi.com:10003)随时会失效,这个服务只用做上传、下载文件的接口就行了,每次请求下载文件的时候计算一个签名就可以了,用签名来控制上传文件的大小和下载链接的有效期,好像R2还支持设置对象生命周期,让它指定时间删除,就可以循环使用10G的空间了。

文章来源: http://mp.weixin.qq.com/s?__biz=MzI3NzI4OTkyNw==&mid=2247488862&idx=1&sn=2ea03a84263588a4071188edea0fd3c5&chksm=eb69dbf6dc1e52e0b40590c4d0f21279c6fe32e8b22557a2b3e1ea2a70dc3def73ffacce5345#rd
如有侵权请联系:admin#unsafe.sh