mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
feat(hashview): report cracked-hash upload counts (2.11.2)
The upload previously printed only "✓ Success: OK", so there was no way to tell how many hashes were accepted. upload_cracked_hashes now surfaces client-side uploaded/skipped counts in its return value, and the CLI prints them plus the server's verified/updated/unmatched breakdown when a newer Hashview reports them (see hashview #355/#356). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a14272a058
commit
a15860a63e
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
Dates are omitted for releases predating this file; see the git tags for exact timing.
|
||||
|
||||
## [2.11.2] - 2026-07-23
|
||||
|
||||
### Added
|
||||
|
||||
- **Hashview cracked-hash upload now reports how many hashes landed.** After an upload the
|
||||
CLI prints the number of pairs the client sent and how many it skipped by validation —
|
||||
available regardless of the server version — and, against a Hashview that reports import
|
||||
counts, also shows how many were newly cracked, verified, and left unmatched (already
|
||||
cracked or not present). `upload_cracked_hashes` surfaces `uploaded`/`skipped` in its
|
||||
return value alongside any server-provided counts. Previously the upload only printed
|
||||
`✓ Success: OK` with no indication of how many hashes were accepted.
|
||||
|
||||
## [2.11.1] - 2026-07-23
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1770,6 +1770,11 @@ class HashviewAPI:
|
||||
raise Exception(
|
||||
f"Hashview API Error: {json_response.get('msg', 'Unknown error')}"
|
||||
)
|
||||
# Surface what the client actually sent so the caller can report a
|
||||
# count even against a Hashview that returns a bare {"msg": "OK"}.
|
||||
if isinstance(json_response, dict):
|
||||
json_response.setdefault("uploaded", len(valid_lines))
|
||||
json_response.setdefault("skipped", len(skipped))
|
||||
return json_response
|
||||
except (json.JSONDecodeError, ValueError):
|
||||
raise Exception(f"Invalid API response: {resp.text[:200]}")
|
||||
|
||||
+24
-2
@@ -3445,8 +3445,30 @@ def hashview_api():
|
||||
print(
|
||||
f"\n✓ Success: {result.get('msg', 'Cracked hashes uploaded')}"
|
||||
)
|
||||
if "count" in result:
|
||||
print(f" Imported: {result['count']} hashes")
|
||||
if not isinstance(result, dict):
|
||||
result = {}
|
||||
# What this client sent (available regardless of server version).
|
||||
if "uploaded" in result:
|
||||
line = f" Uploaded: {result['uploaded']} pair(s)"
|
||||
if result.get("skipped"):
|
||||
line += f" ({result['skipped']} skipped by validation)"
|
||||
print(line)
|
||||
# What the server actually did (newer Hashview reports these).
|
||||
if "verified" in result or "updated" in result or "count" in result:
|
||||
updated = result.get("updated", result.get("count"))
|
||||
print(f" Newly cracked in Hashview: {updated}")
|
||||
if "verified" in result:
|
||||
print(f" Verified: {result['verified']}")
|
||||
if result.get("unmatched"):
|
||||
print(
|
||||
" Unmatched (already cracked or not in Hashview): "
|
||||
f"{result['unmatched']}"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
" (This Hashview did not report an import count; "
|
||||
"upgrade Hashview to see how many landed.)"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"\n✗ Error: {str(e)}")
|
||||
import traceback
|
||||
|
||||
@@ -379,6 +379,40 @@ class TestHashviewAPI:
|
||||
out = capsys.readouterr().out
|
||||
assert "Skipped 1 line" in out
|
||||
|
||||
def test_upload_surfaces_client_counts(self, api, tmp_path):
|
||||
"""upload_cracked_hashes reports uploaded/skipped even when the server
|
||||
returns a bare OK with no counts of its own."""
|
||||
cracked_file = tmp_path / "cracked.txt"
|
||||
cracked_file.write_text(
|
||||
"5f4dcc3b5aa765d61d8327deb882cf99:password\n" # MD5 -> skipped
|
||||
"8846f7eaee8fb117ad06bdd830b7586c:password\n" # NTLM -> uploaded
|
||||
)
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {"status": 200, "type": "message", "msg": "OK"}
|
||||
mock_response.raise_for_status = Mock()
|
||||
api.session.post.return_value = mock_response
|
||||
|
||||
result = api.upload_cracked_hashes(str(cracked_file), hash_type="1000")
|
||||
assert result["uploaded"] == 1
|
||||
assert result["skipped"] == 1
|
||||
|
||||
def test_upload_preserves_server_counts(self, api, tmp_path):
|
||||
"""A Hashview that reports counts keeps them; client counts are added
|
||||
without clobbering the server's."""
|
||||
cracked_file = tmp_path / "cracked.txt"
|
||||
cracked_file.write_text("8846f7eaee8fb117ad06bdd830b7586c:password\n")
|
||||
mock_response = Mock()
|
||||
mock_response.json.return_value = {
|
||||
"msg": "OK", "count": 1, "verified": 1, "updated": 1, "unmatched": 0,
|
||||
}
|
||||
mock_response.raise_for_status = Mock()
|
||||
api.session.post.return_value = mock_response
|
||||
|
||||
result = api.upload_cracked_hashes(str(cracked_file), hash_type="1000")
|
||||
assert result["updated"] == 1
|
||||
assert result["verified"] == 1
|
||||
assert result["uploaded"] == 1 # client count added alongside
|
||||
|
||||
def _sent_body(self, api):
|
||||
sent = api.session.post.call_args.kwargs.get("data")
|
||||
if sent is None:
|
||||
|
||||
Reference in New Issue
Block a user