PHP生成腾讯云COS接口需要的请求签名
COS和请求签名是什么
COS是腾讯云对象存储的缩写及简称,请求签名是第三方在调用COS相关接口时需要按需提供的、经过特定算法创建而成的一组字符串信息,将唯一的标识当前第三方身份,提供通信双方的身份识别,只有有效的签名COS才会提供服务
目标
使用PHP创建COS接口所需要的请求签名,与官方文档给出的示例做比较,验证算法的正确性
认识请求签名
先来看一条官方文档给出的请求签名的样子
q-sign-algorithm=sha1&q-ak=[SecretID]&q-sign-time=[SignTime]&q-key-time=[KeyTime]&q-header-list=[SignedHeaderList]&q-url-param-list=[SignedParameterList]&q-signature=[Signature]
请求签名特点总结
- 是一串字符串
- key=value的键值对格式,key为固定值
- 一共有7对key=value
- sha1也是参数,但截止到官方发文只支持sha1,因此可以直接赋值
- SignedHeaderList、SignedParameterList、Signature三个value需要通过算法生成
键值对的具体描述参见官方文档。
请求签名一共需要7个值,下面一一讲解,各个击破
签名算法,官方目前仅支持sha1,因此直接给值即可
账户ID,即用户的SecretId,可以在控制台 云API密钥 页面获取
当前签名的有效起止时间,Unix时间戳格式,英文半角分号 ; 分割,格式如 1480932292;1481012298
与q-sign-time值相同
个人理解,由HTTP请求头组成,取全部或部分请求头,将 key:value 形式的请求项的key部分取出,转化小写,多个key按字典排序,以字符 ; 连接,最终组成字符串
如原始请求头有两个:
Host:bucket1-1254000000.cos.ap-beijing.myqcloud.com
Content-Type:image/jpeg
key就是Host和Content-Type,经过运算后输出 content-type;host
个人理解,由HTTP请求参数组成,取全部或部分请求参数,将 key=value 形式的请求参数的key部分取出,转化小写,多个key按字典排序,以字符 ; 连接,最终组成字符串
如原始HTTP请求为:
GET/?prefix=abc&max-keys=20
key就是prefix和max-keys,经过运算后输出 max-keys;prefix,如果请求没有参数比如put、post,此处即为空
根据HTTP内容计算签名,算法由COS提供,只需按要求给值
在开始编写逻辑之前,先看一下官方示例给出的参考值,以及经过计算后的结果,以便和自己开发的逻辑进行结果比对
HTTP原始请求,也可以理解为计算签名前或不需要签名时的HTTP请求:
PUT/testfile2HTTP/1.1
Host:bucket1-1254000000.cos.ap-beijing.myqcloud.com
x-cos-content-sha1:7b502c3a1f48c8609ae212cdfb639dee39673f5e
x-cos-storage-class:standardHelloworld
计算签名后应该得到的HTTP请求:
PUT/testfile2HTTP/1.1
Host:bucket1-1254000000.cos.ap-beijing.myqcloud.com
x-cos-content-sha1:7b502c3a1f48c8609ae212cdfb639dee39673f5e
x-cos-storage-class:standard
Authorization:q-sign-algorithm=sha1&q-ak=AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q&>q-sign-time=1417773892;1417853898&q-key-time=1417773892;1417853898&q-header-list=host;x-cos-content-sha1;x-cos-storage-class&q-url-param-list=&q-signature=14e6ebd7955b0c6da532151bf97045e2c5a64e10Helloworld
结论:算法如果能得到Authorization后的那一串字符串即为正确
来看一下(官方提供的)用户信息以及HTTP信息:
- SecretId:AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q
- SecretKey:BQYIM75p8x0iWVFSIgqEKwFprpRSVHlz
- 签名有效起始时间:1417773892
- 签名有效停止时间:1417853898
- HTTP原始请求头:根据上一节示例不难得到HTTP原始请求有三项内容Host、x-cos-content-sha1和x-cos-storage-class
- HTTP请求参数:是PUT请求,没有?参数
将准备工作中的各项参数带入请求签名规则,不难就可以得到结果,如下表:
键(key) | 值(value) | 备注 |
---|---|---|
q-sign-algorithm | sha1 | 目前仅支持sha1签名算法 |
q-ak | AKIDQjz3ltompVjBni5LitkWHFlFpwkn9U5q | SecretId字段 |
q-sign-time | 1417773892;1417853898 | 2014/12/518:04:52到2014/12/616:18:18 |
q-key-time | 1417773892;1417853898 | 2014/12/518:04:52到2014/12/616:18:18 |
q-header-list | host;x-cos-content-sha1;x-cos-storage-class | HTTP头部key的字典顺序排序列表 |
q-url-param-list | HTTP参数列表为空 | |
q-signature | 14e6ebd7955b0c6da532151bf97045e2c5a64e10 | 通过代码计算所得 |
但q-signature怎么来的?
刚才说到,q-signature也需要特定算法计算得来,下面就说明如何计算
计算请求签名
先看代码:
/** *计算签名 *secretId、secretKey为必需参数,qSignStart、qSignEnd为调试需要,测试通过后应取消,改为方法内自动创建 */ functionget_authorization($secretId,$secretKey,$qSignStart,$qSignEnd,$fileUri,$headers){ /* *计算COS签名 *2018-05-17 *author:cinlap*ref:https://cloud.tencent.com/document/product/436/7778 */ $qSignTime="$qSignStart;$qSignEnd";//unix_timestamp;unix_timestamp $qKeyTime=$qSignTime; $header_list=get_q_header_list($headers); //如果Uri中带有?的请求参数,该处应为数组排序后的字符串组合 $url_param_list=''; //computesignature $httpMethod='put'; $httpUri=$fileUri; //与q-url-param-list相同 $httpParameters=$url_param_list; //将自定义请求头分解为&连接的字符串 $headerString=get_http_header_string($headers); //计算签名中的signature部分 $signTime=$qSignTime; $signKey=hash_hmac('sha1',$signTime,$secretKey); $httpString="$httpMethod\n$httpUri\n$httpParameters\n$headerString\n"; $sha1edHttpString=sha1($httpString); $stringToSign="sha1\n$signTime\n$sha1edHttpString\n"; $signature=hash_hmac('sha1',$stringToSign,$signKey); //组合结果 $authorization="q-sign-algorithm=sha1&q-ak=$secretId&q-sign-time=$qSignTime&q-key-time=$qKeyTime&q-header-list=$header_list&q-url-param-list=$url_param_list&q-signature=$signature"; return$authorization; }
为了测试,该方法参数应该是多过需要了,前六个参数是已经给出的,是来自用户的,因此直接赋值即可得到下边字符串:
$authorization="q-sign-algorithm=sha1&q-ak=$secretId&q-sign-time=$qSignTime&q-key-time=$qKeyTime...
$header_list这个值要符合q-header-list规则因此需要计算,逻辑是上文已经描述,是从既定的请求项中抽出key组成有序字符串,代码如下:
/** *按COS要求对header_list内容进行转换 *提取所有key *字典排序 *key转换为小写 *多对key=value之间用连接符连接 * */ functionget_q_header_list($headers){ if(!is_array($headers)){ returnfalse; } try{ $tmpArray=array(); foreach($headersas$key=>$value){ array_push($tmpArray,strtolower($key)); } sort($tmpArray); returnimplode(';',$tmpArray); } catch(Exception$error){ returnfalse; } }
$url-param-list上面讲过,这个值是HTTP请求参数,对于PUT方法没有?参数,自然值为空,所以代码中“偷懒”直接给了空字符串。
Signature的计算和需要小心的地方
官方已经给出了完整的算法,PHP甚至还有写好的代码,应该是很幸福了(但!由于看官方文档看的头晕还是踩了坑,随后一起说明),先看一下signature的“格式”:
SignKey=HMAC-SHA1(SecretKey,"[q-key-time]")
HttpString=[HttpMethod]\n[HttpURI]\n[HttpParameters]\n[HttpHeaders]\n
StringToSign=[q-sign-algorithm]\n[q-sign-time]\nSHA1-HASH(HttpString)\n
Signature=HMAC-SHA1(SignKey,StringToSign)
再看一下Signature的完整算法:
$signTime=$qSignTime;
$signKey=hash_hmac('sha1',$signTime,$secretKey);
$httpString="$httpMethod\n$httpUri\n$httpParameters\n$headerString\n";
$sha1edHttpString=sha1($httpString);
$stringToSign="sha1\n$signTime\n$sha1edHttpString\n";
$signature=hash_hmac('sha1',$stringToSign,$signKey);
$signTime:很简单,起止时间组成的字符串,从上文拿来直接用
$signKey:HMAC-SHA1算法直接计算即可
$httpString:四个部分组成需要分开说
1、$httpMethod:HTTP请求方法,小写,比如put、get
2、$httpUri:HTTP请求的URI部分,从“/”虚拟根开始,如/testfile说明在存储桶根目录下创建一个叫testfile的文件,/image/face1.jpg说明在根目录/image目录下建立一个叫face1.jpg的文件,至于是不是图片文件,不管
3、$httpParameters:这是第一个需要小心的地方。由HTTP原始请求参数组成,即请求URI中?后面的部分,本例调用的是PUTObject接口,因此为空。如果不为空,需要把请求参数每一项的key和value均转换小写,多对key=value按字典排序并以&相连接
4、$headerString:这是第二个需要小心的地方,由HTTP原始请求头组成,根据请求头,选择全部或部分请求头,把每项的key都转换为小写,把value都进行URLEncode转换,每项格式都改为key=value,然后按照key进行字典排序,最后把它们用连接符&组成字符串。这是我整理的逻辑,代码如下:
/** *按COS要求从数组中获取Signature中[HttpString]内容 *标准格式key=value&key=value&... *数组元素按键字典排序* *key转换为小写 *value进行UrlEncode转换 *转换为key=value格式 *多对key=value之间用连接符连接 * */ functionget_http_header_string($headers){ if(!is_array($headers)){ returnfalse; } try{ $tmpArray=array(); foreach($headersas$key=>$value){ $tmpKey=strtolower($key); $tmpArray[$tmpKey]=urlencode($value); } ksort($tmpArray); $headerArray=array(); foreach($tmpArrayas$key=>$value){ array_push($headerArray,"$key=$value"); } returnimplode('&',$headerArray); } catch(Exception$error){ returnfalse; } }
为什么要小心?
HTTP原始请求头和请求参数用在了四个地方,分别是请求签名里的q-header-list和Signature里的HttpHeaders——两者都用到了HTTP原始请求头;请求签名里的q-url-param-list和Signature里的HttpParameters——两者都用到了HTTP请求参数。一定要保证HTTP请求头和请求参数所选用的数量和对象一致
- 相同:生成q-header-list的HTTP请求头数量和成员要和生成HttpHeaders的相同,生成q-url-param-list的HTTP请求参数数量和成员要和生成HttpParameters的相同
- 不同:q-header-list和q-url-param-list只取key部分,HttpHeaders和HttpParameters取key和value部分
输出结果和校验
至此,请求签名中7个值都有了,有的是来自用户信息,有的需要计算,需要计算的上面也给出了所有的计算方法和为什么如此计算的个人理解。最后只需要按照官方要求进行输出即可。看一下