Skip to content

Commit

Permalink
CLI: skip already used items in hf mf elog --decrypt
Browse files Browse the repository at this point in the history
This (often largely) improves the speed of the decrypt process. On my
laptop, with the same logs (37 records for one block and 37 records
for another block), here are the performances, as measuerd using a
simple command:

```bash
time echo -e "hw connect\nhf mf elog --decrypt\nhw disconnect" | ./chameleon_cli_main.py
```

- Before parallelisation (#187): 14m59,277s
- With parallelisation (current main): 6m13,513s
- With item skipping (this PR): 2m42,491s
  • Loading branch information
p-l- committed Dec 20, 2023
1 parent 0124709 commit e3cbd59
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...

## [unreleased][unreleased]
- Skip already used items `hf mf elog --decrypt` (@p-l-)
- Parallelize mfkey32v2 processes called from CLI (@p-l-)
- Added support for mifare classic value block operations (@taichunmin)
- Added regression tests (@doegox)
Expand Down
69 changes: 54 additions & 15 deletions software/script/chameleon_cli_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,10 +1044,57 @@ def _run_mfkey32v2(items):
).stdout
sea_obj = _KEY.search(output_str)
if sea_obj is not None:
return sea_obj[0]
return sea_obj[0], items
return None


class ItemGenerator:
def __init__(self, rs, i=0, j=1):
self.rs = rs
self.i = 0
self.j = 1
self.found = set()
self.keys = set()

def __iter__(self):
return self

def __next__(self):
try:
item_i = self.rs[self.i]
except IndexError:
raise StopIteration
if self.key_from_item(item_i) in self.found:
self.i += 1
self.j = self.i + 1
return next(self)
try:
item_j = self.rs[self.j]
except IndexError:
self.i += 1
self.j = self.i + 1
return next(self)
self.j += 1
if self.key_from_item(item_j) in self.found:
return next(self)
return item_i, item_j

@staticmethod
def key_from_item(item):
return "{uid}-{nt}-{nr}-{ar}".format(**item)

def key_found(self, key, items):
self.keys.add(key)
for item in items:
try:
if item == self.rs[self.i]:
self.i += 1
self.j = self.i + 1
except IndexError:
break
self.found.update(self.key_from_item(item) for item in items)


@hf_mf.command('elog')
class HFMFELog(DeviceRequiredUnit):
detection_log_size = 18
Expand All @@ -1069,24 +1116,16 @@ def decrypt_by_list(self, rs: list):
msg2 = f"/{(len(rs)*(len(rs)-1))//2} combinations. "
msg3 = " key(s) found"
n = 1
keys = set()
gen = ItemGenerator(rs)
with Pool(cpu_count()) as pool:
for key in pool.imap(
_run_mfkey32v2,
(
(item0, rs[j])
for i, item0 in enumerate(rs)
for j in range(i + 1, len(rs))
),
):
for result in pool.imap(_run_mfkey32v2, gen):
# TODO: if some keys already recovered, test them on item before running mfkey32 on item
# TODO: if some keys already recovered, remove corresponding items
if key is not None:
keys.add(key)
print(f"{msg1}{n}{msg2}{len(keys)}{msg3}\r", end="")
if result is not None:
gen.key_found(*result)
print(f"{msg1}{n}{msg2}{len(gen.keys)}{msg3}\r", end="")
n += 1
print()
return keys
return gen.keys

def on_exec(self, args: argparse.Namespace):
if not args.decrypt:
Expand Down

0 comments on commit e3cbd59

Please sign in to comment.