Home > Software design >  Autokey Encryption
Autokey Encryption

Time:03-14

I am working on a project to write to and read from a TP Link / Kaza power strip or smart plug.

The data that is sent is encrypted json that has been "autokey encrypted".

So far I have been able to convert a typescript encrypt function and it works well. I get the expected result. However, I need to add a "header" to my encrypted data. That data is 3 null bytes followed by a byte that is a measure of the length of the encrypted bytes.

The typescript example has this bit of code to "encrypt with headers", however, I've hit a bit of a wall trying to convert it to something usable. Can someone nudge me along the path ?

First are the two typescript functions: (borrowed from https://github.com/plasticrake/tplink-smarthome-crypto/blob/master/src/index.ts)

/**
 * Encrypts input where each byte is XOR'd with the previous encrypted byte.
 *
 * @param input - Data to encrypt
 * @param firstKey - Value to XOR first byte of input
 * @returns encrypted buffer
 */
export function encrypt(input: Buffer | string, firstKey = 0xab): Buffer {
  const buf = Buffer.from(input);
  let key = firstKey;
  for (let i = 0; i < buf.length; i  = 1) {
    // eslint-disable-next-line no-bitwise
    buf[i] ^= key;
    key = buf[i];
  }
  return buf;
}
/**
 * Encrypts input that has a 4 byte big-endian length header;
 * each byte is XOR'd with the previous encrypted byte.
 *
 * @param input - Data to encrypt
 * @param firstKey - Value to XOR first byte of input
 * @returns encrypted buffer with header
 */
export function encryptWithHeader(
  input: Buffer | string,
  firstKey = 0xab
): Buffer {
  const msgBuf = encrypt(input, firstKey);
  const outBuf = Buffer.alloc(msgBuf.length   4);
  outBuf.writeUInt32BE(msgBuf.length, 0);
  msgBuf.copy(outBuf, 4);
  return outBuf;
}

Second is what I have so far.

// This part works well and produces the expected results
String encrypt(String input)
{
    int16_t firstKey = 0xab;
    String buf;
    int key;
    int i;
    buf = input;
    key = firstKey;
    i = 0;
    for (;i < buf.length();(i = i   1))
    {
        buf[i] ^= key;
        key = buf[i];
    }
    return buf;
}

// This does not function yet, as I'm pretty lost..
// This was orginally converted from typescript with https://andrei-markeev.github.io/ts2c/
// I started work on converting this, but ran into errors I don't know how to solve. 

String encryptWithHeader(String input){
    String msgBuf;
    String outBuf;
    int16_t firstKey = 0xab;
    char * null = NULL;
    msgBuf = encrypt(input);
    outBuf = msgBuf.length()  1;
  
//this is where I got lost...  
    assert(null != NULL);
    null[0] = '\0';
    strncat(null, outBuf, msgBuf.length());
    str_int16_t_cat(null, 4);
    outBuf = msgBuf   4
    return outBuf;

}

Finally, the data:

//this is the unencrypted json
String offMsg = "{\"system\":{\"set_relay_state\":{\"state\":0}}}";

//current encrypt function produces:
d0f281f88bff9af7d5ef94b6c5a0d48bf99cf091e8b7c4b0d1a5c0e2d8a381f286e793f6d4eedea3dea3

//the working "withheaders" should produce:

00002ad0f281f88bff9af7d5ef94b6c5a0d48bf99cf091e8b7c4b0d1a5c0e2d8a381f286e793f6d4eedea3dea3 

Admittedly my C/C ability is very limited and I can spell typescript, that's about all. I have a very extensive history with PHP. As useful as that is. So, I understand the basics of data structures and whatnot, but I'm venturing off into areas I've never been in. Any help would be greatly appreciated.

CodePudding user response:

It looks like the encryption is fairly simple: write the current character XORed with the key to the buffer and make that newly written character the new key. It also looks like the "withHeaders" version adds the length of the encrypted string as a 4 byte integer to the start of the buffer. I think it might be easier to allocate a character array and pass that array to a function that writes the result to that buffer. For example:

void encryptWithHeader(byte buffer[], int bufferLength, byte key, String message) {
  int i;
  uint32_t messageLength = message.length();

  Serial.println(message);
  Serial.println(message.length());

  // check that we won't overrun the buffer
  if ( messageLength   5 < bufferLength) {
    buffer[0] = messageLength >> 24 & 0xFF;
    buffer[1] = messageLength >> 16 & 0xFF;
    buffer[2] = messageLength >> 8 & 0xFF;
    buffer[3] = messageLength & 0xFF;

    for (i = 0; i < messageLength; i  ) {
      buffer[i   4] = message[i] ^ key;
      key = buffer[i   4];
    }
  }
  else { // we would have overrun the buffer
    Serial.println("not enough room in buffer for message");
  }
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
}

void loop() {
  byte theBuffer[64];
  int i;
  String offMsg = "{\"system\":{\"set_relay_state\":{\"state\":0}}}";

  encryptWithHeader(theBuffer, 64, 0xab, offMsg);

  // now print it out to check
  for (i = 0; i < offMsg.length()   4; i  ) {
    if (theBuffer[i] < 0x10) // adds an extra zero if a byte prints as on1y 1 char
      Serial.print("0");
    Serial.print(theBuffer[i], HEX);
  }
  while (true)
    ;
}

If you want to send the character buffer to a remote device you can send it out one byte at a time:

for (i = 0; i < offMsg.length()   4; i  )
  Serial.write(theBuffer[i]);
  • Related