# 数据上传文档

# mqtt上传数据

设备通过mqtt协议接入鉴权通过后,可以通过publish消息向HanClouds上传设备数据。单条数据最大8KB

# 上传数据流

topic:data/{deviceKey}/{stream}

  • data为固定前缀
  • deviceKey必须为本设备的deviceKey,否则平台会拒绝数据上传并断开mqtt连接,网关代理的子设备亦使用自身的deviceKey上报数据。
  • stream为物模型定义的数据流标识符,为英文字符串,不可包含符号、特殊字符和中文

payload:设备上传的数据,定义了物模型的数据流,payload需匹配对应物模型,否则上报数据平台将断开与设备连接。

数据类型包括: -int int数据类型 -string 字符串类型 -enum 枚举类型 -float 浮点数据类型 -double double数据类型 -json json格式数据 -bin bin数据类型,base64编码的数据 -date 日期数据 -array 数组类型 -gps 地理位置数据 -boolean boolean数据类型

  • payload在签名模式下为用dynamicSecret加密的密文,在非签名模式下为数据明文

# 上传事件

topic:event/{deviceKey}/{identifier}

  • event为固定前缀
  • topic中deviceKey必须为本设备的deviceKey,网关代理的子设备上传事件时,这里的deviceKey为子设备的deviceKey
  • identifier为物模型定义的事件标识符。

payload : 匹配物模型事件模板的json数据;

  • payload在签名模式下为用dynamicSecret加密的密文,在非签名模式下为数据明文

# 加密流程说明

在设备以签名模式连接到HanClouds后,默认后续数据会采用动态秘钥进行加密传输。其加解密流程如下:

数据加密示意图

  1. device 以签名模式向HanClouds发送Connect消息
  2. HanClouds收到请求并鉴权通过后,生成一个动态秘钥dynamicSecret, 用deviceSecret加密后再通过topic rsp/welcome topic 发送给device
  3. device收到加密后的dynamicSecret,根据签名模式不同,使用AES或者SM4对deviceSecret解密,得到dynamicSecret明文。
  4. 后续publish消息中的payload用dynamicSecret作为秘钥进行加密后传输

# 加密算法说明

1.SHA1签名模式

  • 加密算法为AES/CBC/Pkcs5padding
  • 加密时使用的iv固定为16字节,payload部分的头部16字节为iv,每次加密的iv都不同。例如对某个数据用秘钥A和iv加密后得到加密结果字节数组长度为32字节,则最终传递到网络中的内容为48字节,密文前面附加了16字节的iv.

大部分语言都提供了AES的标准库,Java代码示例如下:

   /**
     * 使用AES加密
     *
     */
  public static byte[] encodeWithAesCbc(String secret, byte[] content) {
        if (secret.length() < 16) {
            return null;
        }
        if (secret.length() > 16) {
            secret = secret.substring(0, 16);
        }
        try {
            SecretKeySpec key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            byte[] ivBytes = getRandomString(16).getBytes(StandardCharsets.UTF_8);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] encrypData = cipher.doFinal(content);
            if (encrypData != null) {
               //将iv附加到加密内容中
                byte[] result = new byte[16 + encrypData.length];
                System.arraycopy(ivBytes, 0, result, 0, 16);
                System.arraycopy(encrypData, 0, result, 16, encrypData.length);
                return result;
            }
            return null;
        } catch (Exception e) {
            logger.error("encodeWithAesCbc(...)  failed, method = AES/CBC/PKCS5Padding, cause={}", e.getMessage());
        }
        return null;
    }
    /**
     * 使用AES解密
     *
     */
  public static byte[] decodeWithAesCbc(String secret, byte[] content) {
        if (secret.length() < AES_KEY_SIZE) {
            return null;
        }
        if (secret.length() > AES_KEY_SIZE) {
            secret = secret.substring(0, AES_KEY_SIZE);
        }
        try {
            SecretKeySpec key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            byte[] ivBytes = new byte[16];
            //从内容中取出iv
            System.arraycopy(content, 0, ivBytes, 0, 16);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] encBytes = new byte[content.length - 16];
            System.arraycopy(content, 16, encBytes, 0, encBytes.length);
            return cipher.doFinal(encBytes);
        } catch (Exception e) {
            logger.error("decodeWithAesCbc(...) failed, {}", e.getMessage());
        }
        return null;
    }

2.国密SM签名模式

  • 加密算法为SM4/CBC/Pkcs5padding
  • 加密时使用的iv固定为16字节,payload部分的头部16字节为iv,每次加密的iv都不同。例如对某个数据用秘钥A和iv加密后得到加密结果字节数组长度为32字节,则最终传递到网络中的内容为48字节,密文前面附加了16字节的iv.

SM4则需要使用第三方标准实现,首先添加Maven依赖:

 <dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcprov-jdk15on</artifactId>
   <version>1.68</version>
</dependency>

Java代码示例如下:

 
  public class SM4Demo{
  
     static {
      //添加三方标准算法实现
      Security.addProvider(new BouncyCastleProvider());
    }
  
    /**
     * 使用SM4加密
     *
     */
    public static byte[] encodeWithSm4Cbc(String secret, byte[] content) {
        if (secret.length() < 16) {
            return null;
        }
        if (secret.length() > 16) {
            secret = secret.substring(0, 16);
        }
        try {
            SecretKeySpec key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "SM4");
            Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
            byte[] ivBytes = getRandomString(16).getBytes(StandardCharsets.UTF_8);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] encrypData = cipher.doFinal(content);
            if (encrypData != null) {
               //将iv附加到加密内容中
                byte[] result = new byte[16 + encrypData.length];
                System.arraycopy(ivBytes, 0, result, 0, 16);
                System.arraycopy(encrypData, 0, result, 16, encrypData.length);
                return result;
            }
            return null;
        } catch (Exception e) {
            logger.error("encodeWithSm4Cbc(...)  failed, method = SM4/CBC/PKCS5Padding, cause={}", e.getMessage());
        }
        return null;
    }
    /**
     * 使用SM4解密
     *
     */
  public static byte[] decodeWithAesCbc(String secret, byte[] content) {
        if (secret.length() < AES_KEY_SIZE) {
            return null;
        }
        if (secret.length() > AES_KEY_SIZE) {
            secret = secret.substring(0, AES_KEY_SIZE);
        }
        try {
            SecretKeySpec key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "SM4");
            Cipher cipher = Cipher.getInstance("SM4/CBC/PKCS5Padding");
            byte[] ivBytes = new byte[16];
            //从内容中取出iv
            System.arraycopy(content, 0, ivBytes, 0, 16);
            IvParameterSpec iv = new IvParameterSpec(ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] encBytes = new byte[content.length - 16];
            System.arraycopy(content, 16, encBytes, 0, encBytes.length);
            return cipher.doFinal(encBytes);
        } catch (Exception e) {
            logger.error("decodeWithSm4Cbc(...) failed, {}", e.getMessage());
        }
        return null;
    }
  }
     
 

# http上传数据

请参考restful api文档: 设备上传数据