公司用户最近需要对自己工厂的设备进行监控,采买了海康摄像头,希望在当下开发的管理系统里,能够直接查看当前设备的实时监控。
调用海康开放平台的接口,header里有一个X-Ca-Signature的参数,需要传安全认证的签名字符串。
海康开放平台本身有提供开发包,我需要参考其签名逻辑,将其转换为php程序。
关于海康认证方式的介绍,你可以看这里:海康签名算法
(转成php代码,其实网上本来就有,很简单,不算啥难点)
1.先通过运行海康提供的开发包程序,得到一个签名值。
2.然后用Postman调通海康接口,证明签名值正确。
3.最后编写php程序,如果最终得到的签名值跟java程序跑的结果一模一样,那就证明php实现成功了。
如果想了解本人运行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,正好代表的是上面图中红框的空格字符。
然后程序调整如下:
$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);
获得的签名却仍然不对。
一直以为,要么自己拼接的字符串内容有问题,要么就是算法不对,即执行了hash_hmac方法,是不是还要做一些额外处理什么的?
就在一步步对照java程序和php程序的运行结果时,才发现问题出在换行符上。
windows下换行符默认为\r\n,linux下换行符为\n
海康开发包里生成签名的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);
}
}
运行后,打印结果如下:
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码打印出来。
$a = []; for($i=0; $i< strlen($str); $i++){ $a[] = ord($str[$i]); } var_dump(json_encode($a));
结果如下:
[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的回车符。
所以上面php程序只是在Windows下有问题,放到Linux服务器下执行,也是能获得正确的签名的。
最终把php代码里字符串拼接换成如下就OK了:
$str = "POST\n*/*\napplication/json\nx-ca-key:29584125\nx-ca-timestamp:16848960298234\n/artemis/api/resource/v1/cameras";
(注意用双引号,用单引号的话,\n会被识别成\和n这样的两个字符)
本文为翟码农个人博客蓝翟红尘里php分类下有关对接海康接口的原创文章,转载请注明出处:对接海康开放平台,被一个换行符折磨了 - 翟码农技术博客 (zhai14.com)
http://www.zhai14.com/blog/I-was-tortured-by-the-line-break-symbol-during-the-abutment-with-haikang-api.html
1. 通过海康开发包获取签名
我下载的海康开发包是这个:OpenAPI安全认证库-JAVA版本。
里面有一个artemis-http-client-1.1.8.jar这样的文件,里面就有这个签名如何生成的方法。
jar包解压后,文件都是编译后的,所以需要先反编译。
反编译工具下载地址:https://pan.baidu.com/s/1wqUCk38uxaPUZIc2LJn4ZA?pwd=ruzn,提取码: ruzn
然后执行如下命令:
./jad.exe -o -r -sjava -dsrc com/**/*.class
就可以将多层级目录下的class文件全部反编译,生成java后缀的文件,然后我们就可以看到源代码了。
反编译命令的更多用法,可见Readme.txt文件。
然后自己创建一个main文件,调用签名方法,打印数据就可以了。
import java.util.HashMap;
import java.util.Map;
import com.hikvision.artemis.sdk.util.*;
public class test {
public static void main(String[] args) {
// TODO Auto-generated method stub
SignUtil su = new SignUtil();
String secret = "UGmBrTB9OVa0rtOsYhd7";
String method = "POST";
String path = "/artemis/api/resource/v1/cameras";
Map<String, String> header = new HashMap<>();
header.put("Accept", "*/*");
header.put("Content-Type", "application/json");
header.put("x-ca-key", "29584125");
header.put("x-ca-timestamp", "16848960298234");
String res = SignUtil.sign(secret, method, path, header, null, null, null);
System.out.println(res.toString());
}
}
结果签名值:
G0cNBkstmt0idBUP4dGaecqgOmu4CKNknG3rJ7tAfQw=
好久都没折腾Java了,几乎忘得一干二净,所以在获取到签名值之前,也是花了一些时间的。
具体内容下回分享。
2. 通过postman调试接口
接口请求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
需要注意的是:
header里要带上X-Ca-Signature-headers这一项参数,而生成签名的程序里,不需要拼接这一段
这个参数,会告诉海康综合安防平台哪些字段参与了签名的生成,所以举个例子,如果签名的生成还用到了ounce字段,postman里测试请求,header里除了要带上ounce字段,X-Ca-Signature-headers字段的值也需要变成如下:
x-ca-key,x-ca-timestamp,ounce
如果始终都不成功,也要质疑自己的key和secret是否正确。
海康开放平台下载开发包的地方,还可以下载如下工具,直接调用接口:
如果能获取请求返回结果,那AppKey和Secret也就没问题了。
(本人这次appkey和secret是他人提供的,所以也要检查下,有可能配置的人会没配置好)
不过这工具不稳定,有时候并没卵用。
3. 生成签名的php程序编写
代码都在文中了。
只能说以后遇到这种变换或加密字符串的功能,提醒自己多注意空格、换行符和特殊字符吧
//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);
java代码:
import java.util.HashMap; import java.util.Map; import com.hikvision.artemis.sdk.util.*; public class test { public static void main(String[] args) { // TODO Auto-generated method stub SignUtil su = new SignUtil(); String secret = "UGmBrTB9OVa0rtOsYhd7"; String method = "POST"; String path = "/artemis/api/resource/v1/cameras"; Map<String, String> header = new HashMap<>(); header.put("Accept", "*/*"); header.put("Content-Type", "application/json"); header.put("x-ca-key", "29584125"); header.put("x-ca-timestamp", "16848960298234"); String res = SignUtil.sign(secret, method, path, header, null, null, null); System.out.println(res.toString()); } }