智能锁 临时密码 ticketKey 解密 加密 问题

云开发产品技术讨论,包括IoT Core和其他云服务API、数据分析产品等话题


Post Reply
allcam
Posts: 9

按API接口说明, 为智能锁 设置 临时密码 时必须先通过涂鸦API 获取为临时密码加密的密钥 (ticketKey), 但是收到的密钥也是加密的,必须用 TUYA_ACCESS_KEY 解密。下面是我用的 js 代码, 我遇到的问题是:

  1. 用 TUYA_ACCESS_KEY 作密钥解密 ticketKey 时发现, 其长度是 32 字节,但AES-128-ECB支持的密钥长度是16字节,只好截出前面 16字节作为密钥,但涂鸦的API接口说明里面没有提到这一点。不过用这个办法解密能得到一个长度为 16字节的二进制的字符串 (转换为可正常显示的 十六进制 字符串为 32 位)
  2. 当用得到的二进制的字符串作为密钥加密 临时密码 得到的字符串 和 相应的 tickeID 回传给涂鸦AP时,出错信息显示钥匙长度不对? 用了 CHATGPT 也没找到办法,哪位大神能指点一下?
Last edited by allcam on 2025年 Jan 1日 21:30, edited 3 times in total.
panda-cat
Posts: 25

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

accessKey是您项目的Access Secret信息,使用 AccessKey的中间 16 位字符(ACCESS_KEY.substring(8, 24))对数据进行AES解密。

allcam
Posts: 9

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

panda-cat 2024年 Dec 31日 11:06

accessKey是您项目的Access Secret信息,使用 AccessKey的中间 16 位字符(ACCESS_KEY.substring(8, 24))对数据进行AES解密。

谢谢高人指点。 还是有点问题:
从涂鸦收到的加密的ticketKey 为 64位 HEX,
用AccessKey的中间 16 位字符(ACCESS_KEY.substring(8, 24))对数据进行AES解密后得到的字符串长度为 32位, 这不能作为密钥用于AES-128加密 临时密码,因为AES-128加密的密钥要求为 16位。如果只截取前面的 16位作为密钥来加密 临时密码, 涂鸦会显示 illegal param 错误。

allcam
Posts: 9

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

如果把AccessKey的全部32位字符当做 HEX,这就相当于 二进制里的 16位,刚好可以作为密钥对 ticketKey 进行AES-128解密, 得到的字符串也是 32位,如果把其当做 HEX 也刚好可以作为密钥对 临时密码 进行AES-128加密,可以得到一个32位的HEX字符串. 但是涂鸦同样显示 illegal param 错误

allcam
Posts: 9

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

这是目前所用的代码,但是涂鸦显示 illegal param 错误码 1109,基本上可以确定还是加密解密过程错误:

Last edited by allcam on 2025年 Jan 1日 21:06, edited 1 time in total.
allcam
Posts: 9

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

这是目前所用的代码,但是涂鸦显示 illegal param 错误码 1109,基本上可以确定还是加密解密过程错误:

// Decrypt the temporary key using AES-128-ECB
const decryptTicketKey = (encryptedKey, TUYA_SECRET_KEY) => {
try {
const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(TUYA_SECRET_KEY.substring(8, 24), 'utf8'), null);
decipher.setAutoPadding(false);
const decrypted = Buffer.concat([decipher.update(Buffer.from(encryptedKey, 'hex')), decipher.final()]);
// console.log('ticketKey Length (HEX):', encryptedKey.length); // Check the length of encryptedKey
// console.log('decrypted Key Length (HEX):', decrypted.length); // Check the length


Code: Select all

    const hexKey = decrypted.toString('hex').slice(0, 32); // treat the decrypted Key as a 32-character hex string (rather than as a binary in buffer)
    console.log('decrypted Key treat as hex, not the Binary in buffer:', hexKey);
    return hexKey;		
} catch (error) {
    console.error('Error decrypting ticketKey:', error.message);
    throw error;
}

};

// Set a temporary password for the Tuya device
const setTemporaryPassword = async ({ deviceId, name, password, effectiveTime, invalidTime }) => {
try {
// Fetch the temporary ticket
const ticket = await fetchTemporaryKey(deviceId);
const decryptedKey = decryptTicketKey(ticket.ticket_key, TUYA_SECRET_KEY);
// Encrypt the password using the decrypted ticket key
const cipher = crypto.createCipheriv(
'aes-128-ecb',
Buffer.from(decryptedKey, 'hex'), // Ensure decryptedKey is treated as 16 bytes binary
null
);
cipher.setAutoPadding(true);
const encryptedPassword = Buffer.concat([
cipher.update(Buffer.from(password, 'utf8')),
cipher.final()
]).toString('hex');

// Prepare the schedule_list (example values, adjust as needed)
const scheduleList = [
{
effective_time: 0, // Example: 0:00 (in minutes)
invalid_time: 1440, // Example: 24:00 (in minutes)
working_day: 0 // Example: All days (or specify specific days as required)
}
];
console.log('Request Payload:', {
password: encryptedPassword,
effective_time: effectiveTime,
invalid_time: invalidTime,
password_type: 'ticket',
ticket_id: ticket.ticket_id,
effective_time: effectiveTime,
invalid_time: invalidTime,
schedule_list: scheduleList,
});
// Send the request to Tuya
const response = await tuya.request({
method: 'POST',
path: /v1.0/devices/${deviceId}/door-lock/temp-password,
body: {
password: encryptedPassword,
effective_time: effectiveTime,
invalid_time: invalidTime,
password_type: 'ticket',
ticket_id: ticket.ticket_id,
schedule_list: scheduleList, // Include schedule_list in the payload
},
});

Code: Select all

if (response.success) {
  console.log(`Temporary password set successfully for device ${deviceId}`);
  return response.result;
} else {
  throw new Error(`Failed to set temporary password: ${response.msg}`);
}

} catch (error) {
console.error(Error setting temporary password for device ${deviceId}:, error.message);
throw error;
}
};

panda-cat
Posts: 25

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

测试了下这个流程,使用aes-256-ecb就可以直接使用完整的SECRET_KEY进行解密,不用做任何处理,以下是我测试的代码,可以试试看,解密出来的秘钥也是16位,可以正常用于下一步流程 ヾ(◍°∇°◍)ノ゙。

const crypto = require('crypto');
function decrypt(encryptedText, key) {
const secretKey = Buffer.from(key, 'utf-8');
const decipher = crypto.createDecipheriv('aes-256-ecb', secretKey, null);
const encryptedBytes = hexStringToByteArray(encryptedText);
let decrypted = decipher.update(encryptedBytes);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString('utf8');
}

function hexStringToByteArray(hex) {
let data = [];
for (let i = 0; i < hex.length; i += 2) {
data.push(parseInt(hex.substr(i, 2), 16));
}
return Buffer.from(data);
}

allcam
Posts: 9

Re: 智能锁 临时密码 ticketKey 解密 加密 问题

谢谢大神,使用aes-256-ecb就可以直接使用完整的SECRET_KEY进行解密 就好了。后面对临时密码的加密还是要使用 aes-128-ecb

Post Reply