Українська версія / Ukrainian version
Scripts for consistent masking of sensitive data in military documents with the ability to accurately restore the original.
Current version — see CHANGELOG.md or python data_masking.py -V.
data_masking.py— data masking (thin wrapper over themasking/package)unmask_data.py— unmasking (thin wrapper over theunmasking/package)diagnose_mapping.py— diagnostics, mapping comparison and recovery verification__main__.py— run from the repository root:python . mask [args]/python . unmask [args]
| Module | Description |
|---|---|
constants.py |
MASK_* flags, patterns, name dictionaries, metadata (single source of version) |
helpers.py |
Seed, mapping, instance tracking, normalization |
language.py |
Gender, grammatical case, name declension |
context.py |
Recognition of lines containing PIB (Прізвище Ім'я По-батькові), context parsing |
mask_personal.py |
IPN, passports, surnames, first names, patronymics |
mask_military.py |
Ranks, units, orders, BR, dates |
engine.py |
Main engine: context-aware masking of text/JSON, initials |
cli.py |
CLI, config, reports, main() |
| Module | Description |
|---|---|
helpers.py |
Instance map, file pair lookup, mapping versions |
engine.py |
Text/JSON recovery, re-mask chains |
io.py |
Loading mapping (.json/.enc), schema validation |
cli.py |
CLI, main() |
rank_data.py— UAF ranks (army, navy, medical/legal service) with declensions;modules/rank_data.py— re-export
| Module | Description |
|---|---|
config.py |
YAML + ENV + CLI configuration with priorities (CLI > ENV > YAML > Default) |
security.py |
AES-256-GCM encryption/decryption of mapping files |
masking_logger.py |
Structured logging (JSON + colored console output) |
selective.py |
--only / --exclude filters for selective masking |
re_mask.py |
Chain re-masking (multi-pass) with chain tracking |
tools.py |
Atomic masking functions for programmatic use (API) |
password_generator.py |
Password generator (ASCII, Cyrillic, custom symbols) |
config_example.py— configuration example (dataclasses, no external dependencies)
python data_masking.py input.txtResult:
output/masked_input_YYYYMMDD_HHMMSS.txt— masked textoutput/mapping_input_YYYYMMDD_HHMMSS.json— mapping for unmaskoutput/report_input_YYYYMMDD_HHMMSS.txt— report
Automatic mode:
python unmask_data.py masked_file.txtManual mode:
python unmask_data.py masked_file.txt -m mapping_file.jsonWith encrypted mapping file (v2.3.0+):
python unmask_data.py masked_file.txt --map mapping.enc --password mypasswordWith configuration file (v2.3.0+):
python unmask_data.py -c config.yamlResult:
result/unmasked_file_YYYYMMDD_HHMMSS.txt— recovered text
python diagnose_mapping.py mapping1.json mapping2.json # compare mappings
python diagnose_mapping.py --verify input.txt recovered.txt # verify recovery- PIB (Прізвище Ім'я По-батькові) — with grammatical case and gender awareness
- PIB with initials (v2.5.0+) —
Іванов П.А.,П. Агранов,К.П. Іванов,Т. А. Сидоренко,КОВАЛЕНКО І.В.; initials are preserved in the mapping and restored during unmask - Patronymics — masked while preserving gender and letter case
- IPN — 10 digits
- ID passports — 9 digits (first 3 + last are fixed, middle 5 are random)
- Passports — АА123456, АА №123456
- Military IDs — МТ123456, мт-123456, мт №123456
- Ranks — all grammatical cases, masculine/feminine forms, with "retired"/"reserve" modifiers
- Brigades — "123 окрема механізована бригада"
- Military units — "в/ч А1234"
- Order numbers — "наказ №123 від 01.01.2025"
- BR numbers — 75/25/3400/Р, 818/856, 86319/06/1689/Р, 566
- Dates — DD.MM.YYYY (±30 days)
- Abbreviations — ЗСУ, МОУ, ВСУ, ДПСУ, НГУ, ДСНС, СБУ, ГУР, ТЦК, СП
Each occurrence is tracked individually:
Вхід: "Іванов зустрів Петрова, потім Іванов пішов"
Маска: "Сидоров зустрів Коваля, потім Сидоров пішов"
↑ instance 1 ↑ instance 2
Unmask правильно відновить обидва входження
Rank declension:
"молодшому сержанту" → "старшому солдату" (давальний)
"молодшого сержанта" → "старшого солдата" (родовий)
"молодшим сержантом" → "старшим солдатом" (орудний)
Name declension:
"Іванову Петру" → "Сидорову Андрію" (давальний)
"Іванова Петра" → "Сидорова Андрія" (родовий)
"Івановим Петром" → "Сидоровим Андрієм" (орудний)
Gender:
"молодшою сержанткою" → "старшою солдаткою" (жіночий)
"капітанці" → "майорці" (жіночий)
Additional modifiers:
"Солдату у відставці" → "Рекруту у відставці" (давальний зберігається!)
"Сержанту в запасі" → "Старшому солдату в запасі" (давальний зберігається!)
"Капітану на пенсії" → "Майору на пенсії" (давальний зберігається!)
"ІВАНОВ" → "ПЕТРЕНКО"
"Іванов" → "Петренко"
"іванов" → "петренко"
The system generates identical mapping files when processing identical input data:
# Запуск 1:
"Іванов" → blake2b hash → seed → "Петренко"
# Запуск 2 (той самий файл):
"Іванов" → blake2b hash → seed → "Петренко" # Той самий результат!Advantages:
- One original = one mask (always)
- Results are predictable and reproducible
- You can re-run on the same file and get the same mapping
- Unmask works consistently
Technical details:
- Uses
hashlib.blake2b()for seed generation - Faker and random are initialized with a deterministic seed
Mapping files can be encrypted with AES-256-GCM:
# Маскування з шифруванням
python data_masking.py input.txt --encrypt --password mypassword
# Результат: mapping_*.json.enc
# Розмаскування з шифрованим mapping
python unmask_data.py masked.txt --map mapping.enc --password mypasswordPriority: CLI > ENV > config.yaml > Default
# Використання YAML конфігурації
python data_masking.py input.txt -c config.yaml
# Генерація прикладу конфігурації — див. config_example.py# Маскувати тільки ІПН та паспорти
python data_masking.py input.txt --only ipn,passport
# Маскувати все крім дат
python data_masking.py input.txt --exclude datesFor re-masking already masked files while preserving the full mapping chain:
from modules.re_mask import ReMasker, MappingChain
chain = MappingChain()
remasker = ReMasker(chain=chain, mask_function=my_mask_fn)Atomic functions for integration into external applications:
from modules.tools import mask_ipn_direct, mask_rank_direct, mask_pib_force
result = mask_ipn_direct("1234567890", masking_dict, instance_counters)from modules.password_generator import generate_password
# Стандартний пароль (24 символи)
pwd = generate_password()
# З кирилицею
pwd = generate_password(length=32, include_cyrillic_lower=True)
# CLI
# python -m modules.password_generator --length 32 --cyrillicStructured logging with JSON and colored console output:
from modules.masking_logger import MaskingLogger, setup_logging
logger = MaskingLogger("masking")
setup_logging(level="DEBUG", json_output=True)In data_masking.py you can enable/disable categories:
# Алгоритм хешування для детермінованої генерації
HASH_ALGORITHM = 'blake2b' # blake2b (рекомендовано), md5 (швидкий), sha256 (популярний)
MASK_IPN = True # ІПН (10 цифр)
MASK_PASSPORT = True # Паспорти (АА123456) та ID-паспорти (9 цифр)
MASK_MILITARY_ID = True # Військові квитки
MASK_NAMES = True # Імена та прізвища
MASK_RANKS = True # Звання
MASK_BRIGADES = True # Бригади
MASK_MILITARY_UNITS = True # Військові частини
MASK_ORDER_NUMBERS = True # Номери наказів
MASK_BR_NUMBERS = True # БР номери
MASK_DATES = True # ДатиHash algorithm comparison:
| Algorithm | Speed | Security | Usage |
|---|---|---|---|
| blake2b | ⚡⚡⚡⚡⚡ | ✅ High | Recommended (default) |
| md5 | ⚡⚡⚡⚡⚡ | Fastest, sufficient for seed | |
| sha1 | ⚡⚡⚡⚡ | Fast, better than md5 | |
| sha256 | ⚡⚡⚡ | ✅ High | Popular, Bitcoin |
| sha512 | ⚡⚡ | ✅ Maximum | Slowest |
Input:
Молодшому сержанту у відставці СИЧУ Роману Сергійовичу (Житомирський ОТЦКСП),
звільненому 31.05.2025, який є особою з інвалідністю III групи.
Result:
Старшому солдату у відставці БОЙКО Павлу Сергійовичу (Житомирський ОТЦКСП),
звільненому 15.06.2025, який є особою з інвалідністю III групи.
Input:
Капітанці медичної служби Коваленко Марії Іванівні, ІПН 1234567890,
ID-паспорт 123456789, видано наказом №75/25/3400/Р від 26.06.2025.
Result:
Майорці медичної служби Петренко Оксані Іванівні, ІПН 9876543210,
ID-паспорт 123947568, видано наказом №59/87/4249/Р від 10.07.2025.
Input:
Командир 72 окремої механізованої бригади
полковник Сергій ЗАПОРОЖЕЦЬ
Result:
Командир 85 окремої механізованої бригади
підполковник Павло ЗАВОДСЬКИЙ
Input:
Капітан ЗСУ Іванов Петро Миколайович, військовий квиток МТ123456,
проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР.
Result:
Майор ЗСУ Сидоров Андрій Миколайович, військовий квиток МТ654321,
проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР.
Input:
Сержанту БОНДАРЕНКО Олегу та молодшому сержанту КОВАЛЕНКО Андрію,
а також рядовому ШЕВЧЕНКО Петру видати по 100 грн.
Result:
Старшому солдату ЗАВОДСЬКИЙ Павлу та молодшому солдату БОЙКО Станіславу,
а також солдату ПЕТРЕНКО Андрію видати по 100 грн.
{
"version": "2.0",
"mappings": {
"rank": {
"молодшому сержанту": {
"masked_as": "старшому солдату",
"instances": [1, 3]
}
},
"surname": {
"іванов": {
"masked_as": "петренко",
"instances": [1, 2]
}
},
"passport_id": {
"123456789": {
"masked_as": "123947568",
"instances": [1]
}
}
},
"instance_tracking": {
"старшому солдату": 3,
"петренко": 2,
"123947568": 1
}
}Enlisted: рекрут, рядовий, солдат, старший солдат
Sergeants: молодший сержант, сержант, старший сержант, головний сержант, штаб-сержант, майстер-сержант, старший майстер-сержант, головний майстер-сержант
Officers: молодший лейтенант, лейтенант, старший лейтенант, капітан, майор, підполковник, полковник
Generals: бригадний генерал, генерал-майор, генерал-лейтенант, генерал
Sailors: матрос, старший матрос
Petty Officers: головний корабельний старшина, головний старшина, старшина 1/2 статті
Officers: молодший лейтенант, лейтенант, старший лейтенант, капітан-лейтенант
Captains: капітан 3/2/1 рангу
Admirals: контр-адмірал, віце-адмірал, адмірал
Medical: капітан/майор/підполковник/полковник медичної служби
Legal: капітан/майор/підполковник/полковник юстиції
- Називний (nominative): хто? що? — "солдат"
- Родовий (genitive): кого? чого? — "солдата"
- Давальний (dative): кому? чому? — "солдату"
- Орудний (instrumental): ким? чим? — "солдатом"
- Store mapping files in a secure location
- DO NOT transmit mapping files together with masked data
- Delete mapping files after work is complete
- Use different mappings for different documents
- Use mapping file encryption (v2.3.0+):
--encrypt --password - Unmask is ONLY possible with the mapping file
- Pass the password via
--password-env VAR_NAMEinstead of--password(command-line arguments are visible in shell history and process lists)
This is pseudonymization, not full anonymization. Consider the following:
- Partial preservation of the original. IPN retains the first 3 and last digit (4 out of 10), passport retains 4 out of 9, long surnames retain the first 3 and last 5 characters. This is intentional (preserves format and partial context) but allows re-identification by brute-forcing against a candidate dictionary.
- Deterministic generation without a secret. Masks are derived from the blake2b hash of the original without salt/key. Anyone who knows the algorithm and has a list of possible originals can match masks without the mapping file.
- Ranks are shifted by only ±1-2 positions in the hierarchy — the approximate rank of the person remains visible.
- Dates are shifted by ±30 days — the time period of events remains recognizable.
Conclusion: masked files protect against accidental disclosure and are suitable for sharing with a limited audience, but are NOT designed for publication against a motivated adversary with knowledge of the context.
- Check the mapping version (should be v2.0)
- Use the latest script versions
- Make sure
MASK_RANKS = True - Update to the latest version
- Update to v2.1.16+ with whitelist support
- Make sure
MASK_PASSPORT = True - ID passports = 9 digits (not 10 like IPN)
# Add as submodule
git submodule add https://github.com/click0/data-masking.git
# Update submodule
git submodule update --remote data-masking
# Use in project
cd data-masking
python data_masking.py ../documents/input.txt# Clone separately
git clone https://github.com/click0/data-masking.git
cd data-masking
python data_masking.py input.txtBSD 3-Clause "New" or "Revised" License
Vladyslav V. Prodan
- GitHub: @click0
- Phone: +38(099)6053340
See CHANGELOG.md for version history and release notes.