API Authentication
1. Body message signature
Body message signature verification is a method of authentication whereby requests are sent between developer and IoT SENSORO cloud platform.
Once developer has submitted Webhook address (custom callback URL), SENSORO will send a request with 3 HTTP headers to developer, namely X-ACCESS-ID
, X-ACCESS-NONCE
and X-ACCESS-SIGNATURE
. Developer authenticates the request based on the signature to confirm this POST request came from SENSORO server.
When developer sends a request to SENSORO IoT open API interface, this request has to include the three headers parameters mentioned above, too. SENSORO server authenticates the request based on the signature and identifies the developer.
Request with header parameters explained in following table:
Key |
Instructions |
X-ACCESS-ID |
Developer application identifier |
X-ACCESS-NONCE |
Request time in Unix format,accurate to 0.001 second |
X-ACCESS-SIGNATURE |
AppSecret as passkey,calculate X-ACCESS-NONCE + GET POST PUT DELETE... + Request completed URL + Request Body (leave blank if none) HMAC(SHA256) result. |
NodeJS sample code as below:
var crypto = require('crypto');
var SECRET = "YOURAPPSECRET";
var error = new Error('Invalid signature');
error.status = 400;
function verifySignatrue(req) {
var url = req.protocol + '://' +
req.get('host') + req.originalUrl;
// Generated ACCESS_NONCE, as current value Unix time, unit as milliseconds
var ACCESS_NONCE = req.headers['x-access-nonce'];
// Set App Id
var ACCESS_ID = req.headers['x-access-id']
var BodyRaw = '';
if (req.body && req.body instanceof Object) {
try{
BodyRaw = JSON.stringify(req.body);
}catch(e) {
console.error(e);
}
}
var original = new Buffer(ACCESS_NONCE +
req.method.toUpperCase() +
url + BodyRaw);
var secret = crypto.createHmac('SHA256', SECRET)
.update(original).digest('base64');
if (req.headers['x-access-signature'] !== secret) {
return callback(error);
}
callback(err);
}
module.exports = function() {
return function(req, res, next) {
verifySignatrue(req, next);
};
};
2. Body message encryption
Message encryption and decryption based on AES encryption and decryption algorithm as follows:
App Key as message encrypt and decrypt key, with a fixed length of 43 characters, chosen from a-z,A-Z, 0-9 of total 62 characters selection. It is filled out by developer during configuration and is modifiable.
AESPassKey: AESKey = Base64_Decode( AppKey + “=”),Append “=”at the end of AppKey, and use Base64_Decode to generate 32 bytes of AESKey.
AES uses CBC mode, and PassKey length is 32 bytes (256 bits), data filled with PKCS#7; PKCS#7:K as PassKey bytes (by 32), buf as pre-encrypt content, N as number of bytes. Buf is integer multiple of K . At the end of buf, fill-in (K-N%K) bytes, each byte substance is (N%K K-).
BASE64 is in MIME format, include 26 case sensitive letters, plus 10 digital numbers, plus “+”, slash "/", a total of 64 characters, equal sign "=" used as suffix;
SENSORO provides NPM encryption and decryption package for developers.
var crypto = require('crypto');
/**
* Provide PKCS7 algorithm for encryption and decryption interface.
*/
var PKCS7Encoder = {};
/**
* Remove of decryption complement characters.
*
* @param {String} text Decryption text
*/
PKCS7Encoder.decode = function (text) {
var pad = text[text.length - 1];
if (pad < 1 || pad > 32) {
pad = 0;
}
return text.slice(0, text.length - pad);
};
/**
* Character fill-in for require encrypted text
*
* @param {String} text Character fill-in for on-going operation requirement
*/
PKCS7Encoder.encode = function (text) {
var blockSize = 32;
var textLength = text.length;
//Calculation requirement number of fill-in bits
var amountToPad = blockSize - (textLength % blockSize);
var result = new Buffer(amountToPad);
result.fill(amountToPad);
return Buffer.concat([text, result]);
};
/**
* Encryption and decryption structure function, refer to WeChat
* Refer to instruction http://docs.sensoro.com/cloud/auth.html
* @param {String} appSecret Developer setting for appSecret
* @param {String} appKey Developer setting in public platform for appKey
* @param {String} appId AppId
*/
var MsgCrypt = function (appSecret, appKey, appId) {
if (!appSecret || !appKey || !appId) {
throw new Error('please check arguments');
}
this.appSecret = appSecret;
this.appId = appId;
var AESKey = new Buffer(appKey + '=', 'base64');
if (AESKey.length !== 32) {
throw new Error('appKey invalid');
}
this.key = AESKey;
this.iv = AESKey.slice(0, 16);
};
/**
* Cipher text decryption.
*
* @param {String} text Pending cipher text decryption
*/
MsgCrypt.prototype.decrypt = function(text) {
// Decryption object, AES uses CBC mode, data filled with PKCS#7;
// IV initial vector size as 16 bytes with first 16 bytes of AESKey;
var decipher = crypto.createDecipheriv('aes-256-cbc', this.key, this.iv);
decipher.setAutoPadding(false);
var deciphered = Buffer.concat([decipher.update(text, 'base64'),
decipher.final()]);
deciphered = PKCS7Encoder.decode(deciphered);
// Algorithm:AES_Encrypt[random(16B) + msg_len(4B) + msg + $CorpID]
// Remove 16-bits random number
var content = deciphered.slice(16);
var length = content.slice(0, 4).readUInt32BE(0);
return {
message: content.slice(4, length + 4).toString(),
id: content.slice(length + 4).toString()
};
};
/**
* Plaintext encryption;
*
* @param {String} text Pending plaintext encryption.
*/
MsgCrypt.prototype.encrypt = function (text) {
// Algorithm:AES_Encrypt[random(16B) + msg_len(4B) + msg + $CorpID]
// Optain 16B random string.
var randomString = crypto.pseudoRandomBytes(16);
var msg = new Buffer(text);
// Optain 4B network byte;
var msgLength = new Buffer(4);
msgLength.writeUInt32BE(msg.length, 0);
var appId = new Buffer(this.appId);
var bufMsg = Buffer.concat([randomString, msgLength, msg, appId]);
// Plaintext repair operation;
var encoded = PKCS7Encoder.encode(bufMsg);
// Encryption object, AES uses CBC mode, data filled with PKCS#7;
//IV initial vector size as 16 bytes with first 16 bytes of AESKey;
(The size of IV initial vector is 16 bytes, and take the first 16 bytes of AESKey)
var cipher = crypto.createCipheriv('aes-256-cbc', this.key, this.iv);
cipher.setAutoPadding(false);
var cipheredMsg = Buffer.concat([cipher.update(encoded), cipher.final()]);
// Return of base64 encrypted code.
return cipheredMsg.toString('base64');
};
module.exports = MsgCrypt;