# 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. – Robert Sep 15 '20 at 16:25

## 2 Answers

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

def pad(xs,w):
n = len(xs)
target_n = (-(-n//w)) * w
delta = target_n - n
xs_padded = xs+[0]*delta
return xs_padded

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_padded = pad(xs,w)
xs_chunked = chunk(xs_padded,w)
xs_ints = [to_int(x) for x in xs_chunked]
return xs_ints

def calculate_checksum(payload):
magicValue = 0xaa
mask = 0xFF

checksum = 0
for element in payload:
checksum = operator.sub(checksum,element)
checksum =  operator.add(checksum,magicValue)
return checksum & mask

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

msg = preprocess(rawmsg,width)
payload = msg[msgStart:]
checksum = msg[checksumPos]
payload[checksumPos] = 0

return calculate_checksum(payload) == checksum

# ================================================================================
# 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
# --------------------------------------------------------------------------------
``````