diff --git a/data_masking.py b/data_masking.py index b8d1665..fcf6498 100644 --- a/data_masking.py +++ b/data_masking.py @@ -30,7 +30,7 @@ # Re-exports from masking package for backward compatibility # ============================================================================ -__version__ = "2.6.0" +__version__ = "2.6.1" from masking.constants import ( __version__, __author__, __contact__, __phone__, __license__, __year__, diff --git a/docs/README.md b/docs/README.md index 92e75c1..6d52807 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,133 +1,135 @@ +[Українська версія / Ukrainian version](README_UK.md) + # Data Masking & Unmasking Scripts -Скрипти для **узгодженого маскування** конфіденційних даних у військових документах з можливістю точного відновлення. +Scripts for **consistent masking** of sensitive data in military documents with the ability to accurately restore the original. -Актуальна версія — див. [CHANGELOG.md](../CHANGELOG.md) або `python data_masking.py -V`. +Current version — see [CHANGELOG.md](../CHANGELOG.md) or `python data_masking.py -V`. --- -## 📋 Структура проекту - -### Точки входу -- **`data_masking.py`** — маскування даних (тонка обгортка над пакетом `masking/`) -- **`unmask_data.py`** — розмаскування (тонка обгортка над пакетом `unmasking/`) -- **`diagnose_mapping.py`** — діагностика, порівняння маппінгів та верифікація відновлення -- **`__main__.py`** — запуск з кореня репозиторію: `python . mask [args]` / `python . unmask [args]` - -### Пакет `masking/` (ядро маскування, v2.5.0+) -| Модуль | Опис | -|--------|------| -| `constants.py` | Прапорці `MASK_*`, патерни, словники імен, метадані (єдине джерело версії) | -| `helpers.py` | Seed, mapping, instance tracking, нормалізація | -| `language.py` | Рід, відмінок, відмінювання імен | -| `context.py` | Розпізнавання рядків з ПІБ, парсинг контексту | -| `mask_personal.py` | ІПН, паспорти, прізвища, імена, по батькові | -| `mask_military.py` | Звання, частини, накази, БР, дати | -| `engine.py` | Головний рушій: контекстне маскування тексту/JSON, ініціали | -| `cli.py` | CLI, конфіг, звіти, `main()` | - -### Пакет `unmasking/` (ядро розмаскування, v2.5.0+) -| Модуль | Опис | -|--------|------| -| `helpers.py` | Instance map, пошук пар файлів, версії маппінгів | -| `engine.py` | Відновлення тексту/JSON, ланцюги re-mask | -| `io.py` | Завантаження mapping (.json/.enc), валідація схеми | +## 📋 Project Structure + +### Entry Points +- **`data_masking.py`** — data masking (thin wrapper over the `masking/` package) +- **`unmask_data.py`** — unmasking (thin wrapper over the `unmasking/` package) +- **`diagnose_mapping.py`** — diagnostics, mapping comparison and recovery verification +- **`__main__.py`** — run from the repository root: `python . mask [args]` / `python . unmask [args]` + +### `masking/` Package (masking core, v2.5.0+) +| 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()` | + +### `unmasking/` Package (unmasking core, v2.5.0+) +| 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`** — звання ЗСУ (армія, флот, медична/юридична служба) з відмінками; `modules/rank_data.py` — ре-експорт +### Data +- **`rank_data.py`** — UAF ranks (army, navy, medical/legal service) with declensions; `modules/rank_data.py` — re-export -### Пакет `modules/` (опційні можливості) -| Модуль | Опис | -|--------|------| -| `config.py` | YAML + ENV + CLI конфігурація з пріоритетами (CLI > ENV > YAML > Default) | -| `security.py` | AES-256-GCM шифрування/дешифрування mapping файлів | -| `masking_logger.py` | Структуроване логування (JSON + кольоровий console output) | -| `selective.py` | `--only` / `--exclude` фільтри для вибіркового маскування | -| `re_mask.py` | Ланцюгове перемаскування (multi-pass) з відстеженням ланцюгів | -| `tools.py` | Атомарні функції маскування для програмного використання (API) | -| `password_generator.py` | Генератор паролів (ASCII, кирилиця, кастомні символи) | +### `modules/` Package (optional features) +| 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`** — приклад конфігурації (dataclasses, без зовнішніх залежностей) +### Supporting Files +- **`config_example.py`** — configuration example (dataclasses, no external dependencies) --- -## 🚀 Швидкий старт +## 🚀 Quick Start -### Маскування +### Masking ```bash python data_masking.py input.txt ``` -**Результат:** -- `output/masked_input_YYYYMMDD_HHMMSS.txt` — замаскований текст -- `output/mapping_input_YYYYMMDD_HHMMSS.json` — mapping для unmask -- `output/report_input_YYYYMMDD_HHMMSS.txt` — звіт +**Result:** +- `output/masked_input_YYYYMMDD_HHMMSS.txt` — masked text +- `output/mapping_input_YYYYMMDD_HHMMSS.json` — mapping for unmask +- `output/report_input_YYYYMMDD_HHMMSS.txt` — report -### Розмаскування +### Unmasking -**Автоматичний режим:** +**Automatic mode:** ```bash python unmask_data.py masked_file.txt ``` -**Ручний режим:** +**Manual mode:** ```bash python unmask_data.py masked_file.txt -m mapping_file.json ``` -**З шифрованим mapping файлом (v2.3.0+):** +**With encrypted mapping file (v2.3.0+):** ```bash python unmask_data.py masked_file.txt --map mapping.enc --password mypassword ``` -**З конфігураційним файлом (v2.3.0+):** +**With configuration file (v2.3.0+):** ```bash python unmask_data.py -c config.yaml ``` -**Результат:** -- `result/unmasked_file_YYYYMMDD_HHMMSS.txt` — відновлений текст +**Result:** +- `result/unmasked_file_YYYYMMDD_HHMMSS.txt` — recovered text -### Діагностика +### Diagnostics ```bash -python diagnose_mapping.py mapping1.json mapping2.json # порівняння маппінгів -python diagnose_mapping.py --verify input.txt recovered.txt # верифікація відновлення +python diagnose_mapping.py mapping1.json mapping2.json # compare mappings +python diagnose_mapping.py --verify input.txt recovered.txt # verify recovery ``` --- -## 📊 Що маскується +## 📊 What Gets Masked -### Персональні дані -- **ПІБ** — з урахуванням відмінків та роду -- **ПІБ з ініціалами** (v2.5.0+) — `Іванов П.А.`, `П. Агранов`, `К.П. Іванов`, `Т. А. Сидоренко`, `КОВАЛЕНКО І.В.`; ініціали зберігаються у mapping і відновлюються при unmask -- **По батькові** — маскуються зі збереженням гендеру та регістру -- **ІПН** — 10 цифр -- **ID-паспорти** — 9 цифр (перші 3 + остання фіксовані, середні 5 — випадкові) -- **Паспорти** — АА123456, АА №123456 -- **Військові квитки** — МТ123456, мт-123456, мт №123456 +### Personal Data +- **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 -### Військові дані -- **Звання** — всі відмінки, чоловічі/жіночі форми, з "у відставці"/"в запасі" -- **Бригади** — "123 окрема механізована бригада" -- **Військові частини** — "в/ч А1234" -- **Номери наказів** — "наказ №123 від 01.01.2025" +### Military Data +- **Ranks** — all grammatical cases, masculine/feminine forms, with "retired"/"reserve" modifiers +- **Brigades** — "123 окрема механізована бригада" +- **Military units** — "в/ч А1234" +- **Order numbers** — "наказ №123 від 01.01.2025" -### Документи -- **БР номери** — 75/25/3400/Р, 818/856, 86319/06/1689/Р, 566 -- **Дати** — DD.MM.YYYY (±30 днів) +### Documents +- **BR numbers** — 75/25/3400/Р, 818/856, 86319/06/1689/Р, 566 +- **Dates** — DD.MM.YYYY (±30 days) -### Винятки -- **Абревіатури** — ЗСУ, МОУ, ВСУ, ДПСУ, НГУ, ДСНС, СБУ, ГУР, ТЦК, СП +### Exceptions +- **Abbreviations** — ЗСУ, МОУ, ВСУ, ДПСУ, НГУ, ДСНС, СБУ, ГУР, ТЦК, СП --- -## 🎯 Особливості +## 🎯 Features ### Instance Tracking -Кожне входження відстежується окремо: +Each occurrence is tracked individually: ``` Вхід: "Іванов зустрів Петрова, потім Іванов пішов" Маска: "Сидоров зустрів Коваля, потім Сидоров пішов" @@ -135,45 +137,45 @@ python diagnose_mapping.py --verify input.txt recovered.txt # верифіка Unmask правильно відновить обидва входження ``` -### Збереження граматичних форм +### Preserving Grammatical Forms -**Відмінки звань:** +**Rank declension:** ``` "молодшому сержанту" → "старшому солдату" (давальний) "молодшого сержанта" → "старшого солдата" (родовий) "молодшим сержантом" → "старшим солдатом" (орудний) ``` -**Відмінки імен:** +**Name declension:** ``` "Іванову Петру" → "Сидорову Андрію" (давальний) "Іванова Петра" → "Сидорова Андрія" (родовий) "Івановим Петром" → "Сидоровим Андрієм" (орудний) ``` -**Рід:** +**Gender:** ``` "молодшою сержанткою" → "старшою солдаткою" (жіночий) "капітанці" → "майорці" (жіночий) ``` -**Додаткові слова:** +**Additional modifiers:** ``` "Солдату у відставці" → "Рекруту у відставці" (давальний зберігається!) "Сержанту в запасі" → "Старшому солдату в запасі" (давальний зберігається!) "Капітану на пенсії" → "Майору на пенсії" (давальний зберігається!) ``` -### Збереження регістру +### Case Preservation ``` "ІВАНОВ" → "ПЕТРЕНКО" "Іванов" → "Петренко" "іванов" → "петренко" ``` -### Детермінована генерація +### Deterministic Generation -Система генерує **однакові** mapping файли при обробці **однакових** вхідних даних: +The system generates **identical** mapping files when processing **identical** input data: ```python # Запуск 1: @@ -183,23 +185,23 @@ Unmask правильно відновить обидва входження "Іванов" → blake2b hash → seed → "Петренко" # Той самий результат! ``` -**Переваги:** -- Один оригінал = одна маска (завжди) -- Результат передбачуваний та відтворюваний -- Можна перезапустити на тому самому файлі — отримаєте той самий mapping -- Unmask працює консистентно +**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 -**Технічні деталі:** -- Використовується `hashlib.blake2b()` для генерації seed -- Faker та random ініціалізуються з детермінованим seed +**Technical details:** +- Uses `hashlib.blake2b()` for seed generation +- Faker and random are initialized with a deterministic seed --- -## 🔧 Модулі v2.3.0 +## 🔧 Modules v2.3.0 -### Шифрування mapping файлів (`modules/security.py`) +### Mapping File Encryption (`modules/security.py`) -Mapping файли можна шифрувати AES-256-GCM: +Mapping files can be encrypted with AES-256-GCM: ```bash # Маскування з шифруванням @@ -210,9 +212,9 @@ python data_masking.py input.txt --encrypt --password mypassword python unmask_data.py masked.txt --map mapping.enc --password mypassword ``` -### Конфігурація (`modules/config.py`) +### Configuration (`modules/config.py`) -Пріоритет: CLI > ENV > config.yaml > Default +Priority: CLI > ENV > config.yaml > Default ```bash # Використання YAML конфігурації @@ -221,7 +223,7 @@ python data_masking.py input.txt -c config.yaml # Генерація прикладу конфігурації — див. config_example.py ``` -### Вибіркове маскування (`modules/selective.py`) +### Selective Masking (`modules/selective.py`) ```bash # Маскувати тільки ІПН та паспорти @@ -231,9 +233,9 @@ python data_masking.py input.txt --only ipn,passport python data_masking.py input.txt --exclude dates ``` -### Ланцюгове перемаскування (`modules/re_mask.py`) +### Chain Re-masking (`modules/re_mask.py`) -Для повторного маскування вже замаскованих файлів зі збереженням повного ланцюга маппінгів: +For re-masking already masked files while preserving the full mapping chain: ```python from modules.re_mask import ReMasker, MappingChain @@ -242,9 +244,9 @@ chain = MappingChain() remasker = ReMasker(chain=chain, mask_function=my_mask_fn) ``` -### Програмний API (`modules/tools.py`) +### Programmatic API (`modules/tools.py`) -Атомарні функції для інтеграції в зовнішні додатки: +Atomic functions for integration into external applications: ```python from modules.tools import mask_ipn_direct, mask_rank_direct, mask_pib_force @@ -252,7 +254,7 @@ from modules.tools import mask_ipn_direct, mask_rank_direct, mask_pib_force result = mask_ipn_direct("1234567890", masking_dict, instance_counters) ``` -### Генератор паролів (`modules/password_generator.py`) +### Password Generator (`modules/password_generator.py`) ```python from modules.password_generator import generate_password @@ -267,9 +269,9 @@ pwd = generate_password(length=32, include_cyrillic_lower=True) # python -m modules.password_generator --length 32 --cyrillic ``` -### Логування (`modules/masking_logger.py`) +### Logging (`modules/masking_logger.py`) -Структуроване логування з JSON та кольоровим console output: +Structured logging with JSON and colored console output: ```python from modules.masking_logger import MaskingLogger, setup_logging @@ -280,9 +282,9 @@ setup_logging(level="DEBUG", json_output=True) --- -## ⚙️ Налаштування +## ⚙️ Configuration -В `data_masking.py` можна увімкнути/вимкнути категорії: +In `data_masking.py` you can enable/disable categories: ```python # Алгоритм хешування для детермінованої генерації @@ -300,80 +302,80 @@ MASK_BR_NUMBERS = True # БР номери MASK_DATES = True # Дати ``` -**Порівняння алгоритмів хешування:** +**Hash algorithm comparison:** -| Алгоритм | Швидкість | Безпека | Використання | -|----------|-----------|---------|--------------| -| blake2b | ⚡⚡⚡⚡⚡ | ✅ Високо | Рекомендовано (за замовчуванням) | -| md5 | ⚡⚡⚡⚡⚡ | ⚠️ Низька для криптографії | Найшвидший, достатній для seed | -| sha1 | ⚡⚡⚡⚡ | ⚠️ Застарілий | Швидкий, краще ніж md5 | -| sha256 | ⚡⚡⚡ | ✅ Високо | Популярний, Bitcoin | -| sha512 | ⚡⚡ | ✅ Максимально | Найповільніший | +| Algorithm | Speed | Security | Usage | +|-----------|-------|----------|-------| +| blake2b | ⚡⚡⚡⚡⚡ | ✅ High | Recommended (default) | +| md5 | ⚡⚡⚡⚡⚡ | ⚠️ Low for cryptography | Fastest, sufficient for seed | +| sha1 | ⚡⚡⚡⚡ | ⚠️ Deprecated | Fast, better than md5 | +| sha256 | ⚡⚡⚡ | ✅ High | Popular, Bitcoin | +| sha512 | ⚡⚡ | ✅ Maximum | Slowest | --- -## 📝 Приклади маскування +## 📝 Masking Examples -### Приклад 1: Відмінки та додаткові слова -**Вхід:** +### Example 1: Declensions and additional modifiers +**Input:** ``` Молодшому сержанту у відставці СИЧУ Роману Сергійовичу (Житомирський ОТЦКСП), звільненому 31.05.2025, який є особою з інвалідністю III групи. ``` -**Результат:** +**Result:** ``` Старшому солдату у відставці БОЙКО Павлу Сергійовичу (Житомирський ОТЦКСП), звільненому 15.06.2025, який є особою з інвалідністю III групи. ``` -### Приклад 2: Жіночі форми та документи -**Вхід:** +### Example 2: Feminine forms and documents +**Input:** ``` Капітанці медичної служби Коваленко Марії Іванівні, ІПН 1234567890, ID-паспорт 123456789, видано наказом №75/25/3400/Р від 26.06.2025. ``` -**Результат:** +**Result:** ``` Майорці медичної служби Петренко Оксані Іванівні, ІПН 9876543210, ID-паспорт 123947568, видано наказом №59/87/4249/Р від 10.07.2025. ``` -### Приклад 3: Формат підпису -**Вхід:** +### Example 3: Signature format +**Input:** ``` Командир 72 окремої механізованої бригади полковник Сергій ЗАПОРОЖЕЦЬ ``` -**Результат:** +**Result:** ``` Командир 85 окремої механізованої бригади підполковник Павло ЗАВОДСЬКИЙ ``` -### Приклад 4: Абревіатури не маскуються -**Вхід:** +### Example 4: Abbreviations are not masked +**Input:** ``` Капітан ЗСУ Іванов Петро Миколайович, військовий квиток МТ123456, проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР. ``` -**Результат:** +**Result:** ``` Майор ЗСУ Сидоров Андрій Миколайович, військовий квиток МТ654321, проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР. ``` -### Приклад 5: Множинні ПІБ в одному рядку -**Вхід:** +### Example 5: Multiple PIB in one line +**Input:** ``` Сержанту БОНДАРЕНКО Олегу та молодшому сержанту КОВАЛЕНКО Андрію, а також рядовому ШЕВЧЕНКО Петру видати по 100 грн. ``` -**Результат:** +**Result:** ``` Старшому солдату ЗАВОДСЬКИЙ Павлу та молодшому солдату БОЙКО Станіславу, а також солдату ПЕТРЕНКО Андрію видати по 100 грн. @@ -381,7 +383,7 @@ ID-паспорт 123947568, видано наказом №59/87/4249/Р від --- -## 📝 Формат mapping файлу +## 📝 Mapping File Format ```json { @@ -416,34 +418,34 @@ ID-паспорт 123947568, видано наказом №59/87/4249/Р від --- -## 📚 Підтримувані звання +## 📚 Supported Ranks -### Армія (32 звання) -**Рядові:** рекрут, рядовий, солдат, старший солдат +### Army (32 ranks) +**Enlisted:** рекрут, рядовий, солдат, старший солдат -**Сержанти:** молодший сержант, сержант, старший сержант, головний сержант, штаб-сержант, майстер-сержант, старший майстер-сержант, головний майстер-сержант +**Sergeants:** молодший сержант, сержант, старший сержант, головний сержант, штаб-сержант, майстер-сержант, старший майстер-сержант, головний майстер-сержант -**Офіцери:** молодший лейтенант, лейтенант, старший лейтенант, капітан, майор, підполковник, полковник +**Officers:** молодший лейтенант, лейтенант, старший лейтенант, капітан, майор, підполковник, полковник -**Генерали:** бригадний генерал, генерал-майор, генерал-лейтенант, генерал +**Generals:** бригадний генерал, генерал-майор, генерал-лейтенант, генерал -### Флот (22 звання) -**Матроси:** матрос, старший матрос +### Navy (22 ranks) +**Sailors:** матрос, старший матрос -**Старшини:** головний корабельний старшина, головний старшина, старшина 1/2 статті +**Petty Officers:** головний корабельний старшина, головний старшина, старшина 1/2 статті -**Офіцери:** молодший лейтенант, лейтенант, старший лейтенант, капітан-лейтенант +**Officers:** молодший лейтенант, лейтенант, старший лейтенант, капітан-лейтенант -**Капітани:** капітан 3/2/1 рангу +**Captains:** капітан 3/2/1 рангу -**Адмірали:** контр-адмірал, віце-адмірал, адмірал +**Admirals:** контр-адмірал, віце-адмірал, адмірал -### Спеціальні служби -**Медична:** капітан/майор/підполковник/полковник медичної служби +### Special Services +**Medical:** капітан/майор/підполковник/полковник медичної служби -**Юридична:** капітан/майор/підполковник/полковник юстиції +**Legal:** капітан/майор/підполковник/полковник юстиції -### Підтримувані відмінки +### Supported Grammatical Cases - **Називний** (nominative): хто? що? — "солдат" - **Родовий** (genitive): кого? чого? — "солдата" - **Давальний** (dative): кому? чому? — "солдату" @@ -451,70 +453,70 @@ ID-паспорт 123947568, видано наказом №59/87/4249/Р від --- -## 🔐 Безпека - -### Рекомендації: -1. Зберігайте mapping файли в **захищеному місці** -2. **НЕ** передавайте mapping разом з замаскованими даними -3. Видаляйте mapping після завершення роботи -4. Використовуйте різні mapping для різних документів -5. Використовуйте шифрування mapping файлів (v2.3.0+): `--encrypt --password` -6. Unmask можливий ТІЛЬКИ при наявності mapping файлу -7. Передавайте пароль через `--password-env ІМ'Я_ЗМІННОЇ`, а не `--password` - (аргументи командного рядка видно в історії shell та списку процесів) - -### ⚠️ Модель загроз та обмеження - -Це **псевдонімізація**, а не повна анонімізація. Враховуйте: - -- **Часткове збереження оригіналу.** ІПН зберігає перші 3 та останню цифру - (4 з 10), паспорт — 4 з 9, довгі прізвища — перші 3 та останні 5 літер. - Це навмисно (зберігає формат і частковий контекст), але дозволяє - ре-ідентифікацію перебором за словником кандидатів. -- **Детермінована генерація без секрету.** Маски виводяться з blake2b-хешу - оригіналу без солі/ключа. Хто знає алгоритм і має список можливих - оригіналів, може зіставити маски без mapping-файлу. -- **Звання зсуваються лише на ±1–2 позиції** в ієрархії — приблизний ранг - особи залишається видимим. -- **Дати зсуваються на ±30 днів** — період подій залишається впізнаваним. - -**Висновок:** замасковані файли захищають від випадкового розголошення та -підходять для передачі обмеженому колу, але НЕ розраховані на публікацію -проти мотивованого супротивника зі знанням контексту. +## 🔐 Security + +### Recommendations: +1. Store mapping files in a **secure location** +2. **DO NOT** transmit mapping files together with masked data +3. Delete mapping files after work is complete +4. Use different mappings for different documents +5. Use mapping file encryption (v2.3.0+): `--encrypt --password` +6. Unmask is ONLY possible with the mapping file +7. Pass the password via `--password-env VAR_NAME` instead of `--password` + (command-line arguments are visible in shell history and process lists) + +### ⚠️ Threat Model and Limitations + +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. --- ## 🛠️ Troubleshooting -### Unmask не відновлює правильно -- Перевірте версію mapping (має бути v2.0) -- Використайте актуальні версії скриптів +### Unmask does not restore correctly +- Check the mapping version (should be v2.0) +- Use the latest script versions -### Звання не маскуються -- Переконайтесь що `MASK_RANKS = True` -- Оновіть до актуальної версії +### Ranks are not masked +- Make sure `MASK_RANKS = True` +- Update to the latest version -### Абревіатури маскуються -- Оновіть до v2.1.16+ з whitelist підтримкою +### Abbreviations are being masked +- Update to v2.1.16+ with whitelist support -### ID-паспорти не маскуються -- Переконайтесь що `MASK_PASSPORT = True` -- ID-паспорти = 9 цифр (не 10 як ІПН) +### ID passports are not masked +- Make sure `MASK_PASSPORT = True` +- ID passports = 9 digits (not 10 like IPN) --- -## 📦 Використання в проектах +## 📦 Usage in Projects -### Git Submodule (рекомендовано) +### Git Submodule (recommended) ```bash -# Додати як submodule +# Add as submodule git submodule add https://github.com/click0/data-masking.git -# Оновити submodule +# Update submodule git submodule update --remote data-masking -# Використання в проекті +# Use in project cd data-masking python data_masking.py ../documents/input.txt ``` @@ -522,7 +524,7 @@ python data_masking.py ../documents/input.txt ### Standalone ```bash -# Клонувати окремо +# Clone separately git clone https://github.com/click0/data-masking.git cd data-masking python data_masking.py input.txt @@ -530,13 +532,13 @@ python data_masking.py input.txt --- -## 📜 Ліцензія +## 📜 License BSD 3-Clause "New" or "Revised" License --- -## 👨‍💻 Автор +## 👨‍💻 Author **Vladyslav V. Prodan** - GitHub: [@click0](https://github.com/click0) @@ -544,6 +546,6 @@ BSD 3-Clause "New" or "Revised" License --- -## 📅 Версія +## 📅 Version -Актуальну версію та історію змін див. у [CHANGELOG.md](../CHANGELOG.md) +See [CHANGELOG.md](../CHANGELOG.md) for version history and release notes. diff --git a/docs/README_UK.md b/docs/README_UK.md new file mode 100644 index 0000000..83baa0b --- /dev/null +++ b/docs/README_UK.md @@ -0,0 +1,551 @@ +[English version](README.md) + +# Data Masking & Unmasking Scripts + +Скрипти для **узгодженого маскування** конфіденційних даних у військових документах з можливістю точного відновлення. + +Актуальна версія — див. [CHANGELOG.md](../CHANGELOG.md) або `python data_masking.py -V`. + +--- + +## 📋 Структура проекту + +### Точки входу +- **`data_masking.py`** — маскування даних (тонка обгортка над пакетом `masking/`) +- **`unmask_data.py`** — розмаскування (тонка обгортка над пакетом `unmasking/`) +- **`diagnose_mapping.py`** — діагностика, порівняння маппінгів та верифікація відновлення +- **`__main__.py`** — запуск з кореня репозиторію: `python . mask [args]` / `python . unmask [args]` + +### Пакет `masking/` (ядро маскування, v2.5.0+) +| Модуль | Опис | +|--------|------| +| `constants.py` | Прапорці `MASK_*`, патерни, словники імен, метадані (єдине джерело версії) | +| `helpers.py` | Seed, mapping, instance tracking, нормалізація | +| `language.py` | Рід, відмінок, відмінювання імен | +| `context.py` | Розпізнавання рядків з ПІБ, парсинг контексту | +| `mask_personal.py` | ІПН, паспорти, прізвища, імена, по батькові | +| `mask_military.py` | Звання, частини, накази, БР, дати | +| `engine.py` | Головний рушій: контекстне маскування тексту/JSON, ініціали | +| `cli.py` | CLI, конфіг, звіти, `main()` | + +### Пакет `unmasking/` (ядро розмаскування, v2.5.0+) +| Модуль | Опис | +|--------|------| +| `helpers.py` | Instance map, пошук пар файлів, версії маппінгів | +| `engine.py` | Відновлення тексту/JSON, ланцюги re-mask | +| `io.py` | Завантаження mapping (.json/.enc), валідація схеми | +| `cli.py` | CLI, `main()` | + +### Дані +- **`rank_data.py`** — звання ЗСУ (армія, флот, медична/юридична служба) з відмінками; `modules/rank_data.py` — ре-експорт + +### Пакет `modules/` (опційні можливості) +| Модуль | Опис | +|--------|------| +| `config.py` | YAML + ENV + CLI конфігурація з пріоритетами (CLI > ENV > YAML > Default) | +| `security.py` | AES-256-GCM шифрування/дешифрування mapping файлів | +| `masking_logger.py` | Структуроване логування (JSON + кольоровий console output) | +| `selective.py` | `--only` / `--exclude` фільтри для вибіркового маскування | +| `re_mask.py` | Ланцюгове перемаскування (multi-pass) з відстеженням ланцюгів | +| `tools.py` | Атомарні функції маскування для програмного використання (API) | +| `password_generator.py` | Генератор паролів (ASCII, кирилиця, кастомні символи) | + +### Допоміжні файли +- **`config_example.py`** — приклад конфігурації (dataclasses, без зовнішніх залежностей) + +--- + +## 🚀 Швидкий старт + +### Маскування +```bash +python data_masking.py input.txt +``` + +**Результат:** +- `output/masked_input_YYYYMMDD_HHMMSS.txt` — замаскований текст +- `output/mapping_input_YYYYMMDD_HHMMSS.json` — mapping для unmask +- `output/report_input_YYYYMMDD_HHMMSS.txt` — звіт + +### Розмаскування + +**Автоматичний режим:** +```bash +python unmask_data.py masked_file.txt +``` + +**Ручний режим:** +```bash +python unmask_data.py masked_file.txt -m mapping_file.json +``` + +**З шифрованим mapping файлом (v2.3.0+):** +```bash +python unmask_data.py masked_file.txt --map mapping.enc --password mypassword +``` + +**З конфігураційним файлом (v2.3.0+):** +```bash +python unmask_data.py -c config.yaml +``` + +**Результат:** +- `result/unmasked_file_YYYYMMDD_HHMMSS.txt` — відновлений текст + +### Діагностика +```bash +python diagnose_mapping.py mapping1.json mapping2.json # порівняння маппінгів +python diagnose_mapping.py --verify input.txt recovered.txt # верифікація відновлення +``` + +--- + +## 📊 Що маскується + +### Персональні дані +- **ПІБ** — з урахуванням відмінків та роду +- **ПІБ з ініціалами** (v2.5.0+) — `Іванов П.А.`, `П. Агранов`, `К.П. Іванов`, `Т. А. Сидоренко`, `КОВАЛЕНКО І.В.`; ініціали зберігаються у mapping і відновлюються при unmask +- **По батькові** — маскуються зі збереженням гендеру та регістру +- **ІПН** — 10 цифр +- **ID-паспорти** — 9 цифр (перші 3 + остання фіксовані, середні 5 — випадкові) +- **Паспорти** — АА123456, АА №123456 +- **Військові квитки** — МТ123456, мт-123456, мт №123456 + +### Військові дані +- **Звання** — всі відмінки, чоловічі/жіночі форми, з "у відставці"/"в запасі" +- **Бригади** — "123 окрема механізована бригада" +- **Військові частини** — "в/ч А1234" +- **Номери наказів** — "наказ №123 від 01.01.2025" + +### Документи +- **БР номери** — 75/25/3400/Р, 818/856, 86319/06/1689/Р, 566 +- **Дати** — DD.MM.YYYY (±30 днів) + +### Винятки +- **Абревіатури** — ЗСУ, МОУ, ВСУ, ДПСУ, НГУ, ДСНС, СБУ, ГУР, ТЦК, СП + +--- + +## 🎯 Особливості + +### Instance Tracking +Кожне входження відстежується окремо: +``` +Вхід: "Іванов зустрів Петрова, потім Іванов пішов" +Маска: "Сидоров зустрів Коваля, потім Сидоров пішов" + ↑ instance 1 ↑ instance 2 +Unmask правильно відновить обидва входження +``` + +### Збереження граматичних форм + +**Відмінки звань:** +``` +"молодшому сержанту" → "старшому солдату" (давальний) +"молодшого сержанта" → "старшого солдата" (родовий) +"молодшим сержантом" → "старшим солдатом" (орудний) +``` + +**Відмінки імен:** +``` +"Іванову Петру" → "Сидорову Андрію" (давальний) +"Іванова Петра" → "Сидорова Андрія" (родовий) +"Івановим Петром" → "Сидоровим Андрієм" (орудний) +``` + +**Рід:** +``` +"молодшою сержанткою" → "старшою солдаткою" (жіночий) +"капітанці" → "майорці" (жіночий) +``` + +**Додаткові слова:** +``` +"Солдату у відставці" → "Рекруту у відставці" (давальний зберігається!) +"Сержанту в запасі" → "Старшому солдату в запасі" (давальний зберігається!) +"Капітану на пенсії" → "Майору на пенсії" (давальний зберігається!) +``` + +### Збереження регістру +``` +"ІВАНОВ" → "ПЕТРЕНКО" +"Іванов" → "Петренко" +"іванов" → "петренко" +``` + +### Детермінована генерація + +Система генерує **однакові** mapping файли при обробці **однакових** вхідних даних: + +```python +# Запуск 1: +"Іванов" → blake2b hash → seed → "Петренко" + +# Запуск 2 (той самий файл): +"Іванов" → blake2b hash → seed → "Петренко" # Той самий результат! +``` + +**Переваги:** +- Один оригінал = одна маска (завжди) +- Результат передбачуваний та відтворюваний +- Можна перезапустити на тому самому файлі — отримаєте той самий mapping +- Unmask працює консистентно + +**Технічні деталі:** +- Використовується `hashlib.blake2b()` для генерації seed +- Faker та random ініціалізуються з детермінованим seed + +--- + +## 🔧 Модулі v2.3.0 + +### Шифрування mapping файлів (`modules/security.py`) + +Mapping файли можна шифрувати AES-256-GCM: + +```bash +# Маскування з шифруванням +python data_masking.py input.txt --encrypt --password mypassword +# Результат: mapping_*.json.enc + +# Розмаскування з шифрованим mapping +python unmask_data.py masked.txt --map mapping.enc --password mypassword +``` + +### Конфігурація (`modules/config.py`) + +Пріоритет: CLI > ENV > config.yaml > Default + +```bash +# Використання YAML конфігурації +python data_masking.py input.txt -c config.yaml + +# Генерація прикладу конфігурації — див. config_example.py +``` + +### Вибіркове маскування (`modules/selective.py`) + +```bash +# Маскувати тільки ІПН та паспорти +python data_masking.py input.txt --only ipn,passport + +# Маскувати все крім дат +python data_masking.py input.txt --exclude dates +``` + +### Ланцюгове перемаскування (`modules/re_mask.py`) + +Для повторного маскування вже замаскованих файлів зі збереженням повного ланцюга маппінгів: + +```python +from modules.re_mask import ReMasker, MappingChain + +chain = MappingChain() +remasker = ReMasker(chain=chain, mask_function=my_mask_fn) +``` + +### Програмний API (`modules/tools.py`) + +Атомарні функції для інтеграції в зовнішні додатки: + +```python +from modules.tools import mask_ipn_direct, mask_rank_direct, mask_pib_force + +result = mask_ipn_direct("1234567890", masking_dict, instance_counters) +``` + +### Генератор паролів (`modules/password_generator.py`) + +```python +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 --cyrillic +``` + +### Логування (`modules/masking_logger.py`) + +Структуроване логування з JSON та кольоровим console output: + +```python +from modules.masking_logger import MaskingLogger, setup_logging + +logger = MaskingLogger("masking") +setup_logging(level="DEBUG", json_output=True) +``` + +--- + +## ⚙️ Налаштування + +В `data_masking.py` можна увімкнути/вимкнути категорії: + +```python +# Алгоритм хешування для детермінованої генерації +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 # Дати +``` + +**Порівняння алгоритмів хешування:** + +| Алгоритм | Швидкість | Безпека | Використання | +|----------|-----------|---------|--------------| +| blake2b | ⚡⚡⚡⚡⚡ | ✅ Високо | Рекомендовано (за замовчуванням) | +| md5 | ⚡⚡⚡⚡⚡ | ⚠️ Низька для криптографії | Найшвидший, достатній для seed | +| sha1 | ⚡⚡⚡⚡ | ⚠️ Застарілий | Швидкий, краще ніж md5 | +| sha256 | ⚡⚡⚡ | ✅ Високо | Популярний, Bitcoin | +| sha512 | ⚡⚡ | ✅ Максимально | Найповільніший | + +--- + +## 📝 Приклади маскування + +### Приклад 1: Відмінки та додаткові слова +**Вхід:** +``` +Молодшому сержанту у відставці СИЧУ Роману Сергійовичу (Житомирський ОТЦКСП), +звільненому 31.05.2025, який є особою з інвалідністю III групи. +``` + +**Результат:** +``` +Старшому солдату у відставці БОЙКО Павлу Сергійовичу (Житомирський ОТЦКСП), +звільненому 15.06.2025, який є особою з інвалідністю III групи. +``` + +### Приклад 2: Жіночі форми та документи +**Вхід:** +``` +Капітанці медичної служби Коваленко Марії Іванівні, ІПН 1234567890, +ID-паспорт 123456789, видано наказом №75/25/3400/Р від 26.06.2025. +``` + +**Результат:** +``` +Майорці медичної служби Петренко Оксані Іванівні, ІПН 9876543210, +ID-паспорт 123947568, видано наказом №59/87/4249/Р від 10.07.2025. +``` + +### Приклад 3: Формат підпису +**Вхід:** +``` +Командир 72 окремої механізованої бригади +полковник Сергій ЗАПОРОЖЕЦЬ +``` + +**Результат:** +``` +Командир 85 окремої механізованої бригади +підполковник Павло ЗАВОДСЬКИЙ +``` + +### Приклад 4: Абревіатури не маскуються +**Вхід:** +``` +Капітан ЗСУ Іванов Петро Миколайович, військовий квиток МТ123456, +проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР. +``` + +**Результат:** +``` +Майор ЗСУ Сидоров Андрій Миколайович, військовий квиток МТ654321, +проходив службу у МОУ, нагороджений СБУ за заслуги перед ГУР. +``` + +### Приклад 5: Множинні ПІБ в одному рядку +**Вхід:** +``` +Сержанту БОНДАРЕНКО Олегу та молодшому сержанту КОВАЛЕНКО Андрію, +а також рядовому ШЕВЧЕНКО Петру видати по 100 грн. +``` + +**Результат:** +``` +Старшому солдату ЗАВОДСЬКИЙ Павлу та молодшому солдату БОЙКО Станіславу, +а також солдату ПЕТРЕНКО Андрію видати по 100 грн. +``` + +--- + +## 📝 Формат mapping файлу + +```json +{ + "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 + } +} +``` + +--- + +## 📚 Підтримувані звання + +### Армія (32 звання) +**Рядові:** рекрут, рядовий, солдат, старший солдат + +**Сержанти:** молодший сержант, сержант, старший сержант, головний сержант, штаб-сержант, майстер-сержант, старший майстер-сержант, головний майстер-сержант + +**Офіцери:** молодший лейтенант, лейтенант, старший лейтенант, капітан, майор, підполковник, полковник + +**Генерали:** бригадний генерал, генерал-майор, генерал-лейтенант, генерал + +### Флот (22 звання) +**Матроси:** матрос, старший матрос + +**Старшини:** головний корабельний старшина, головний старшина, старшина 1/2 статті + +**Офіцери:** молодший лейтенант, лейтенант, старший лейтенант, капітан-лейтенант + +**Капітани:** капітан 3/2/1 рангу + +**Адмірали:** контр-адмірал, віце-адмірал, адмірал + +### Спеціальні служби +**Медична:** капітан/майор/підполковник/полковник медичної служби + +**Юридична:** капітан/майор/підполковник/полковник юстиції + +### Підтримувані відмінки +- **Називний** (nominative): хто? що? — "солдат" +- **Родовий** (genitive): кого? чого? — "солдата" +- **Давальний** (dative): кому? чому? — "солдату" +- **Орудний** (instrumental): ким? чим? — "солдатом" + +--- + +## 🔐 Безпека + +### Рекомендації: +1. Зберігайте mapping файли в **захищеному місці** +2. **НЕ** передавайте mapping разом з замаскованими даними +3. Видаляйте mapping після завершення роботи +4. Використовуйте різні mapping для різних документів +5. Використовуйте шифрування mapping файлів (v2.3.0+): `--encrypt --password` +6. Unmask можливий ТІЛЬКИ при наявності mapping файлу +7. Передавайте пароль через `--password-env ІМ'Я_ЗМІННОЇ`, а не `--password` + (аргументи командного рядка видно в історії shell та списку процесів) + +### ⚠️ Модель загроз та обмеження + +Це **псевдонімізація**, а не повна анонімізація. Враховуйте: + +- **Часткове збереження оригіналу.** ІПН зберігає перші 3 та останню цифру + (4 з 10), паспорт — 4 з 9, довгі прізвища — перші 3 та останні 5 літер. + Це навмисно (зберігає формат і частковий контекст), але дозволяє + ре-ідентифікацію перебором за словником кандидатів. +- **Детермінована генерація без секрету.** Маски виводяться з blake2b-хешу + оригіналу без солі/ключа. Хто знає алгоритм і має список можливих + оригіналів, може зіставити маски без mapping-файлу. +- **Звання зсуваються лише на ±1–2 позиції** в ієрархії — приблизний ранг + особи залишається видимим. +- **Дати зсуваються на ±30 днів** — період подій залишається впізнаваним. + +**Висновок:** замасковані файли захищають від випадкового розголошення та +підходять для передачі обмеженому колу, але НЕ розраховані на публікацію +проти мотивованого супротивника зі знанням контексту. + +--- + +## 🛠️ Troubleshooting + +### Unmask не відновлює правильно +- Перевірте версію mapping (має бути v2.0) +- Використайте актуальні версії скриптів + +### Звання не маскуються +- Переконайтесь що `MASK_RANKS = True` +- Оновіть до актуальної версії + +### Абревіатури маскуються +- Оновіть до v2.1.16+ з whitelist підтримкою + +### ID-паспорти не маскуються +- Переконайтесь що `MASK_PASSPORT = True` +- ID-паспорти = 9 цифр (не 10 як ІПН) + +--- + +## 📦 Використання в проектах + +### Git Submodule (рекомендовано) + +```bash +# Додати як submodule +git submodule add https://github.com/click0/data-masking.git + +# Оновити submodule +git submodule update --remote data-masking + +# Використання в проекті +cd data-masking +python data_masking.py ../documents/input.txt +``` + +### Standalone + +```bash +# Клонувати окремо +git clone https://github.com/click0/data-masking.git +cd data-masking +python data_masking.py input.txt +``` + +--- + +## 📜 Ліцензія + +BSD 3-Clause "New" or "Revised" License + +--- + +## 👨‍💻 Автор + +**Vladyslav V. Prodan** +- GitHub: [@click0](https://github.com/click0) +- Phone: +38(099)6053340 + +--- + +## 📅 Версія + +Актуальну версію та історію змін див. у [CHANGELOG.md](../CHANGELOG.md) diff --git a/masking/constants.py b/masking/constants.py index 06af580..343176a 100644 --- a/masking/constants.py +++ b/masking/constants.py @@ -26,7 +26,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.0" +__version__ = "2.6.1" __author__ = "Vladyslav V. Prodan" __contact__ = "github.com/click0" __phone__ = "+38(099)6053340" diff --git a/unmasking/cli.py b/unmasking/cli.py index 3c96eba..4d21c9a 100644 --- a/unmasking/cli.py +++ b/unmasking/cli.py @@ -58,7 +58,7 @@ # ============================================================================ # МЕТАДАНІ # ============================================================================ -__version__ = "2.6.0" +__version__ = "2.6.1" def main():