Skip to content

feat(classic): MIFARE Classic key recovery via PN533 nested attack#240

Open
codebutler wants to merge 7 commits intoflipperfrom
classic-key-recovery
Open

feat(classic): MIFARE Classic key recovery via PN533 nested attack#240
codebutler wants to merge 7 commits intoflipperfrom
classic-key-recovery

Conversation

@codebutler
Copy link
Owner

Summary

  • Implements MIFARE Classic Crypto1 cipher in pure Kotlin (faithful port of crapto1 by blapost)
  • Adds raw MIFARE Classic communication via PN533 InCommunicateThru, bypassing the chip's internal Crypto1 handling to expose raw authentication nonces
  • Implements the nested attack: when dictionary keys fail and at least one sector key is known, automatically recovers unknown sector keys by exploiting the card's weak 16-bit PRNG
  • Integrated as a transparent fallback in ClassicCardReader — works on PN533 backends (desktop USB + web WebUSB)

New files

File Description
crypto1/Crypto1.kt 48-bit LFSR cipher: filter function, PRNG, parity, state management
crypto1/Crypto1Auth.kt 3-pass mutual auth protocol, CRC-A, encrypt/decrypt
crypto1/Crypto1Recovery.kt lfsr_recovery32 — recovers LFSR states from 32-bit keystream
crypto1/NestedAttack.kt Attack orchestration: PRNG calibration → nonce collection → key recovery
pn533/PN533RawClassic.kt Raw MIFARE via InCommunicateThru with CIU register control
5 test files ~1,400 lines of tests with crapto1-verified reference values

How it works

  1. ClassicCardReader tries known keys (default, MAD, user-provided, dictionary)
  2. If all keys fail for a sector and we're on a PN533 backend and at least one other sector was read:
  3. NestedAttack authenticates with the known key, sends nested AUTH to the target sector
  4. Card responds with encrypted nonce — predictable due to weak 16-bit PRNG
  5. lfsrRecovery32 finds candidate LFSR states from the extracted keystream
  6. Candidates are verified by attempting real authentication
  7. Card reading continues with the recovered key

Platforms

  • Desktop (JVM/USB) and Web (WebUSB) — supported (both use PN533)
  • Android/iOS — not supported (no raw NFC access through platform APIs)

Test plan

  • Verify ./gradlew :card:classic:jvmTest passes (all crypto + recovery tests)
  • Test with a real MIFARE Classic card on desktop with a PN533 reader (e.g. SCL3711)
  • Verify cards with default keys still read normally (no regression)
  • Verify key recovery triggers when a sector has a non-default key and another sector was readable
  • Test with MIFARE Classic 1K and 4K cards

🤖 Generated with Claude Code

Claude and others added 7 commits February 17, 2026 05:59
Faithful port of the crapto1 reference implementation by blapost. Implements
the 48-bit LFSR cipher used in MIFARE Classic cards, including the nonlinear
filter function, PRNG successor, key load/extract, forward and rollback
clocking, and encrypted mode support. All test vectors verified against
compiled C reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement MIFARE Classic three-pass mutual authentication handshake
using the Crypto1 cipher: initCipher, computeReaderResponse,
verifyCardResponse, encryptBytes, decryptBytes, and ISO 14443-3A
CRC-A computation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…teThru

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…stream)

Implement LFSR state recovery from 32-bit keystream, ported faithfully from
Proxmark3's crapto1 lfsr_recovery32(). The algorithm splits keystream into
odd/even bits, builds filter-consistent tables, extends them from 20 to 24
bits, then recursively extends with contribution tracking and bucket-sort
intersection to find matching state pairs.

Key implementation details:
- extendTableSimple: in-place table extension for initial 20->24 bit phase
- extendTable: new-array approach with contribution bit tracking
- recover: recursive extension with bucket-sort intersection (replaces
  mfcuk's buggy quicksort/binsearch merge)
- Input parameter transformation matching C: byte-swap and left-shift
- nonceDistance and recoverKeyFromNonces helper functions

Tests verify end-to-end key recovery using:
- mfkey32 attack pattern (ks2 with input=0, encrypted nR rollback)
- Nested attack pattern (ks0 with input=uid^nT)
- Simple and init-only recovery scenarios
- Nonce distance computation
- Filter constraint pruning (candidate count sanity check)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… recovery

Implements NestedAttack class that coordinates the three-phase key recovery
process: PRNG calibration, encrypted nonce collection via nested authentication,
and key recovery using LFSR state recovery. Tests cover the pure-logic components
(PRNG calibration, simulated key recovery) since the full attack requires PN533
hardware.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eader

Wire the MIFARE Classic nested attack into the card reading flow as a
fallback when all dictionary-based authentication methods fail. When
using a PN533 backend and at least one sector key is already known,
the reader now attempts key recovery via the Crypto1 nested attack
before giving up on a sector.

Changes:
- PN533ClassicTechnology: expose rawPn533, rawUid, and uidAsUInt
  properties so card/classic can construct PN533RawClassic directly
  (avoids circular dependency between card and card/classic modules)
- ClassicCardReader: track successful keys in recoveredKeys map, attempt
  nested attack after global dictionary keys fail, add keyBytesToLong
  and longToKeyBytes helper functions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…overy status

Thread an onProgress callback through ClassicCardReader.readCard so the
UI can report nested attack key recovery status. The desktop PN53x backend
prints progress messages to the console. The parameter defaults to null
so existing callers are unaffected.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments