# What checksum algorithm is this?

I'm trying to control an air conditioning unit. The app and the unit communicate using the Broadlink protocol. I can decode the settings it's sending and replay them.

The payload is 32 bytes but only the middle 15 ever change (prefixed with 19 00 bb 00 06 80 00 00 0f 00 01 01 and suffixed with zeros). I think the last two bytes are some kind of checksum, but I'm unsuccessful in recreating it.

Here's a sample (more):

9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19
9f e4 07 60 00 20 00 00 20 00 00 00 00 67 19
9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19
97 e4 87 60 00 20 00 00 20 00 00 00 00 ef 18
97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19
9f e4 87 60 00 20 00 00 20 00 00 00 00 e7 18
a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19
a7 e4 07 60 00 80 00 00 20 00 00 00 00 5e b9
a7 e4 07 40 00 20 00 00 20 00 00 00 00 5f 39
a7 e4 07 20 00 20 00 00 20 00 00 00 00 5f 59
a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59

I think it's related to a sum because the same number of bits change in the sum, but I've calculated the difference and the XOR between the sum and the actual checksum and they aren't constant. Also tried reversing the bits with no luck.

• If it is an app usually the easiest way to understand how the CRC is calculated is by decompiling the app. Sep 15, 2020 at 16:25

I'll give a stab at this. It's hard to tell without more context and information, it appears that the last two bytes are a little endian value added to the sum to make it equal some number. Possibly 0xff17 if it sums every single value. Without knowing more information, it's impossible to tell which bytes it's checking but assuming we check everything, I think they are used something like this:

def calculate(x):
for arr in x:
diff = 0x80EA # sum of the first 6 little endian words
diff += (arr[12] + (arr[13] << 8)) # add seventh little endian word
diff += (arr[14] + (arr[15] << 8)) # add eighth little endian word
diff += (arr[16] + (arr[17] << 8)) # add ninth little endian word
# diff += (arr[18] + (arr[19] << 8)) these values are all zeros...
diff += (arr[20] + (arr[21] << 8)) # add eleventh little endian word
diff += (arr[22] + (arr[23] << 8)) # add twelfth little endian word
diff += arr[24]
checksum = (arr[25] + (arr[26] << 8) & 0xffff) # grab checksum
print(hex((diff+checksum) & 0xffff))

When I run this with you list of 171 samples as input, I only get three unique values, and they're all within a byte of each other.

~/ python3 test.py | sort | uniq | wc
3       3      21
~/ python3 test.py | sort | uniq -c
21 0xff16
146 0xff17
4 0xff18

Assuming you are correct about the first 12 bytes not changing, perhaps these are due to transmission error (that's the point of a checksum, right?) or I am missing something in checksum algorithm.

As a side note, the first two bytes 19 00 may refer to the payload length in bytes.

I think there may be some noise on the line. Lauren Labell has a tool to try and automate reversing checksums: https://github.com/laurenlabell/checksum_finder

Here's what it generated:

#  start: 0 end: 0 check: 13 foldOp: <built-in function sub> finalOp: <built-in function add> magicValue: 0xaa
# ================================================================================
# Generated Code
# --------------------------------------------------------------------------------

import operator

def twosComp(n):
return -n

def onesComp(n1, n2):
mod = 1 << 8
result = n1 + n2
return result if result < mod else (result + 1) % mod

n = len(xs)
target_n = (-(-n//w)) * w
delta = target_n - n

def chunk(xs,w):
xs_chunked = [xs[i:i+w] for i in range(0,len(xs),w)]
return xs_chunked

def to_int(x):
return int.from_bytes(bytes(x),'big')

def preprocess(hex_str,w):
hex_str = ''.join(hex_str.split(' '))
xs = [x for x in bytes.fromhex(hex_str)]
xs_ints = [to_int(x) for x in xs_chunked]
return xs_ints

magicValue = 0xaa

checksum = 0
checksum = operator.sub(checksum,element)

def validate_message(rawmsg):
msgStart = 0
msgEnd = 0
checksumPos = 13
width = 1

msg = preprocess(rawmsg,width)
checksum = msg[checksumPos]

# ================================================================================
# Unit Tests
# --------------------------------------------------------------------------------

print(validate_message('9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19'),'9f e4 07 60 00 20 00 00 00 00 00 00 00 87 19')
print(validate_message('9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19'),'9f e4 2d 60 00 20 00 00 20 00 00 00 00 41 19')
print(validate_message('97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19'),'97 e4 07 60 00 20 00 00 20 00 00 00 00 6f 19')
print(validate_message('a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19'),'a7 e4 07 60 00 20 00 00 20 00 00 00 00 5f 19')
print(validate_message('a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59'),'a7 e4 07 20 40 20 00 00 20 00 00 00 00 1f 59')

# --------------------------------------------------------------------------------
# End Generated Code
# --------------------------------------------------------------------------------