Overview
All packets sent to the Whoop 4.0 device include a 4-byte checksum for validation. The checksum is a custom CRC-32 variant with non-standard parameters.
Checksum Requirements
Packets sent without a valid checksum will be rejected by the device. The checksum must be calculated correctly for commands to work.
The checksum:
- Is 4 bytes (32 bits) long
- Appears at the end of every packet
- Is in little-endian byte order
- Covers all bytes before the checksum itself
- Does not match any standard CRC-32 implementation (CRC-32, CRC-32/BZIP2, etc.)
Failed Attempts
Standard CRC calculators do not work. Testing with crccalc.com shows no matches:
Input: aa100057236d4201d036656600000000 (hex)
Expected: f62deb81
CRC-32: [No match]
CRC-32/BZIP2: [No match]
CRC-32/MPEG-2: [No match]
... etc
Reverse Engineering with crcbeagle
@colinoflynn created crcbeagle, a Python tool for reverse engineering CRC algorithms from sample data.
Installation
git clone https://github.com/colinoflynn/crcbeagle
cd crcbeagle
pip install -r requirements.txt
Analysis Script
Create examine.py with sample packets and their known checksums:
from crcbeagle.crcbeagle import CRCBeagle
crc = CRCBeagle()
def hex_to_int(hex):
return [i for i in bytearray.fromhex(hex)]
# Sample packets (checksum removed)
data = """
aa100057236d4201d036656600000000f62deb81
aa100057236e42010c376566000000001023ccef
aa100057236f4201207d656600000000fea1e060
aa100057237042015011656600000000f226a8bd
""".split('\n')
data = [i for i in data if i]
# Extract checksums (last 8 hex chars = 4 bytes)
checksums = [i[len(i) - 8:] for i in data]
# Extract data (everything before checksum)
data = [i[:len(i) - 8] for i in data]
# Run the search
crc.search(
[hex_to_int(i) for i in data],
[hex_to_int(i) for i in checksums]
)
Running the Analysis
Output
crcbeagle will identify the CRC parameters and output working code:
import struct
from crccheck.crc import Crc32Base
crc = Crc32Base
def my_crc(message):
crc._poly = 0x4C11DB7
crc._reflect_input = True
crc._reflect_output = True
crc._initvalue = 0x0
crc._xor_output = 0xF43F44AC
output_int = crc.calc(message)
output_bytes = struct.pack("<I", output_int)
output_list = list(output_bytes)
return (output_int, output_bytes, output_list)
Testing the CRC
# Test with known packet
m = [170, 16, 0, 87, 35, 109, 66, 1, 208, 54, 101, 102, 0, 0, 0, 0]
output = my_crc(m)
print(hex(output[0])) # Should print: 0xf62deb81
CRC Parameters
The Whoop 4.0 uses these CRC-32 parameters:
| Parameter | Value | Description |
|---|
| Polynomial | 0x04C11DB7 | CRC-32 standard polynomial |
| Initial Value | 0x00000000 | Starting value |
| Reflect Input | True | Bit-reverse each input byte |
| Reflect Output | True | Bit-reverse final CRC value |
| XOR Output | 0xF43F44AC | Non-standard XOR mask (this is the key difference) |
The custom XOR output value (0xF43F44AC) is what makes this CRC unique and incompatible with standard calculators.
Python Implementation
Complete implementation using the crccheck library:
import struct
from crccheck.crc import Crc32Base
class WhoopCRC:
def __init__(self):
self.crc = Crc32Base
self.crc._poly = 0x4C11DB7
self.crc._reflect_input = True
self.crc._reflect_output = True
self.crc._initvalue = 0x0
self.crc._xor_output = 0xF43F44AC
def calculate(self, data):
"""Calculate CRC for data (bytes or bytearray)"""
crc_int = self.crc.calc(data)
crc_bytes = struct.pack("<I", crc_int) # Little-endian
return crc_int, crc_bytes
def calculate_hex(self, hex_string):
"""Calculate CRC for hex string (without spaces)"""
data = bytearray.fromhex(hex_string)
crc_int, crc_bytes = self.calculate(data)
return crc_bytes.hex()
# Usage
whoop_crc = WhoopCRC()
# From hex string
packet_hex = "aa100057236d4201d036656600000000"
checksum_hex = whoop_crc.calculate_hex(packet_hex)
print(f"Checksum: {checksum_hex}") # f62deb81
# From bytes
packet_bytes = bytearray.fromhex(packet_hex)
checksum_int, checksum_bytes = whoop_crc.calculate(packet_bytes)
print(f"Checksum int: {hex(checksum_int)}") # 0xf62deb81
print(f"Checksum bytes: {checksum_bytes.hex()}") # f62deb81
Building Complete Packets
Example: Creating a command with proper checksum
import struct
import time
def build_alarm_packet(unix_timestamp):
"""Build alarm packet with valid checksum"""
# Packet header and data (without checksum)
header = "aa10005723"
packet_count = "70" # Can be any value
flags = "4201"
unix_hex = struct.pack('<I', unix_timestamp).hex()
padding = "00000000"
# Combine packet data
packet_data = f"{header}{packet_count}{flags}{unix_hex}{padding}"
# Calculate checksum
whoop_crc = WhoopCRC()
checksum_hex = whoop_crc.calculate_hex(packet_data)
# Complete packet
return f"{packet_data}{checksum_hex}"
# Set alarm for 10 seconds from now
future_time = int(time.time()) + 10
packet = build_alarm_packet(future_time)
print(f"Packet to send: {packet}")
Validation Testing
Verify your implementation with these known packet/checksum pairs:
test_packets = [
("aa100057236d4201d036656600000000", "f62deb81"),
("aa100057236e42010c37656600000000", "1023ccef"),
("aa100057236f4201207d656600000000", "fea1e060"),
("aa100057237042015011656600000000", "f226a8bd"),
("aa0800a823070e00", "c7e40f08"),
("aa0800a823080e01", "6c935474"),
]
whoop_crc = WhoopCRC()
for data, expected_checksum in test_packets:
calculated = whoop_crc.calculate_hex(data)
status = "✓" if calculated == expected_checksum else "✗"
print(f"{status} {data} -> {calculated} (expected: {expected_checksum})")
All tests should pass (✓) if your implementation is correct.
Alternative: Manual CRC Calculation
If you don’t want to use crccheck, here’s a manual implementation:
import struct
def whoop_crc32(data):
"""Manual CRC-32 calculation with Whoop parameters"""
crc = 0x00000000 # Initial value
poly = 0x04C11DB7
for byte in data:
# Reflect input byte
byte = int('{:08b}'.format(byte)[::-1], 2)
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc >>= 1
# Reflect output
crc = int('{:032b}'.format(crc)[::-1], 2)
# XOR output
crc ^= 0xF43F44AC
return crc
# Test
data = bytearray.fromhex("aa100057236d4201d036656600000000")
checksum = whoop_crc32(data)
print(hex(checksum)) # 0xf62deb81
The manual implementation above is simplified and may not handle bit reflection correctly in all cases. Use the crccheck library for production code.