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

9,665 阅读4分钟

需求场景

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

发现问题

对接海康开放平台的接口,第一步就是要获取一个签名值。然后发起请求时,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);

所以在以后类似这种转换或加密的操作时,一定要注意换行、空格等这些不可见的字符对程序的影响。