Last active
July 27, 2026 15:32
-
-
Save JJTech0130/3ff9b5c0b348326f12f7c41d30464158 to your computer and use it in GitHub Desktop.
Ford UDS SecurityAccess seed-key algorithms [untested]
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Ford ECU SecurityAccess seed-key algorithms, reversed from libfnvfsa.so on the ECG2 GWM | |
| Algorithm 0: legacy 24-bit Galois LFSR cipher (3-byte seed, 5-byte/40-bit secret) | |
| Described in https://flaviodgarcia.com/publications/BtB.pdf | |
| Algorithms 1/2: modern HMAC-SHA1 (16-byte seed, 12-byte fixedBytes) | |
| TODO: Test against a real vehicle | |
| """ | |
| import argparse | |
| from cryptography.hazmat.primitives import hashes, hmac | |
| FORD_LFSR_INIT = 0xC541A9 | |
| FORD_LFSR_TAPS = 0x109028 | |
| def ford_legacy_key(seed: bytes, fixed: bytes) -> bytes: | |
| """Algorithm 0. seed: 3 bytes. fixed: 5 bytes (40-bit magic secret).""" | |
| if len(seed) != 3 or len(fixed) != 5: | |
| raise ValueError("algorithm 0 needs a 3-byte seed and 5-byte fixed value") | |
| f = int.from_bytes(fixed, "big") | |
| cc = [f & 0xFF, (f >> 8) & 0xFF, (f >> 16) & 0xFF, (f >> 24) & 0xFF, (f >> 32) & 0xFF, | |
| seed[2], seed[1], seed[0]] | |
| state = FORD_LFSR_INIT | |
| for _ in range(64): | |
| bbit = (state & 1) ^ (cc[7] & 1) | |
| state = ((state >> 1) + bbit * 0x800000) | |
| if bbit: | |
| state ^= FORD_LFSR_TAPS | |
| cc[7] = (cc[7] >> 1) & 0xFF | |
| for a in range(7, 0, -1): | |
| cc[a] = (cc[a] + (cc[a - 1] & 1) * 128) & 0xFF | |
| cc[a - 1] = cc[a - 1] >> 1 | |
| return bytes([ | |
| (state >> 4) & 0xFF, | |
| (((state >> 12) & 0xF) << 4) + ((state >> 20) & 0xF), | |
| ((state >> 16) & 0xF) + ((state & 0xF) << 4), | |
| ]) | |
| def ford_hmac_key(mode: int, seed: bytes, fixed_bytes: bytes) -> bytes: | |
| """Algorithm 1 or 2. seed: 16 bytes. fixed_bytes: 12 bytes.""" | |
| if len(seed) != 16 or len(fixed_bytes) != 12: | |
| raise ValueError("algorithms 1/2 need a 16-byte seed and 12-byte fixedBytes") | |
| if mode == 1: | |
| data = b"zaCK" + seed | |
| elif mode == 2: | |
| data = seed + b"JaKe" | |
| else: | |
| raise ValueError("mode must be 1 or 2") | |
| h = hmac.HMAC(fixed_bytes, hashes.SHA1()) | |
| h.update(data) | |
| digest = h.finalize() | |
| return digest[4:20] if mode == 1 else digest[0:18] | |
| def get_unlock_key(algorithm: int, seed: bytes, fixed_bytes: bytes) -> bytes: | |
| if algorithm == 0: | |
| return ford_legacy_key(seed, fixed_bytes) | |
| if algorithm in (1, 2): | |
| return ford_hmac_key(algorithm, seed, fixed_bytes) | |
| raise ValueError(f"unknown algorithm {algorithm}") | |
| def main(): | |
| p = argparse.ArgumentParser(description="Ford ECU SecurityAccess seed-key calculator") | |
| p.add_argument("algorithm", type=int, choices=[0, 1, 2]) | |
| p.add_argument("seed", help="hex challenge/seed") | |
| p.add_argument("fixed_bytes", help="hex secret/fixedBytes") | |
| args = p.parse_args() | |
| key = get_unlock_key(args.algorithm, bytes.fromhex(args.seed), bytes.fromhex(args.fixed_bytes)) | |
| print(key.hex()) | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment