Encryption for Smart Lock API
Hi,
I am running into an error when trying to create a temporary password for my Wi-Fi Smart Lock. This is my error message:
"Error: request api failed: param is illegal ,please check it"
Here are my steps:
I retrieve a
ticket_key
andticket_id
using/v1.0/devices/{device_id}/door-lock/password-ticket
and receive a response:Code: Select all
result: { expire_time: 47, ticket_id: 'w2mA7CbZ', ticket_key: 'EE2A3C241BB7F7C261EE9DC9025AA92BD2E5A66E1ADDC5A640D045C6F2B1E7CF' }, success: true, t: 1700684973862, tid: 'd8e56ec6897511ee9dcc3ea672c7e535' }
I decrypt the
ticket_key
with my secret key using AES-ECB-128. The plaintext data is 64 bytes (a 256-bit key), such as98dfaf1dcd4a4beb8a733ae539635dddd6d4c9a1808cf5a37b3a56ffd86f5ddd
I use this plaintext ticket_key to encrypt a 7 digit lock password, such as
1234567
. I guess it is AES-ECB-256 because the key I decrypted is 256-bits. Is this correct?I send POST request to
/v1.0/devices/{device_id}/door-lock/temp-password
, with a body such as:Code: Select all
{ password: '9141A0E2AFEC0927A96685374B8ED7AA', password_type: 'ticket', ticket_id: 'w2mA7CbZ', effective_time: 1700934300, invalid_time: 1700984301, name: 'test1' }
I receive an error back:
Error: request api failed: param is illegal ,please check it
My code:
Code: Select all
import * as qs from 'qs';
import * as crypto from 'crypto';
import { default as axios } from 'axios';
let token = '';
const config = {
/* openapi host */
host: 'https://openapi.tuyaus.com',
/* fetch from openapi platform */
accessKey: 'xxxxx',
/* fetch from openapi platform */
secretKey: 'xxxxx',
/* Interface example device_ID */
deviceId: 'xxxxx', // smart lock
};
const httpClient = axios.create({
baseURL: config.host,
timeout: 5 * 1e3,
});
async function main() {
await getToken();
const data = await createTemporaryPassword(config.deviceId,'1234567');
console.log('fetch success: ', JSON.stringify(data,null,2));
}
/**
* fetch highway login token
*/
async function getToken() {
const method = 'GET';
const timestamp = Date.now().toString();
const signUrl = '/v1.0/token?grant_type=1';
const contentHash = crypto.createHash('sha256').update('').digest('hex');
const stringToSign = [method, contentHash, '', signUrl].join('\n');
const signStr = config.accessKey + timestamp + stringToSign;
const headers = {
t: timestamp,
sign_method: 'HMAC-SHA256',
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
};
const { data: login } = await httpClient.get('/v1.0/token?grant_type=1', { headers });
if (!login || !login.success) {
throw Error(`fetch failed: ${login.msg}`);
}
token = login.result.access_token;
}
async function createTemporaryPassword(deviceId: string, password: string) {
const tempData = await getLockTemporaryEncryptionKey(config.deviceId);
const decryptedTempTicketKey = decrypt_AES_128(tempData.result.ticket_key,config.secretKey);
const encryptedPassword = encrypt_AES_256(password,decryptedTempTicketKey.toString('hex')).toString('hex').toUpperCase();
const method = 'POST';
let url = `/v1.0/devices/${deviceId}/door-lock/temp-password`;
const body = {
"password":encryptedPassword,
"password_type":"ticket",
"ticket_id":tempData.result.ticket_id,
"effective_time": 1700934300,
"invalid_time": 1700984301,
"name": "test1",
//"phone":12345,
//"time_zone":"",
}
const reqHeaders: { [k: string]: string } = await getRequestSign(url, method, {}, body);
const { data } = await httpClient.request({
method,
data: body,
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
console.log('Request:', method, reqHeaders.path, body);
console.log('Response:', data);
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
return data;
}
async function getLockTemporaryEncryptionKey(deviceId: string) {
const method = 'POST';
let url = `/v1.0/devices/${deviceId}/door-lock/password-ticket`;
const reqHeaders: { [k: string]: string } = await getRequestSign(url, method);
const body = {};
const { data } = await httpClient.request({
method,
data: body,
params: {},
headers: reqHeaders,
url: reqHeaders.path,
});
console.log('Request:', method, reqHeaders.path, body);
console.log('Response:', data);
if (!data || !data.success) {
throw Error(`request api failed: ${data.msg}`);
}
return data;
}
/**
* HMAC-SHA256 crypto function
*/
async function encryptStr(str: string, secret: string): Promise<string> {
return crypto.createHmac('sha256', secret).update(str, 'utf8').digest('hex').toUpperCase();
}
/**
* request sign, save headers
* @param path
* @param method
* @param query
* @param body
*/
async function getRequestSign(
path: string,
method: string,
query: { [k: string]: any } = {},
body: { [k: string]: any } = {},
) {
const [uri, pathQuery] = path.split('?');
const queryMerged = Object.assign(query, qs.parse(pathQuery));
const sortedQuery: { [k: string]: string } = {};
Object.keys(queryMerged)
.sort()
.forEach((i) => (sortedQuery[i] = query[i]));
const querystring = decodeURIComponent(qs.stringify(sortedQuery));
const url = querystring ? `${uri}?${querystring}` : uri;
const t = Date.now().toString();
const contentHash = crypto.createHash('sha256').update(JSON.stringify(body)).digest('hex');
const stringToSign = [method, contentHash, '', url].join('\n');
const signStr = config.accessKey + token + t + stringToSign;
return {
t,
path: url,
client_id: config.accessKey,
sign: await encryptStr(signStr, config.secretKey),
sign_method: 'HMAC-SHA256',
access_token: token,
};
}
main().catch(err => {
throw Error(`error: ${err}`);
});
// ECB encrypt 256 bit AES ECB
function encrypt_AES_256(data:string, secretKey:string) {
const algorithm = 'aes-256-ecb'
const buf = Buffer.from(data,'utf-8');
const iv = null; // no IV for ECB cipher
const key = Buffer.from(secretKey,'hex');
const cipher = crypto.createCipheriv(algorithm, key, iv);
cipher.setAutoPadding(true);
const encrypted = Buffer.concat([cipher.update(buf), cipher.final()])
return encrypted;
}
// ECB decrypt 128 bit AES ECB
function decrypt_AES_128(data:string, secretKey:string) {
const algorithm = 'aes-128-ecb'
const buf = Buffer.from(data,'hex');
const iv = null; // no IV for ECB cipher
const key = Buffer.from(secretKey,'hex');
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAutoPadding(false);
const decrypted = Buffer.concat([decipher.update(buf), decipher.final()])
return decrypted;
}
My POST body looks correct to me. What am I doing wrong?
Please help, thank you!