Skip to content

Commit

Permalink
Merge pull request #655 from nospam2k/patch-2
Browse files Browse the repository at this point in the history
Update cipher.js
  • Loading branch information
Apollon77 authored Aug 15, 2024
2 parents 787fffc + 68b7c06 commit 32fa7f6
Showing 1 changed file with 83 additions and 3 deletions.
86 changes: 83 additions & 3 deletions lib/cipher.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class TuyaCipher {
}

/**
* Sets the session key used for Protocol 3.4
* Sets the session key used for Protocol 3.4, 3.5
* @param {Buffer} sessionKey Session key
*/
setSessionKey(sessionKey) {
Expand All @@ -37,6 +37,10 @@ class TuyaCipher {
return this._encrypt34(options);
}

else if (this.version === '3.5') {
return this._encrypt35(options);
}

return this._encryptPre34(options);
}

Expand Down Expand Up @@ -82,6 +86,32 @@ class TuyaCipher {
return encrypted;
}

/**
* Encrypt data for protocol 3.5
* @param {Object} options Options for encryption
* @param {String} options.data data to encrypt
* @param {Boolean} [options.base64=true] `true` to return result in Base64
* @returns {Buffer|String} returns Buffer unless options.base64 is true
*/
_encrypt35(options) {
let encrypted;
let localIV = Buffer.from((Date.now() * 10).toString().substring(0, 12));
if(options.iv !== undefined) localIV = options.iv.slice(0, 12);

const cipher = crypto.createCipheriv('aes-128-gcm', this.getKey(), localIV);
if(options.aad !== undefined)
{
cipher.setAAD(options.aad);
encrypted = Buffer.concat([localIV, cipher.update(options.data), cipher.final(), cipher.getAuthTag(), Buffer.from([0x00, 0x00, 0x99, 0x66])]);
}
else
{
encrypted = Buffer.concat([cipher.update(options.data), cipher.final()]);
}

return encrypted;
}

/**
* Decrypts data.
* @param {String|Buffer} data to decrypt
Expand All @@ -93,6 +123,10 @@ class TuyaCipher {
return this._decrypt34(data);
}

else if (this.version === '3.5') {
return this._decrypt35(data);
}

return this._decryptPre34(data);
}

Expand Down Expand Up @@ -180,6 +214,52 @@ class TuyaCipher {
}
}

/**
* Decrypts data for protocol 3.5
* @param {String|Buffer} data to decrypt
* @returns {Object|String}
* returns object if data is JSON, else returns string
*/
_decrypt35(data) {
let result;
let header = data.slice(0, 14);
let iv = data.slice(14, 26);
let tag = data.slice(data.length - 16);
data = data.slice(26, data.length - 16);

try {
const decipher = crypto.createDecipheriv('aes-128-gcm', this.getKey(), iv);
decipher.setAuthTag(tag);
decipher.setAAD(header);

result = Buffer.concat([decipher.update(data), decipher.final()]);
result = result.slice(4); // remove 32bit return code
} catch (_) {
throw new Error('Decrypt failed');
}

// Try to parse data as JSON,
// otherwise return as string.
// 3.5 protocol
// {"protocol":4,"t":1632405905,"data":{"dps":{"101":true},"cid":"00123456789abcde"}}
try {
if (result.indexOf(this.version) === 0) {
result = result.slice(15);
}

const res = JSON.parse(result);
if ('data' in res) {
const resData = res.data;
resData.t = res.t;
return resData; // Or res.data // for compatibility with tuya-mqtt
}

return res;
} catch (_) {
return result;
}
}

/**
* Calculates a MD5 hash.
* @param {String} data to hash
Expand All @@ -192,14 +272,14 @@ class TuyaCipher {

/**
* Gets the key used for encryption/decryption
* @returns {String} sessionKey (if set for protocol 3.4) or key
* @returns {String} sessionKey (if set for protocol 3.4, 3.5) or key
*/
getKey() {
return this.sessionKey === null ? this.key : this.sessionKey;
}

/**
* Returns the HMAC for the current key (sessionKey if set for protocol 3.4 or key)
* Returns the HMAC for the current key (sessionKey if set for protocol 3.4, 3.5 or key)
* @param {Buffer} data data to hash
* @returns {Buffer} HMAC
*/
Expand Down

0 comments on commit 32fa7f6

Please sign in to comment.