需求场景
公司用户最近需要对自己工厂的设备进行监控,采买了海康摄像头,期望在当下开发的管理体系里,能够直接检查各个设备的实时监控。
发现问题
对接海康敞开渠道的接口,第一步就是要获取一个签名值。然后发起请求时,header里头部要带上X-Ca-Signature的参数,把这个签名值带上。
接口请求header内容大约如下:
Content-Type:application/json
X-Ca-Key:29584125
X-Ca-Signature:G0cNBkstmt0idBUP4dGaecqgOmu4CKNknG3rJ7tAfQw=
X-Ca-Signature-headers:x-ca-key,x-ca-timestamp
Accept:*/*
X-Ca-Timestamp:16848960298234
然后我模拟Java程序,用php写了一段如下代码来生成签名:
成果跟java程序得到的签名值完全不一样。
检查过程中,检查$a数组的值,发现多了很多32的值:
[80,79,83,84,13,10,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32,32...
这些32,正好代表的是上面图中红框的空格字符。
然后我继续调整php程序如下:
$str = 'POST'.PHP_EOL;
$str .= '*/*'.PHP_EOL;
$str .= 'application/json'.PHP_EOL;
$str .= 'x-ca-key:29584125'.PHP_EOL;
$str .= 'x-ca-timestamp:16848960298234'.PHP_EOL;
$str .= '/artemis/api/resource/v1/cameras';
$secret = 'UGmBrTB9OVa0rtOsYhd7';
$php_res = hash_hmac('sha256', $str, $secret, true);
echo base64_encode($php_res);
但是得到的签名值依然不对。
通过一番捣腾,真实不知道问题出在哪里。内心怀疑会不会是算法压根就不对,只好也在java程序里打印出拼接的字符串,期望能看出一些什么:
public static String sign(String secret, String method, String path, Map headers, Map querys, Map bodys, List signHeaderPrefixList)
{
try
{
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
byte keyBytes[] = secret.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, "HmacSHA256"));
String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList);
//本人增加的两行打印句子
System.out.println(stringToSign);
System.out.println(Arrays.toString(stringToSign.getBytes("UTF-8")));
return new String(Base64.encodeBase64(hmacSha256.doFinal(stringToSign.getBytes("UTF-8"))), "UTF-8");
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
运转后,java打印成果如下:
POST
*/*
application/json
x-ca-key:29584125
x-ca-timestamp:16848960298234
/artemis/api/resource/v1/cameras
[80, 79, 83, 84, 10, 42, 47, 42, 10, 97, 112, 112, 108, 105, 99, 97, 116, 105, 111, 110, 47, 106, 115, 111, 110, 10, 120, 45, 99, 97, 45, 107, 101, 121, 58, 50, 57, 53, 56, 52, 49, 50, 53, 10, 120, 45, 99, 97, 45, 116, 105, 109, 101, 115, 116, 97, 109, 112, 58, 49, 54, 56, 52, 56, 57, 54, 48, 50, 57, 56, 50, 51, 52, 10, 47, 97, 114, 116, 101, 109, 105, 115, 47, 97, 112, 105, 47, 114, 101, 115, 111, 117, 114, 99, 101, 47, 118, 49, 47, 99, 97, 109, 101, 114, 97, 115]
但在php里,我将拼接字符串每个字符的ASCII码打印出来,成果如下:
[80,79,83,84,13,10,42,47,42,13,10,97,112,112,108,105,99,97,116,105,111,110,47,106,115,111,110,13,10,120,45,99,97,45,107,101,121,58,50,57,53,56,52,49,50,53,13,10,120,45,99,97,45,116,105,109,101,115,116,97,109,112,58,49,54,56,52,56,57,54,48,50,57,56,50,51,52,13,10,47,97,114,116,101,109,105,115,47,97,112,105,47,114,101,115,111,117,114,99,101,47,118,49,47,99,97,109,101,114,97,115]
将两个数组一对照,就发现,php数组里解析换行符,都是13和10两个数字,正好对应ASCII表里的\r和\n字符:比java程序的多了一个\r的回车符。
这时才意识到,本来问题一向出在换行符上。
Windows下换行符默许是\r\n,Linux下默许是\n
这个常识点我是知道的,仅仅在这次编写程序时,却底子没想到是这个影响到的。
最终解决
直接将换行符提前定义好,这样就不会自动被识别成Windows渠道的换行符了。
//php代码,留意第一行要用双引号
$next = "\n";
$str = 'POST'.$next;
$str .= '*/*'.$next;
$str .= 'application/json'.$next;
$str .= 'x-ca-key:29584125'.$next;
$str .= 'x-ca-timestamp:16848960298234'.$next;
$str .= '/artemis/api/resource/v1/cameras';
$secret = 'UGmBrTB9OVa0rtOsYhd7';
$php_res = hash_hmac('sha256', $str, $secret, true);
echo base64_encode($php_res);
所以在以后相似这种转化或加密的操作时,必定要留意换行、空格等这些不可见的字符对程序的影响。