Encryption for Smart Lock API

Discussing technical aspects of Yun development products, including IoT Core and other cloud service APIs, data analysis products, etc.


Post Reply
james1
Posts: 3

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:

  1. I retrieve a ticket_key and ticket_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'
    }
  2. I decrypt the ticket_key with my secret key using AES-ECB-128. The plaintext data is 64 bytes (a 256-bit key), such as 98dfaf1dcd4a4beb8a733ae539635dddd6d4c9a1808cf5a37b3a56ffd86f5ddd

  3. 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?

  4. 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!

kobe8936
Posts: 2

Re: Encryption for Smart Lock API

You should encrypt your password by using the AES-128 algorithm with ECB mode and PKCS7Padding.
https://developer.tuya.com/en/docs/clou ... 20password

james1
Posts: 3

Re: Encryption for Smart Lock API

kobe8936 2023年 Nov 23日 14:56

You should encrypt your password by using the AES-128 algorithm with ECB mode and PKCS7Padding.
https://developer.tuya.com/en/docs/clou ... 20password

Thank you kobe, but which encryption key do I use for this? The temporary key I receive from the /password-ticket API is 256-bit, not 128-bit.

james1
Posts: 3

Re: Encryption for Smart Lock API

I figured this out. Turns out the access key should be interpreted as an ASCII or UTF-8 string, and not a hex string, for the encryption key. Thus, ticket_key is decrypted as a 128-bit key with 128-bits of padding, not 256 bits total.

evanstinger
Posts: 3

Re: Encryption for Smart Lock API

james1 2023年 Dec 8日 02:45

I figured this out. Turns out the access key should be interpreted as an ASCII or UTF-8 string, and not a hex string, for the encryption key. Thus, ticket_key is decrypted as a 128-bit key with 128-bits of padding, not 256 bits total.

Hey, I'm currently stuck here.
And my code is 98% similar to the one you posted above.
Can you share you currently working code here, please?
I'm also confused, which one is true, are we using the accessKey or secretKey as the decryption key?

Thanks in advance

evanstinger
Posts: 3

Re: Encryption for Smart Lock API

james1 2023年 Dec 8日 02:45

I figured this out. Turns out the access key should be interpreted as an ASCII or UTF-8 string, and not a hex string, for the encryption key. Thus, ticket_key is decrypted as a 128-bit key with 128-bits of padding, not 256 bits total.

Replying again to bump this thread.
Really appreciate it if you can share your solution.
Many, many thanks in advance!

osobiste1
Posts: 1

Re: Encryption for Smart Lock API

Have same problem. Can someone help solve it please?

Remendado
Posts: 1

Re: Encryption for Smart Lock API

Same problem. Need help.

evanstinger
Posts: 3

Re: Encryption for Smart Lock API

Somebody, please help!
I've been spending hundreds of hours here, trying so many things, so many different methods, fiddling overnights.
Please help.

Post Reply