This topic describes how to connect a device to IoT Platform by using Constrained Application Protocol (CoAP).

CoAP is suitable for devices such as NB-IoT devices that have constrained resources and run at low power. For more information about how to connect a device to IoT Platform by using CoAP, see Establish connections over CoAP.

Note The feature is available only in the China (Shanghai) region.

When you connect a device to IoT Platform by using CoAP, you must specify the required parameters, generate a signature for the device, and perform other operations. Eclipse Californium-based sample code is used as an example to describe the configuration process. To ensure data security, symmetric encryption is applied.

pom.xml

Add the following dependencies to the pom.xml file to import the Californium open-source framework, Apache Commons toolkit, and Alibaba fastjson package.

<dependency>
  <groupId>org.eclipse.californium</groupId>
  <artifactId>californium-core</artifactId>
  <version>2.0.0-M17</version>
</dependency>
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-lang3</artifactId>
  <version>3.5</version>
</dependency>
<dependency>
  <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.13</version>
</dependency>
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.61</version>
</dependency>

Sample code

/*   
 * Copyright © 2019 Alibaba. All rights reserved.
 */
package com.aliyun.iot.demo;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.RandomUtils;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.Utils;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.CoAP.Code;
import org.eclipse.californium.core.coap.CoAP.Type;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Option;
import org.eclipse.californium.core.coap.OptionNumberRegistry;
import org.eclipse.californium.core.coap.OptionSet;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.elements.exception.ConnectorException;

import com.alibaba.fastjson.JSONObject;

/**
 * The following Eclipse Californium-based code shows how to connect a device to IoT Platform by using CoAP.
 * For more information about how to develop custom code and specify the required parameters, see the Use the symmetric encryption method Section in Establish connections over CoAP.
 */
public class IotCoapClientWithAes {

    // ===================The start of the section where you can specify the required parameters===========================
    // The ID of the region. Only the China (Shanghai) region is available.
    private static String regionId = "cn-shanghai";
    // The key of the product.
    private static String productKey = "The productKey of your device";
    // The name of the device.
    private static String deviceName = "The deviceName of your device";
    // The key of the device.
    private static String deviceSecret = "The deviceSecret of your device";
    // ===================The end of the section where you can specify the required parameters===========================

    // Set the HMAC_ALGORITHM parameter to hmacsha1 or hmacmd5. The value that you set must be the same as that of the signmethod parameter.
    private static final String HMAC_ALGORITHM = "hmacsha1";

    // The endpoint that is used to authenticate CoAP clients. The port number of the endpoint is 5682 if you use symmetric encryption.
    private static String serverURI = "coap://" + productKey + ".coap." + regionId + ".link.aliyuncs.com:5682";

    // The topic to which messages are sent. In the IoT Platform console, you can create a custom topic and grant the publish permission to devices.
    private static String updateTopic = "/" + productKey + "/" + deviceName + "/user/update";

    // token option
    private static final int COAP2_OPTION_TOKEN = 2088;
    // seq option
    private static final int COAP2_OPTION_SEQ = 2089;

    // Specify the SHA-256 encryption algorithm.
    private static final String SHA_256 = "SHA-256";

    private static final int DIGITAL_16 = 16;
    private static final int DIGITAL_48 = 48;

    // Create a CoAP client.
    private CoapClient coapClient = new CoapClient();

    // The validity period of a token is seven days. After a token expires, you must obtain a new token.
    private String token = null;
    private String random = null;
    @SuppressWarnings("unused")
    private long seqOffset = 0;

    /**
     * Initialize a CoAP client
     * 
     * @param productKey The key of the product
     * @param deviceName The name of the device
     * @param deviceSecret The key of the device
     */
    public void connect(String productKey, String deviceName, String deviceSecret) {
        try {
            // The endpoint for authentication.
            String uri = serverURI + "/auth";

            // Only the POST method is available.
            Request request = new Request(Code.POST, Type.CON);

            // Specify options
            OptionSet optionSet = new OptionSet();
            optionSet.addOption(new Option(OptionNumberRegistry.CONTENT_FORMAT, MediaTypeRegistry.APPLICATION_JSON));
            optionSet.addOption(new Option(OptionNumberRegistry.ACCEPT, MediaTypeRegistry.APPLICATION_JSON));
            request.setOptions(optionSet);

            // Specify the endpoint that you can use to authenticate the device
            request.setURI(uri);

            // Specify the required parmeters for an authentication request
            request.setPayload(authBody(productKey, deviceName, deviceSecret));

            // Send the authentication request
            CoapResponse response = coapClient.advanced(request);
            System.out.println(Utils.prettyPrint(response));
            System.out.println();

            // Parse the response
            JSONObject json = JSONObject.parseObject(response.getResponseText());
            token = json.getString("token");
            random = json.getString("random");
            seqOffset = json.getLongValue("seqOffset");
        } catch (ClientException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Send messages
     * 
     @param topic The topic to which messages are sent.
     * @param payload The contents of messages.
     */
    public void publish(String topic, byte[] payload) {
        try {
            // The endponit of the topic. The syntax of a topic is /topic/${topic}.
            String uri = serverURI + "/topic" + topic;

            // Use  seq=RandomUtils.nextInt() to encrypt the seq option by using the AES encryption algorithm.
            String shaKey = encod(deviceSecret + "," + random);
            byte[] keys = Hex.decodeHex(shaKey.substring(DIGITAL_16, DIGITAL_48));
            byte[] seqBytes = encrypt(String.valueOf(RandomUtils.nextInt()).getBytes(StandardCharsets.UTF_8), keys);

            // Only the POST method is available
            Request request = new Request(CoAP.Code.POST, CoAP.Type.CON);

            // Specify options
            OptionSet optionSet = new OptionSet();
            optionSet.addOption(new Option(OptionNumberRegistry.CONTENT_FORMAT, MediaTypeRegistry.APPLICATION_JSON));
            optionSet.addOption(new Option(OptionNumberRegistry.ACCEPT, MediaTypeRegistry.APPLICATION_JSON));
            optionSet.addOption(new Option(COAP2_OPTION_TOKEN, token));
            optionSet.addOption(new Option(COAP2_OPTION_SEQ, seqBytes));
            request.setOptions(optionSet);

            // Specify the endpoint of the topic.
            request.setURI(uri);

            // Specify the payload parmameter.
            request.setPayload(encrypt(payload, keys));

            // Send messages.
            CoapResponse response = coapClient.advanced(request);
            System.out.println(Utils.prettyPrint(response));

            // Parse the result.
            String result = null;
            if (response.getPayload() ! = null) {
                result = new String(decrypt(response.getPayload(), keys));
            }
            System.out.println("Received: " + result);
            System.out.println();
        } catch (ConnectorException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DecoderException e) {
            e.printStackTrace();
        }
    }

    /**
     * Generate the required parameters for authentication
     * 
     * @param productKey The name of the product
     * @param deviceName The name of the device
     * @param deviceSecret The key of the device
     * @return An authentication request
     */
    private String authBody(String productKey, String deviceName, String deviceSecret) {

        // Create an authentication request
        JSONObject body = new JSONObject();
        body.put("productKey", productKey);
        body.put("deviceName", deviceName);
        body.put("clientId", productKey + "." + deviceName);
        //signMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
        body.put("signmethod", HMAC_ALGORITHM);
        body.put("seq", DIGITAL_16);
        body.put("sign", sign(body, deviceSecret));

        System.out.println("----- auth body -----");
        System.out.println(body.toJSONString());

        return body.toJSONString();
    }

    /**
     * Generate a signature for a device
     * 
     * @param params The required parameters that you can use to generate a signature.
     * @param deviceSecret The key of the device.
     * @return The signature in the hexadecimal format.
     */
    private String sign(JSONObject params, String deviceSecret) {

        // Sort request parameters in the alphabetical order
        Set<String> keys = getSortedKeys(params);

        //  Remove the sign, signmethod, version, and resources parameters.
        keys.remove("sign");
        keys.remove("signmethod");
        keys.remove("version");
        keys.remove("resources");

        // Obtain the plaintext of the signature.
        StringBuffer content = new StringBuffer();
        for (String key : keys) {
            content.append(key);
            content.append(params.getString(key));
        }

        // Generate a signature.
        String sign = encrypt(content.toString(), deviceSecret);
        System.out.println("sign content=" + content);
        System.out.println("sign result=" + sign);

        return sign;
    }

    /**
     * Convert a JSON object to a set of key-value pairs.
     * 
     * @param json The JSON object to be converted.
     * @return A set of key-value pairs that are converted from a JSON object.
     */
    private Set<String> getSortedKeys(JSONObject json) {
        SortedMap<String, String> map = new TreeMap<String, String>();
        for (String key : json.keySet()) {
            String vlaue = json.getString(key);
            map.put(key, vlaue);
        }
        return map.keySet();
    }

    /**
     * Specify an encryption algorithm in the HMAC_ALGORITHM parameter.
     * 
     * @param content Plaintext.
     * @param secret An encryption key.
     * @return Ciphertext.
     */
    private String encrypt(String content, String secret) {
        try {
            byte[] text = content.getBytes(StandardCharsets.UTF_8);
            byte[] key = secret.getBytes(StandardCharsets.UTF_8);
            SecretKeySpec secretKey = new SecretKeySpec(key, HMAC_ALGORITHM);
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            return Hex.encodeHexString(mac.doFinal(text));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * SHA-256
     * 
     * @param str A message to be encrypted.
     */
    private String encod(String str) {
        MessageDigest messageDigest;
        String encdeStr = "";
        try {
            messageDigest = MessageDigest.getInstance(SHA_256);
            byte[] hash = messageDigest.digest(str.getBytes(StandardCharsets.UTF_8));
            encdeStr = Hex.encodeHexString(hash);
        } catch (NoSuchAlgorithmException e) {
            System.out.println(String.format("Exception@encod: str=%s;", str));
            e.printStackTrace();
            return null;
        }
        return encdeStr;
    }

    // Encrypt and decrypt data based on the AES algorithm.
    private static final String IV = "543yhjy97ae7fyfg";
    private static final String TRANSFORM = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";

    /**
     * key length = 16 bits
     */
    private byte[] encrypt(byte[] content, byte[] key) {
        return encrypt(content, key, IV);
    }

    /**
     * key length = 16 bits
     */
    private byte[] decrypt(byte[] content, byte[] key) {
        return decrypt(content, key, IV);
    }

    /**
     * aes 128 cbc key length = 16 bits
     */
    private byte[] encrypt(byte[] content, byte[] key, String ivContent) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORM);
            IvParameterSpec iv = new IvParameterSpec(ivContent.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
            return cipher.doFinal(content);
        } catch (Exception ex) {
            System.out.println(
                    String.format("AES encrypt error, %s, %s, %s", content, Hex.encodeHex(key), ex.getMessage()));
            return null;
        }
    }

    /**
     * aes 128 cbc key length = 16 bits
     */
    private byte[] decrypt(byte[] content, byte[] key, String ivContent) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(key, ALGORITHM);
            Cipher cipher = Cipher.getInstance(TRANSFORM);
            IvParameterSpec iv = new IvParameterSpec(ivContent.getBytes(StandardCharsets.UTF_8));
            cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
            return cipher.doFinal(content);
        } catch (Exception ex) {
            System.out.println(String.format("AES decrypt error, %s, %s, %s", Hex.encodeHex(content),
                    Hex.encodeHex(key), ex.getMessage()));
            return null;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IotCoapClientWithAes client = new IotCoapClientWithAes();
        client.connect(productKey, deviceName, deviceSecret);
        client.publish(updateTopic, "hello coap".getBytes(StandardCharsets.UTF_8));
        client.publish(updateTopic, new byte[] { 0x01, 0x02, 0x03, 0x05 });
    }
}