altifyhwid 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- altifyhwid-1.1.0/MANIFEST.in +10 -0
- altifyhwid-1.1.0/PKG-INFO +218 -0
- altifyhwid-1.1.0/PUBLISH.md +112 -0
- altifyhwid-1.1.0/README.md +204 -0
- altifyhwid-1.1.0/altifyhwid.egg-info/PKG-INFO +218 -0
- altifyhwid-1.1.0/altifyhwid.egg-info/SOURCES.txt +11 -0
- altifyhwid-1.1.0/altifyhwid.egg-info/dependency_links.txt +1 -0
- altifyhwid-1.1.0/altifyhwid.egg-info/entry_points.txt +2 -0
- altifyhwid-1.1.0/altifyhwid.egg-info/top_level.txt +1 -0
- altifyhwid-1.1.0/altifyhwid.py +714 -0
- altifyhwid-1.1.0/pyproject.toml +30 -0
- altifyhwid-1.1.0/setup.cfg +4 -0
- altifyhwid-1.1.0/tests/test_altifyhwid.py +601 -0
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: altifyhwid
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Windows hardware identity matching across three or more hardware categories
|
|
5
|
+
Author: Altify
|
|
6
|
+
Keywords: hardware,hwid,windows,device,identity
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Topic :: System :: Hardware
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Altify HWID
|
|
16
|
+
|
|
17
|
+
Recognize a Windows device using three or more matching hardware categories.
|
|
18
|
+
Python 3.10 or newer. No third-party runtime dependencies.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
After the maintainer publishes this project to PyPI:
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
python -m pip install altifyhwid
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The PyPI distribution name is `altifyhwid`; the Python import is `altifyhwid`.
|
|
29
|
+
|
|
30
|
+
## Use
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import altifyhwid as hwid
|
|
34
|
+
|
|
35
|
+
device_id = hwid.get_id()
|
|
36
|
+
print(device_id)
|
|
37
|
+
|
|
38
|
+
result = hwid.identify()
|
|
39
|
+
print(result.to_dict())
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Hardware collection is cached for 60 seconds per process. Database matching
|
|
43
|
+
still runs on each call. When both the ID and details are needed, call
|
|
44
|
+
`identify()` once and read `result.device_id`. Use `get_id(cache_ttl=0)` for a
|
|
45
|
+
fresh scan or `clear_cache()` to discard cached snapshots.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from altifyhwid import hwid
|
|
49
|
+
|
|
50
|
+
print(hwid())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Default database on Windows:
|
|
54
|
+
`%LOCALAPPDATA%\Altify\HWID\devices.sqlite3`.
|
|
55
|
+
Preserve this database to preserve device IDs. Override it with `db_path` or
|
|
56
|
+
the `ALTIFY_HWID_DB` environment variable. Explicit `db_path` takes priority.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import altifyhwid
|
|
60
|
+
|
|
61
|
+
# Reuse the registry created by the earlier standalone script:
|
|
62
|
+
device_id = altifyhwid.get_id(
|
|
63
|
+
db_path=r"C:\MyApp\settings\AltifyHWID\devices.sqlite3"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Match only, never write (no enrollment, no refresh, no last_seen update):
|
|
67
|
+
device_id = altifyhwid.get_id(read_only=True, create=False)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`identify()` accepts `create=False` (match only), `refresh=False` (do not
|
|
71
|
+
store newly seen hardware on a match), and `read_only=True` (open the
|
|
72
|
+
database read-only; implies both). `IdentityResult.stored_kinds` lists the
|
|
73
|
+
identifier kinds written to the registry by that call.
|
|
74
|
+
|
|
75
|
+
## Command line
|
|
76
|
+
|
|
77
|
+
```shell
|
|
78
|
+
python -m altifyhwid --json
|
|
79
|
+
python -m altifyhwid scan
|
|
80
|
+
python -m altifyhwid scan --raw
|
|
81
|
+
python -m altifyhwid --no-tpm --no-monitors
|
|
82
|
+
python -m altifyhwid list
|
|
83
|
+
python -m altifyhwid merge KEEP_ID ABSORB_ID
|
|
84
|
+
python -m altifyhwid delete DEVICE_ID
|
|
85
|
+
python -m altifyhwid -v
|
|
86
|
+
altifyhwid --json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`scan` reports categories, counts, and collection warnings without saving an
|
|
90
|
+
identity. `--raw` also prints hardware identifiers and diagnostic context.
|
|
91
|
+
Programmatic diagnostic context is available with
|
|
92
|
+
`collect(include_diagnostics=True)`. TPM is queried by default, but a missing
|
|
93
|
+
or inaccessible endorsement public key contributes no match evidence.
|
|
94
|
+
`--no-refresh`, `--no-create`, and `--read-only` mirror the API flags.
|
|
95
|
+
`-v` logs collector timings, PowerShell exit codes with a stderr tail, and
|
|
96
|
+
registry activity to stderr. Exit code 0 is success, 2 is a reported error
|
|
97
|
+
(JSON on stderr), 1 is an unexpected error. Console entry point
|
|
98
|
+
`altifyhwid` requires the Scripts directory of the environment on PATH; the
|
|
99
|
+
`python -m altifyhwid` form works without that extra PATH requirement.
|
|
100
|
+
|
|
101
|
+
## Matching
|
|
102
|
+
|
|
103
|
+
The categories are firmware, storage, RAM, network, display, TPM public key,
|
|
104
|
+
and CPU serial where available. Related SMBIOS fields together count as one
|
|
105
|
+
firmware vote. Multiple disks or RAM modules do not multiply category votes.
|
|
106
|
+
CPU/GPU models and ordinary CPU ProcessorId values do not count as unique IDs.
|
|
107
|
+
|
|
108
|
+
A new enrollment gets a random UUID. Later scans reuse it when at least three
|
|
109
|
+
categories match that enrollment. Use `min_matches=4` or higher to require
|
|
110
|
+
more evidence.
|
|
111
|
+
|
|
112
|
+
On a match the registry also stores identifiers it has not seen before for
|
|
113
|
+
that device (a replaced disk, new RAM, a new network adapter), so gradual
|
|
114
|
+
hardware changes keep the same ID as long as each scan still matches three
|
|
115
|
+
categories. Old identifiers are kept, so a reverted swap still matches. Each
|
|
116
|
+
device keeps at most 128 identifiers per kind; beyond that the oldest ones not
|
|
117
|
+
present in the current scan are dropped. Pass `refresh=False` to disable this.
|
|
118
|
+
|
|
119
|
+
Insufficient usable categories raise `InsufficientHardwareError`. `create=False`
|
|
120
|
+
requires an existing match and otherwise raises `NoMatchError`.
|
|
121
|
+
|
|
122
|
+
Multiple qualifying records raise `AmbiguousMatchError`; `.candidates` lists
|
|
123
|
+
the two strongest, best first, each with `match_count`. Resolve it once with
|
|
124
|
+
the registry tools below (usually `merge` the weaker into the stronger).
|
|
125
|
+
|
|
126
|
+
## Registry maintenance
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from altifyhwid import Registry
|
|
130
|
+
|
|
131
|
+
registry = Registry() # same defaults as identify()
|
|
132
|
+
registry.list_devices() # dicts with device_id, created_at,
|
|
133
|
+
# last_seen, token_count, groups, kinds
|
|
134
|
+
registry.list_devices(all_namespaces=True)
|
|
135
|
+
registry.merge("ALTIFY-KEEP...", "ALTIFY-DUPLICATE...")
|
|
136
|
+
registry.delete("ALTIFY-OLD...")
|
|
137
|
+
registry.backup(r"D:\backups\devices.sqlite3")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`merge` moves the identifiers of the absorbed enrollments onto the kept one and
|
|
141
|
+
removes the absorbed records. Both must be in the namespace of the registry.
|
|
142
|
+
`delete` and `merge` refuse a `read_only=True` registry.
|
|
143
|
+
|
|
144
|
+
## Integrity, backups, and schema
|
|
145
|
+
|
|
146
|
+
Each registry path is checked with `PRAGMA quick_check` once per process. A
|
|
147
|
+
corrupt or non-SQLite file raises `RegistryError` naming the newest backup.
|
|
148
|
+
|
|
149
|
+
After any change to enrollments (a new device, stored identifiers, merge,
|
|
150
|
+
delete) the registry writes `devices.sqlite3.bak1` next to the database, at
|
|
151
|
+
most once per `backup_interval` seconds (default one day), and rotates older
|
|
152
|
+
copies to `.bak2` and so on up to `backup_copies` (default 2; 0 disables).
|
|
153
|
+
Backup failures are logged and never fail the identity call. To restore, stop
|
|
154
|
+
the application and copy `.bak1` over `devices.sqlite3`.
|
|
155
|
+
|
|
156
|
+
The database schema is version 2. A schema 1 registry (created by 1.0.x) is
|
|
157
|
+
migrated automatically on the first writable open; a `read_only=True` open
|
|
158
|
+
refuses it until then. A registry written by a newer module version is refused
|
|
159
|
+
rather than modified.
|
|
160
|
+
|
|
161
|
+
Only HMAC tokens are persisted, but the key is in the same database: this is
|
|
162
|
+
pseudonymization, not encryption. No data is uploaded by this module. Collection
|
|
163
|
+
uses read-only Windows queries, bounded worker threads, and timeouts. TPM and
|
|
164
|
+
some provider queries can be unavailable under ordinary user permissions.
|
|
165
|
+
|
|
166
|
+
## Logging
|
|
167
|
+
|
|
168
|
+
The module logs to `logging.getLogger("altifyhwid")` and stays silent unless
|
|
169
|
+
the application configures logging. `WARNING` covers failed collector batches
|
|
170
|
+
(exit code, duration, last 2 KiB of PowerShell stderr), ambiguous matches,
|
|
171
|
+
rollback problems, and failed automatic backups. `INFO` covers enrollments,
|
|
172
|
+
migrations, backups, and scan summaries. `DEBUG` covers per-batch timings and
|
|
173
|
+
match details. Hardware identifiers are never logged.
|
|
174
|
+
|
|
175
|
+
## Scope
|
|
176
|
+
|
|
177
|
+
Hardware collection requires Windows. Registry matching can run elsewhere with
|
|
178
|
+
an explicitly supplied `HardwareSnapshot`. Separate registry databases produce
|
|
179
|
+
separate IDs; installing this package does not create a shared identity service.
|
|
180
|
+
For multiple clients, use a trusted central registry through your own
|
|
181
|
+
authenticated transport. Client-supplied hardware values are not attestation.
|
|
182
|
+
|
|
183
|
+
This is heuristic recognition, not a guarantee of uniqueness or resistance to
|
|
184
|
+
spoofing. Shared components, OEM duplicates, cloned VMs, and extensive hardware
|
|
185
|
+
changes can produce incorrect matches or new IDs. Storing newly seen hardware
|
|
186
|
+
on a match also means a wrong match teaches the wrong record; raise
|
|
187
|
+
`min_matches` or set `refresh=False` where that matters. Proprietary kernel
|
|
188
|
+
anti-cheat internals are not implemented.
|
|
189
|
+
|
|
190
|
+
## Resource limits
|
|
191
|
+
|
|
192
|
+
Collection uses at most four concurrent workers and an overall deadline. One
|
|
193
|
+
scan runs at a time per process; a caller waits up to `timeout` seconds for an
|
|
194
|
+
active scan, then gets the cached result when the same options were scanned,
|
|
195
|
+
or runs its own scan with a fresh deadline. Cache reads and `clear_cache()`
|
|
196
|
+
never wait behind a scan. The scan cache holds at most eight immutable
|
|
197
|
+
snapshots. No workers run between calls. Provider results are limited to 128
|
|
198
|
+
rows and Python reads at most 2 MiB of output per collector batch. SQLite
|
|
199
|
+
connections and temporary output files close when each operation completes.
|
|
200
|
+
|
|
201
|
+
The registry uses SQLite WAL mode, so readers never block the single writer.
|
|
202
|
+
Opening an existing registry takes no write lock; only enrollment, refresh,
|
|
203
|
+
merge, delete, and migration do. Backup has a deadline and removes incomplete
|
|
204
|
+
output on failure. Matching uses an index and retrieves at most two qualifying
|
|
205
|
+
records to detect ambiguity.
|
|
206
|
+
|
|
207
|
+
## Tests
|
|
208
|
+
|
|
209
|
+
```shell
|
|
210
|
+
py -m unittest discover -s tests -v
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
The suite covers normalization, snapshot building from recorded provider
|
|
214
|
+
output, enrollment and matching, refresh and its cap, ambiguity and merge,
|
|
215
|
+
read-only mode, schema migration, integrity failures, backups and rotation,
|
|
216
|
+
collector caching and locking, PowerShell failure handling, and the command
|
|
217
|
+
line. It runs without hardware access. `ALTIFY_LIVE=1` adds one live
|
|
218
|
+
collection on the current Windows PC.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Publish Altify HWID to PyPI from Windows
|
|
2
|
+
|
|
3
|
+
This folder is the source project. It contains `pyproject.toml`, `altifyhwid.py`,
|
|
4
|
+
and the README. Nothing has been published automatically.
|
|
5
|
+
|
|
6
|
+
## 1. Prepare your account
|
|
7
|
+
|
|
8
|
+
Register at https://pypi.org/account/register/, verify your email, and enable
|
|
9
|
+
two-factor authentication in account settings.
|
|
10
|
+
|
|
11
|
+
Create an API token at https://pypi.org/manage/account/token/ with the scope
|
|
12
|
+
**Entire account** for your first upload, because the project does not exist
|
|
13
|
+
under your account yet. Save it when shown. Once the project exists, you can
|
|
14
|
+
replace that token with one scoped to this project.
|
|
15
|
+
|
|
16
|
+
Use the token only at the local upload prompt. It does not belong in this
|
|
17
|
+
source tree, `pyproject.toml`, your README, or a chat message.
|
|
18
|
+
|
|
19
|
+
The proposed project name is `altifyhwid`. Availability has not been confirmed;
|
|
20
|
+
PyPI makes the final determination at upload. If unavailable, edit only
|
|
21
|
+
`name = "altifyhwid"` in `pyproject.toml`, choose a different name, and rebuild.
|
|
22
|
+
The Python module remains `altifyhwid`. Update README installation examples to
|
|
23
|
+
use the new distribution name. Package names are case-insensitive and `.`, `_`,
|
|
24
|
+
and `-` are normalized; changing only those characters will not avoid a clash.
|
|
25
|
+
|
|
26
|
+
## 2. Open a terminal in this folder
|
|
27
|
+
|
|
28
|
+
Extract the download, enter the `altifyhwid` folder in File Explorer, type
|
|
29
|
+
`powershell` into the address bar, then press Enter.
|
|
30
|
+
|
|
31
|
+
The following must list your project file:
|
|
32
|
+
|
|
33
|
+
```powershell
|
|
34
|
+
Get-Item .\pyproject.toml
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 3. Test, build and check
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
py -m unittest discover -s tests -v
|
|
41
|
+
py -m pip install --upgrade pip build twine
|
|
42
|
+
py -m build
|
|
43
|
+
py -m twine check dist/*
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The unit tests need no hardware access. Set `ALTIFY_LIVE=1` to also run one
|
|
47
|
+
live PowerShell collection on this PC.
|
|
48
|
+
|
|
49
|
+
Build creates a wheel (`.whl`) and a source archive (`.tar.gz`) in `dist`.
|
|
50
|
+
Only these distribution files are uploaded. The outer ZIP is not uploaded.
|
|
51
|
+
|
|
52
|
+
Test the wheel on your Windows PC before publishing:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
py -m venv .verify-env
|
|
56
|
+
.\.verify-env\Scripts\python.exe -m pip install --no-deps (Get-ChildItem .\dist\*.whl).FullName
|
|
57
|
+
.\.verify-env\Scripts\python.exe -I -m altifyhwid scan
|
|
58
|
+
.\.verify-env\Scripts\python.exe -I -m altifyhwid --json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Isolated mode (`-I`) ensures these checks run the installed module instead of
|
|
62
|
+
the source file in the current folder.
|
|
63
|
+
|
|
64
|
+
## 4. Upload to real PyPI
|
|
65
|
+
|
|
66
|
+
```powershell
|
|
67
|
+
py -m twine upload --username __token__ dist/*
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
At the token/password prompt, paste the full token including its `pypi-`
|
|
71
|
+
prefix. Input stays invisible. Press Enter. This publishes the package to
|
|
72
|
+
https://pypi.org/ and creates the project if the name is available.
|
|
73
|
+
|
|
74
|
+
Do not use `--repository testpypi` for the real upload. TestPyPI is optional,
|
|
75
|
+
uses a separate account/token, and does not make ordinary `pip install` find
|
|
76
|
+
your package.
|
|
77
|
+
|
|
78
|
+
## 5. Install and use
|
|
79
|
+
|
|
80
|
+
After upload succeeds, you and others can run:
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
py -m pip install --upgrade altifyhwid
|
|
84
|
+
py -m altifyhwid --json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
import altifyhwid as hwid
|
|
89
|
+
print(hwid.get_id())
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Use `python -m pip` instead of `py -m pip` when selecting an already activated
|
|
93
|
+
virtual environment. Python used for installation and running your app must
|
|
94
|
+
be the same environment.
|
|
95
|
+
|
|
96
|
+
## Updates
|
|
97
|
+
|
|
98
|
+
Change `__version__` in `altifyhwid.py`, for example from `1.0.2` to `1.0.3`.
|
|
99
|
+
The build reads this version automatically. Move the old `dist` folder out of
|
|
100
|
+
this project so the upload glob contains only the new release, then repeat
|
|
101
|
+
the build, check, and upload commands. PyPI does not allow replacing an
|
|
102
|
+
already-uploaded distribution filename with different contents, even after
|
|
103
|
+
deletion. A new release needs a new version number.
|
|
104
|
+
|
|
105
|
+
No license has been chosen for you. If you want an open-source license, add
|
|
106
|
+
your chosen LICENSE and matching project metadata before the public upload.
|
|
107
|
+
|
|
108
|
+
Official references:
|
|
109
|
+
- https://packaging.python.org/en/latest/tutorials/packaging-projects/
|
|
110
|
+
- https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/
|
|
111
|
+
- https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
|
|
112
|
+
- https://pypi.org/help/
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Altify HWID
|
|
2
|
+
|
|
3
|
+
Recognize a Windows device using three or more matching hardware categories.
|
|
4
|
+
Python 3.10 or newer. No third-party runtime dependencies.
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
After the maintainer publishes this project to PyPI:
|
|
9
|
+
|
|
10
|
+
```shell
|
|
11
|
+
python -m pip install altifyhwid
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The PyPI distribution name is `altifyhwid`; the Python import is `altifyhwid`.
|
|
15
|
+
|
|
16
|
+
## Use
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
import altifyhwid as hwid
|
|
20
|
+
|
|
21
|
+
device_id = hwid.get_id()
|
|
22
|
+
print(device_id)
|
|
23
|
+
|
|
24
|
+
result = hwid.identify()
|
|
25
|
+
print(result.to_dict())
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Hardware collection is cached for 60 seconds per process. Database matching
|
|
29
|
+
still runs on each call. When both the ID and details are needed, call
|
|
30
|
+
`identify()` once and read `result.device_id`. Use `get_id(cache_ttl=0)` for a
|
|
31
|
+
fresh scan or `clear_cache()` to discard cached snapshots.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from altifyhwid import hwid
|
|
35
|
+
|
|
36
|
+
print(hwid())
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Default database on Windows:
|
|
40
|
+
`%LOCALAPPDATA%\Altify\HWID\devices.sqlite3`.
|
|
41
|
+
Preserve this database to preserve device IDs. Override it with `db_path` or
|
|
42
|
+
the `ALTIFY_HWID_DB` environment variable. Explicit `db_path` takes priority.
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
import altifyhwid
|
|
46
|
+
|
|
47
|
+
# Reuse the registry created by the earlier standalone script:
|
|
48
|
+
device_id = altifyhwid.get_id(
|
|
49
|
+
db_path=r"C:\MyApp\settings\AltifyHWID\devices.sqlite3"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Match only, never write (no enrollment, no refresh, no last_seen update):
|
|
53
|
+
device_id = altifyhwid.get_id(read_only=True, create=False)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`identify()` accepts `create=False` (match only), `refresh=False` (do not
|
|
57
|
+
store newly seen hardware on a match), and `read_only=True` (open the
|
|
58
|
+
database read-only; implies both). `IdentityResult.stored_kinds` lists the
|
|
59
|
+
identifier kinds written to the registry by that call.
|
|
60
|
+
|
|
61
|
+
## Command line
|
|
62
|
+
|
|
63
|
+
```shell
|
|
64
|
+
python -m altifyhwid --json
|
|
65
|
+
python -m altifyhwid scan
|
|
66
|
+
python -m altifyhwid scan --raw
|
|
67
|
+
python -m altifyhwid --no-tpm --no-monitors
|
|
68
|
+
python -m altifyhwid list
|
|
69
|
+
python -m altifyhwid merge KEEP_ID ABSORB_ID
|
|
70
|
+
python -m altifyhwid delete DEVICE_ID
|
|
71
|
+
python -m altifyhwid -v
|
|
72
|
+
altifyhwid --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`scan` reports categories, counts, and collection warnings without saving an
|
|
76
|
+
identity. `--raw` also prints hardware identifiers and diagnostic context.
|
|
77
|
+
Programmatic diagnostic context is available with
|
|
78
|
+
`collect(include_diagnostics=True)`. TPM is queried by default, but a missing
|
|
79
|
+
or inaccessible endorsement public key contributes no match evidence.
|
|
80
|
+
`--no-refresh`, `--no-create`, and `--read-only` mirror the API flags.
|
|
81
|
+
`-v` logs collector timings, PowerShell exit codes with a stderr tail, and
|
|
82
|
+
registry activity to stderr. Exit code 0 is success, 2 is a reported error
|
|
83
|
+
(JSON on stderr), 1 is an unexpected error. Console entry point
|
|
84
|
+
`altifyhwid` requires the Scripts directory of the environment on PATH; the
|
|
85
|
+
`python -m altifyhwid` form works without that extra PATH requirement.
|
|
86
|
+
|
|
87
|
+
## Matching
|
|
88
|
+
|
|
89
|
+
The categories are firmware, storage, RAM, network, display, TPM public key,
|
|
90
|
+
and CPU serial where available. Related SMBIOS fields together count as one
|
|
91
|
+
firmware vote. Multiple disks or RAM modules do not multiply category votes.
|
|
92
|
+
CPU/GPU models and ordinary CPU ProcessorId values do not count as unique IDs.
|
|
93
|
+
|
|
94
|
+
A new enrollment gets a random UUID. Later scans reuse it when at least three
|
|
95
|
+
categories match that enrollment. Use `min_matches=4` or higher to require
|
|
96
|
+
more evidence.
|
|
97
|
+
|
|
98
|
+
On a match the registry also stores identifiers it has not seen before for
|
|
99
|
+
that device (a replaced disk, new RAM, a new network adapter), so gradual
|
|
100
|
+
hardware changes keep the same ID as long as each scan still matches three
|
|
101
|
+
categories. Old identifiers are kept, so a reverted swap still matches. Each
|
|
102
|
+
device keeps at most 128 identifiers per kind; beyond that the oldest ones not
|
|
103
|
+
present in the current scan are dropped. Pass `refresh=False` to disable this.
|
|
104
|
+
|
|
105
|
+
Insufficient usable categories raise `InsufficientHardwareError`. `create=False`
|
|
106
|
+
requires an existing match and otherwise raises `NoMatchError`.
|
|
107
|
+
|
|
108
|
+
Multiple qualifying records raise `AmbiguousMatchError`; `.candidates` lists
|
|
109
|
+
the two strongest, best first, each with `match_count`. Resolve it once with
|
|
110
|
+
the registry tools below (usually `merge` the weaker into the stronger).
|
|
111
|
+
|
|
112
|
+
## Registry maintenance
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from altifyhwid import Registry
|
|
116
|
+
|
|
117
|
+
registry = Registry() # same defaults as identify()
|
|
118
|
+
registry.list_devices() # dicts with device_id, created_at,
|
|
119
|
+
# last_seen, token_count, groups, kinds
|
|
120
|
+
registry.list_devices(all_namespaces=True)
|
|
121
|
+
registry.merge("ALTIFY-KEEP...", "ALTIFY-DUPLICATE...")
|
|
122
|
+
registry.delete("ALTIFY-OLD...")
|
|
123
|
+
registry.backup(r"D:\backups\devices.sqlite3")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`merge` moves the identifiers of the absorbed enrollments onto the kept one and
|
|
127
|
+
removes the absorbed records. Both must be in the namespace of the registry.
|
|
128
|
+
`delete` and `merge` refuse a `read_only=True` registry.
|
|
129
|
+
|
|
130
|
+
## Integrity, backups, and schema
|
|
131
|
+
|
|
132
|
+
Each registry path is checked with `PRAGMA quick_check` once per process. A
|
|
133
|
+
corrupt or non-SQLite file raises `RegistryError` naming the newest backup.
|
|
134
|
+
|
|
135
|
+
After any change to enrollments (a new device, stored identifiers, merge,
|
|
136
|
+
delete) the registry writes `devices.sqlite3.bak1` next to the database, at
|
|
137
|
+
most once per `backup_interval` seconds (default one day), and rotates older
|
|
138
|
+
copies to `.bak2` and so on up to `backup_copies` (default 2; 0 disables).
|
|
139
|
+
Backup failures are logged and never fail the identity call. To restore, stop
|
|
140
|
+
the application and copy `.bak1` over `devices.sqlite3`.
|
|
141
|
+
|
|
142
|
+
The database schema is version 2. A schema 1 registry (created by 1.0.x) is
|
|
143
|
+
migrated automatically on the first writable open; a `read_only=True` open
|
|
144
|
+
refuses it until then. A registry written by a newer module version is refused
|
|
145
|
+
rather than modified.
|
|
146
|
+
|
|
147
|
+
Only HMAC tokens are persisted, but the key is in the same database: this is
|
|
148
|
+
pseudonymization, not encryption. No data is uploaded by this module. Collection
|
|
149
|
+
uses read-only Windows queries, bounded worker threads, and timeouts. TPM and
|
|
150
|
+
some provider queries can be unavailable under ordinary user permissions.
|
|
151
|
+
|
|
152
|
+
## Logging
|
|
153
|
+
|
|
154
|
+
The module logs to `logging.getLogger("altifyhwid")` and stays silent unless
|
|
155
|
+
the application configures logging. `WARNING` covers failed collector batches
|
|
156
|
+
(exit code, duration, last 2 KiB of PowerShell stderr), ambiguous matches,
|
|
157
|
+
rollback problems, and failed automatic backups. `INFO` covers enrollments,
|
|
158
|
+
migrations, backups, and scan summaries. `DEBUG` covers per-batch timings and
|
|
159
|
+
match details. Hardware identifiers are never logged.
|
|
160
|
+
|
|
161
|
+
## Scope
|
|
162
|
+
|
|
163
|
+
Hardware collection requires Windows. Registry matching can run elsewhere with
|
|
164
|
+
an explicitly supplied `HardwareSnapshot`. Separate registry databases produce
|
|
165
|
+
separate IDs; installing this package does not create a shared identity service.
|
|
166
|
+
For multiple clients, use a trusted central registry through your own
|
|
167
|
+
authenticated transport. Client-supplied hardware values are not attestation.
|
|
168
|
+
|
|
169
|
+
This is heuristic recognition, not a guarantee of uniqueness or resistance to
|
|
170
|
+
spoofing. Shared components, OEM duplicates, cloned VMs, and extensive hardware
|
|
171
|
+
changes can produce incorrect matches or new IDs. Storing newly seen hardware
|
|
172
|
+
on a match also means a wrong match teaches the wrong record; raise
|
|
173
|
+
`min_matches` or set `refresh=False` where that matters. Proprietary kernel
|
|
174
|
+
anti-cheat internals are not implemented.
|
|
175
|
+
|
|
176
|
+
## Resource limits
|
|
177
|
+
|
|
178
|
+
Collection uses at most four concurrent workers and an overall deadline. One
|
|
179
|
+
scan runs at a time per process; a caller waits up to `timeout` seconds for an
|
|
180
|
+
active scan, then gets the cached result when the same options were scanned,
|
|
181
|
+
or runs its own scan with a fresh deadline. Cache reads and `clear_cache()`
|
|
182
|
+
never wait behind a scan. The scan cache holds at most eight immutable
|
|
183
|
+
snapshots. No workers run between calls. Provider results are limited to 128
|
|
184
|
+
rows and Python reads at most 2 MiB of output per collector batch. SQLite
|
|
185
|
+
connections and temporary output files close when each operation completes.
|
|
186
|
+
|
|
187
|
+
The registry uses SQLite WAL mode, so readers never block the single writer.
|
|
188
|
+
Opening an existing registry takes no write lock; only enrollment, refresh,
|
|
189
|
+
merge, delete, and migration do. Backup has a deadline and removes incomplete
|
|
190
|
+
output on failure. Matching uses an index and retrieves at most two qualifying
|
|
191
|
+
records to detect ambiguity.
|
|
192
|
+
|
|
193
|
+
## Tests
|
|
194
|
+
|
|
195
|
+
```shell
|
|
196
|
+
py -m unittest discover -s tests -v
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The suite covers normalization, snapshot building from recorded provider
|
|
200
|
+
output, enrollment and matching, refresh and its cap, ambiguity and merge,
|
|
201
|
+
read-only mode, schema migration, integrity failures, backups and rotation,
|
|
202
|
+
collector caching and locking, PowerShell failure handling, and the command
|
|
203
|
+
line. It runs without hardware access. `ALTIFY_LIVE=1` adds one live
|
|
204
|
+
collection on the current Windows PC.
|