Page 1 of 1

Encryption for Smart Lock API

Posted: 2023年 Nov 23日 05:00
by james1

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!


Re: Encryption for Smart Lock API

Posted: 2023年 Nov 23日 14:56
by kobe8936

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


Re: Encryption for Smart Lock API

Posted: 2023年 Nov 24日 04:19
by james1
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.


Re: Encryption for Smart Lock API

Posted: 2023年 Dec 8日 02:45
by james1

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.


Re: Encryption for Smart Lock API

Posted: 2023年 Dec 23日 04:25
by evanstinger
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


Re: Encryption for Smart Lock API

Posted: 2024年 Jan 8日 16:52
by evanstinger
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!


Re: Encryption for Smart Lock API

Posted: 2024年 Jan 18日 20:44
by osobiste1

Have same problem. Can someone help solve it please?


Re: Encryption for Smart Lock API

Posted: 2024年 Feb 6日 20:44
by Remendado

Same problem. Need help.


Re: Encryption for Smart Lock API

Posted: 2024年 Feb 7日 12:33
by evanstinger

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


Re: Encryption for Smart Lock API

Posted: 2024年 Nov 12日 01:19
by lavezpa

I am in the same situation!
2 days with no exit at all