Implement 8370 message encryption
This commit is contained in:
parent
d1bef25ac9
commit
2aec54c7a7
7 changed files with 140 additions and 40 deletions
14
device.py
14
device.py
|
@ -171,12 +171,26 @@ class MiedaDevice(threading.Thread):
|
|||
|
||||
def send_message_v3(self, data, msg_type=MSGTYPE_ENCRYPTED_REQUEST):
|
||||
data = self._security.encode_8370(data, msg_type)
|
||||
|
||||
print("=== encrypted ===")
|
||||
print(data)
|
||||
|
||||
self.send_message_v2(data)
|
||||
|
||||
def build_send(self, cmd):
|
||||
data = cmd.serialize()
|
||||
|
||||
print("=== data ===")
|
||||
print(data)
|
||||
print
|
||||
|
||||
_LOGGER.debug(f"[{self._device_id}] Sending: {cmd}")
|
||||
msg = PacketBuilder(self._device_id, data).finalize()
|
||||
|
||||
print("=== msg ===")
|
||||
print(msg)
|
||||
print
|
||||
|
||||
self.send_message(msg)
|
||||
|
||||
def refresh_status(self, wait_response=False):
|
||||
|
|
25
midea.py
25
midea.py
|
@ -71,31 +71,6 @@ async def test():
|
|||
if dev.connect(True):
|
||||
return dev
|
||||
|
||||
|
||||
|
||||
t = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f")[:16]
|
||||
|
||||
t = "20230929142324212188673"[:16]
|
||||
|
||||
print(t)
|
||||
|
||||
b = bytearray()
|
||||
|
||||
for i in range(0, len(t), 2):
|
||||
|
||||
|
||||
tmp = t[i:i+2]
|
||||
d = int(tmp)
|
||||
|
||||
|
||||
b.insert(0, d)
|
||||
|
||||
|
||||
print(b)
|
||||
|
||||
b_ref = bytearray([21, 24, 23, 14, 29, 9, 23, 20])
|
||||
print(b_ref)
|
||||
|
||||
dev = asyncio.run(test())
|
||||
|
||||
print(dev)
|
||||
|
|
|
@ -6,6 +6,9 @@ pub trait RequestSerializer {
|
|||
}
|
||||
|
||||
pub struct CommandRequest {
|
||||
#[cfg(debug_assertions)]
|
||||
pub name: &'static str,
|
||||
|
||||
command: Command,
|
||||
}
|
||||
|
||||
|
@ -19,6 +22,7 @@ impl CommandRequest {
|
|||
device_type: u8,
|
||||
message_type: MessageType,
|
||||
body: Body,
|
||||
#[cfg(debug_assertions)] name: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
command: Command {
|
||||
|
@ -30,6 +34,8 @@ impl CommandRequest {
|
|||
),
|
||||
body,
|
||||
},
|
||||
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +75,9 @@ impl CommandQuerySubtype {
|
|||
0,
|
||||
device_type,
|
||||
MessageType::QuerySubtype,
|
||||
Body::from([0x00; 18].as_slice()),
|
||||
Body::from(([0x00; 18].as_slice(), 0x00)),
|
||||
#[cfg(debug_assertions)]
|
||||
"QuerySubtype",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +108,14 @@ pub struct CommandQueryCustom {
|
|||
impl CommandQueryCustom {
|
||||
pub fn new(device_type: u8, message_type: MessageType, body: Body) -> Self {
|
||||
Self {
|
||||
command: CommandRequest::new(0, device_type, message_type, body),
|
||||
command: CommandRequest::new(
|
||||
0,
|
||||
device_type,
|
||||
message_type,
|
||||
body,
|
||||
#[cfg(debug_assertions)]
|
||||
"QueryCustom",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -151,7 +151,7 @@ impl Device {
|
|||
}
|
||||
|
||||
fn parse_message(&mut self, msg: &[u8]) -> Result<ParseMessage> {
|
||||
let (messages, buffer) = self.security.decode_8370(&self.buffer, msg)?;
|
||||
let (messages, buffer) = self.security.decode_8370([&self.buffer, msg].concat())?;
|
||||
self.buffer = buffer;
|
||||
|
||||
if messages.is_empty() {
|
||||
|
@ -207,6 +207,12 @@ impl Device {
|
|||
|
||||
fn send_message(&self, msg: &[u8]) -> Result<()> {
|
||||
let data = self.security.encode_8370(msg, MsgType::ENCRYPTED_REQUEST)?;
|
||||
|
||||
// encrypted
|
||||
let e = b"\x83p\x00~ fq\xa8\xa9b(\x8c\r,\x96X\xbdT\x1d\x06\xa1/\xb5@\xa2\xeb\x96\x0c\x01s\xf0\x8c\x98ELT\x89\x81\xcc\x9d\xaa\xb6[dq\xcf\x98\xd1s\x8c\x08\x0e\xe6u1D\x80\x17I\xe2\x987s\xbe\xb2\xa9\x13\x86\xce\xb3qq\xfe\xa6\x11\x10\xcfi\xc2\xaeXJH\xb8\xa8\x0b5\x08z\x00\xec\xa2t\x13\xeds\xe7:\x0f\x0eP\xfe\x80w7\xbb\xdf\x0f\x14D\xfd9\xceZ\xda\x1a\xda\xfb\x0b\xe0\x92\xc2D\xb4\xdfWE\x89_\xd9\xd0\xb2\xb6\xd9";
|
||||
|
||||
// assert_eq!(data, e);
|
||||
|
||||
self.socket.lock().unwrap().write(&data)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -219,6 +225,12 @@ impl Device {
|
|||
|
||||
fn build_send(&self, cmd: impl RequestSerializer) -> Result<()> {
|
||||
let data = PacketBuilder::builder(self.info.id, cmd).finalize(1);
|
||||
|
||||
// msg
|
||||
let d = b"ZZ\x01\x11X\x00 \x00\x00\x00\x00\x00\x0435\x06\x02\n\x17\x14\x02\x86\x02\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17G\x0b\xc3\xe7\r\xaa\xb93\x81r0?\x02s\xf8\x88\xe9G)\xa5\x127\x99\xdd\x8afi\xbd\xf6\xc3\xf5\xf1B\x84\x8a&n\xd9\xdb\xba\xee(\\\'\xb2\x0c\x02";
|
||||
|
||||
// assert_eq!(data, d);
|
||||
|
||||
self.send_message(&data)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -293,9 +293,14 @@ pub struct CommandE1Base {
|
|||
}
|
||||
|
||||
impl CommandE1Base {
|
||||
pub fn new(device_protocol_version: u8, message_type: MessageType, body: Body) -> Self {
|
||||
pub fn new(
|
||||
device_protocol_version: u8,
|
||||
message_type: MessageType,
|
||||
body: Body,
|
||||
#[cfg(debug_assertions)] name: &'static str,
|
||||
) -> Self {
|
||||
Self {
|
||||
command: CommandRequest::new(device_protocol_version, 0xE1, message_type, body),
|
||||
command: CommandRequest::new(device_protocol_version, 0xE1, message_type, body, name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -325,6 +330,8 @@ impl CommandPower {
|
|||
device_protocol_version,
|
||||
MessageType::Set,
|
||||
Body::from(([0x00; 4].as_slice(), 0x08)),
|
||||
#[cfg(debug_assertions)]
|
||||
"Power",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -361,6 +368,8 @@ impl CommandLock {
|
|||
device_protocol_version,
|
||||
MessageType::Set,
|
||||
Body::from((concat!(vec![0x04], vec![0x00; 36],).as_slice(), 0x08)),
|
||||
#[cfg(debug_assertions)]
|
||||
"Lock",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -400,6 +409,8 @@ impl CommandStorage {
|
|||
concat!(vec![0x00; 4], vec![0xFF; 6], vec![0x00; 27],).as_slice(),
|
||||
0x81,
|
||||
)),
|
||||
#[cfg(debug_assertions)]
|
||||
"Storage",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -436,6 +447,8 @@ impl CommandQuery {
|
|||
device_protocol_version,
|
||||
MessageType::Query,
|
||||
Body::from(([].as_slice(), 0x00)),
|
||||
#[cfg(debug_assertions)]
|
||||
"Query",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,12 @@ impl<S: RequestSerializer> PacketBuilder<S> {
|
|||
packet[6] = 0x7b;
|
||||
} else {
|
||||
let mut data = self.command.serialize();
|
||||
|
||||
// data
|
||||
let d = b"\xaa\x1d\xe1\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00b";
|
||||
|
||||
assert_eq!(data, d);
|
||||
|
||||
Security::aes_encrypt(&mut data);
|
||||
|
||||
packet.extend(data);
|
||||
|
|
|
@ -74,13 +74,18 @@ impl Security {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn aes_cbc_encrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||
pub fn aes_cbc_encrypt(&self, raw: &[u8], key: &[u8; 32]) -> Vec<u8> {
|
||||
type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
|
||||
|
||||
debug_assert_eq!(raw.len() % key.len(), 0);
|
||||
|
||||
raw.chunks(key.len())
|
||||
.map(|r| {
|
||||
Aes256CbcEnc::new(key.into(), &Self::IV.into())
|
||||
.encrypt_padded_vec_mut::<NoPadding>(&raw)
|
||||
.try_into()
|
||||
.unwrap()
|
||||
.encrypt_padded_vec_mut::<NoPadding>(r)
|
||||
})
|
||||
.flatten()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn aes_cbc_decrypt(&self, raw: [u8; 32], key: &[u8; 32]) -> [u8; 32] {
|
||||
|
@ -163,9 +168,7 @@ impl Security {
|
|||
.into_iter()
|
||||
.collect();
|
||||
|
||||
data = self
|
||||
.aes_cbc_encrypt(data.try_into().unwrap(), self.tcp_key.as_ref().unwrap())
|
||||
.to_vec();
|
||||
data = self.aes_cbc_encrypt(&data, self.tcp_key.as_ref().unwrap());
|
||||
data.extend(sign);
|
||||
}
|
||||
|
||||
|
@ -178,8 +181,70 @@ impl Security {
|
|||
lhs
|
||||
}
|
||||
|
||||
pub fn decode_8370(&self, buffer: &[u8], msg: &[u8]) -> Result<(Vec<Vec<u8>>, Vec<u8>)> {
|
||||
todo!()
|
||||
pub fn decode_8370(&self, mut data: Vec<u8>) -> Result<(Vec<Vec<u8>>, Vec<u8>)> {
|
||||
if data.len() < 6 {
|
||||
return Ok((Vec::new(), data));
|
||||
}
|
||||
|
||||
let header = data[..6].to_vec();
|
||||
|
||||
if header[0] != 0x83 || header[1] != 0x70 {
|
||||
bail!("no an 8370 message");
|
||||
} else if header[4] != 0x20 {
|
||||
bail!("missing byte 4");
|
||||
}
|
||||
|
||||
let size = u16::from_be_bytes(header[2..4].try_into().unwrap()) as usize + 8;
|
||||
|
||||
let mut leftover = Vec::new();
|
||||
|
||||
if data.len() < size {
|
||||
return Ok((Vec::new(), data.to_vec()));
|
||||
} else if data.len() > size {
|
||||
leftover = data[size..].to_vec();
|
||||
data = data[..size].to_vec();
|
||||
}
|
||||
|
||||
let padding = header[5] >> 4;
|
||||
let msgtype = header[5] & 0xf;
|
||||
data = data[6..].to_vec();
|
||||
|
||||
if msgtype == MsgType::ENCRYPTED_RESPONSE as u8
|
||||
|| msgtype == MsgType::ENCRYPTED_REQUEST as u8
|
||||
{
|
||||
let sign = data[(data.len() - 32)..].to_vec();
|
||||
data = data[..(data.len() - 32)].to_vec();
|
||||
data = self
|
||||
.aes_cbc_decrypt(data.try_into().unwrap(), &self.tcp_key.unwrap())
|
||||
.to_vec();
|
||||
|
||||
let compare_sign: Vec<u8> = Sha256::digest(&[header.to_vec(), data.clone()].concat())
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
if sign != compare_sign {
|
||||
bail!("sign does not match");
|
||||
}
|
||||
|
||||
if padding != 0 {
|
||||
data = data[..(data.len() - padding as usize)].to_vec();
|
||||
}
|
||||
}
|
||||
|
||||
self.response_count
|
||||
.store(u16::from_be_bytes(data[..2].try_into().unwrap()), SeqCst);
|
||||
|
||||
data = data[2..].to_vec();
|
||||
|
||||
if leftover.len() > 0 {
|
||||
let (mut packets, incomplete) = self.decode_8370(leftover)?;
|
||||
|
||||
packets.insert(0, data);
|
||||
|
||||
Ok((packets, incomplete))
|
||||
} else {
|
||||
Ok((vec![data], Vec::new()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue