上一篇:vue3.0 学习笔记(2023-05-17 17:35:02)
文章大纲

对接海康开放平台接口,居然被一个换行符折磨了

2023-05-27 12:47:33

需求场景

公司用户最近需要对自己工厂的设备进行监控,采买了海康摄像头,希望在当下开发的管理系统里,能够直接查看当前设备的实时监控。



任务难点

调用海康开放平台的接口,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代码:
//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());
	}

}
上一篇:vue3.0 学习笔记(2023-05-17 17:35:02)
我要评论
评论列表