obfuscator-rb 0.3.2 → 0.8.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +14 -2
- data/CHANGELOG.md +90 -0
- data/README.md +205 -61
- data/RELEASE_CHECKLIST.md +192 -0
- data/lib/obfuscator/date_obfuscator.rb +16 -6
- data/lib/obfuscator/internal/rng.rb +6 -0
- data/lib/obfuscator/number_obfuscator.rb +323 -0
- data/lib/obfuscator/version.rb +1 -1
- data/lib/obfuscator-rb.rb +57 -5
- metadata +15 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7884592e18d5055a2d68e8cb40df90adc40a3408e331add6ee8afa7195c329bd
|
4
|
+
data.tar.gz: d24f0d673b56d638b87b6d86e23592b8043fdb765d8c9718e852f5b94e11b255
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5da8d928def751c287668898aa6b9026eab99a0c2d963ca6d5247a0681809e838e193f86451c5e091d9c21e529fa19c444703bed85cbf2f58525c61985322a0c
|
7
|
+
data.tar.gz: 9ea77d3bb0d6aa04a2cdfa0512c3b254bb22b7a61bffeaa15a8b090ed8950de36a77f5ea3b7335421b5901c8af11dbb69440bf66212eaaab9c6091d79d58f8ef
|
data/.rubocop.yml
CHANGED
@@ -33,7 +33,7 @@ Layout/HashAlignment:
|
|
33
33
|
EnforcedColonStyle: table
|
34
34
|
|
35
35
|
Metrics/ClassLength:
|
36
|
-
Max:
|
36
|
+
Max: 250
|
37
37
|
Exclude:
|
38
38
|
- "test/*"
|
39
39
|
|
@@ -44,7 +44,7 @@ Metrics/MethodLength:
|
|
44
44
|
Max: 60
|
45
45
|
|
46
46
|
Metrics/AbcSize:
|
47
|
-
Max:
|
47
|
+
Max: 45
|
48
48
|
Exclude:
|
49
49
|
- "test/*"
|
50
50
|
|
@@ -53,3 +53,15 @@ Metrics/PerceivedComplexity:
|
|
53
53
|
|
54
54
|
Metrics/CyclomaticComplexity:
|
55
55
|
Max: 30
|
56
|
+
|
57
|
+
Minitest/MultipleAssertions:
|
58
|
+
Max: 6
|
59
|
+
|
60
|
+
Performance/ChainArrayAllocation:
|
61
|
+
Enabled: true
|
62
|
+
|
63
|
+
Style/MultilineBlockChain:
|
64
|
+
Enabled: true
|
65
|
+
|
66
|
+
Performance/IoReadlines:
|
67
|
+
Enabled: true
|
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,96 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.8.1] - 2024-02-23
|
8
|
+
|
9
|
+
### Changed
|
10
|
+
- Updated RELEASE_CHECKLIST.md with Git workflow guide
|
11
|
+
|
12
|
+
## [0.8.0] - 2024-02-23
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
- Enhanced top-level documentation with comprehensive NumberObfuscator examples
|
16
|
+
- Improved bilingual documentation symmetry in README
|
17
|
+
- Updated gem description to better reflect all features
|
18
|
+
- Made thread safety warnings more prominent
|
19
|
+
|
20
|
+
### Fixed
|
21
|
+
- Corrected and standardized examples across both languages in README
|
22
|
+
- Fixed missing US format examples in Russian documentation
|
23
|
+
- Fixed missing short format examples in English documentation
|
24
|
+
- Synchronized error handling examples between languages
|
25
|
+
|
26
|
+
## [0.7.0] - 2024-02-22
|
27
|
+
|
28
|
+
### Changed
|
29
|
+
- Clarified thread safety requirements in documentation
|
30
|
+
- Updated gem description to better reflect current capabilities
|
31
|
+
- Enhanced documentation with bilingual thread safety guidelines
|
32
|
+
|
33
|
+
### Fixed
|
34
|
+
- Corrected misleading thread safety claims in documentation
|
35
|
+
- Updated examples to show proper instance usage in concurrent scenarios
|
36
|
+
- Fixed DateObfuscator examples to match actual implementation
|
37
|
+
|
38
|
+
## [0.6.0] - 2024-02-22
|
39
|
+
|
40
|
+
### Added
|
41
|
+
- Support for IP-like number sequences (e.g., "21.11.234.23")
|
42
|
+
- Configurable unsigned mode for NumberObfuscator
|
43
|
+
- Improved decimal precision handling in float obfuscation
|
44
|
+
|
45
|
+
### Fixed
|
46
|
+
- Resolved repeating patterns in seeded number generation
|
47
|
+
- Fixed decimal places preservation in float obfuscation
|
48
|
+
- Corrected handling of leading zeros with format preservation
|
49
|
+
- Fixed sign handling in unsigned mode
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
- Enhanced number generation algorithm for better randomization
|
53
|
+
- Improved format preservation for complex number patterns
|
54
|
+
- Optimized RNG state management for consistent results
|
55
|
+
|
56
|
+
## [0.5.0] - 2024-02-21
|
57
|
+
|
58
|
+
### Added
|
59
|
+
- Performance optimizations for NumberObfuscator with large numbers
|
60
|
+
- Added stress test mode for extreme number handling
|
61
|
+
- Improved test coverage for edge cases in number obfuscation
|
62
|
+
- Support for US date formats in DateObfuscator
|
63
|
+
- ISO full datetime format support
|
64
|
+
|
65
|
+
### Changed
|
66
|
+
- Adjusted number magnitude checks in tests for better reliability
|
67
|
+
- Made extreme number tests optional via STRESS_TEST environment variable
|
68
|
+
- Enhanced DateObfuscator format presets
|
69
|
+
|
70
|
+
### Fixed
|
71
|
+
- Performance issues with Float::MAX/MIN handling in NumberObfuscator
|
72
|
+
- DateObfuscator now properly handles seeds with different input dates
|
73
|
+
- Added combined seed calculation to ensure unique but reproducible results for different dates
|
74
|
+
- Added test coverage for seed behavior with different inputs
|
75
|
+
|
76
|
+
## [0.4.0] - 2024-02-21
|
77
|
+
|
78
|
+
### Added
|
79
|
+
- NumberObfuscator class for handling numeric content
|
80
|
+
- Support for Cyrillic alphabet in mixed content with numbers
|
81
|
+
- Format preservation for decimal and thousand separators
|
82
|
+
- Leading zeros handling with configuration option
|
83
|
+
- UTF-8 encoding support for mixed content
|
84
|
+
- Improved number format preservation
|
85
|
+
|
86
|
+
### Fixed
|
87
|
+
- Float decimal places preservation
|
88
|
+
- Character range issues with Cyrillic letters
|
89
|
+
- Length preservation in formatted numbers
|
90
|
+
- Mixed content handling with Unicode properties
|
91
|
+
|
92
|
+
### Changed
|
93
|
+
- Enhanced documentation with bilingual number obfuscation examples
|
94
|
+
- More robust number format preservation
|
95
|
+
- Improved mixed content handling
|
96
|
+
|
7
97
|
## [0.3.2] - 2025-02-07
|
8
98
|
### Added
|
9
99
|
- Test coverage for the Naturalizer class core functionality
|
data/README.md
CHANGED
@@ -6,15 +6,18 @@
|
|
6
6
|
|
7
7
|
## Русский
|
8
8
|
|
9
|
-
Ruby-гем для обфускации
|
10
|
-
|
9
|
+
Ruby-гем для обфускации данных с сохранением их структуры и формата. Поддерживает:
|
10
|
+
- Текст на русском и английском языках
|
11
|
+
- Числа (включая IP-адреса и смешанный контент)
|
12
|
+
- Даты в различных форматах
|
13
|
+
- Воспроизводимые результаты через seed
|
11
14
|
|
12
15
|
### Установка
|
13
16
|
|
14
17
|
Добавьте эту строку в Gemfile вашего приложения:
|
15
18
|
|
16
19
|
```ruby
|
17
|
-
gem 'obfuscator-rb'
|
20
|
+
gem 'obfuscator-rb'
|
18
21
|
```
|
19
22
|
|
20
23
|
И выполните:
|
@@ -29,23 +32,53 @@ $ bundle install
|
|
29
32
|
$ gem install obfuscator-rb
|
30
33
|
```
|
31
34
|
|
35
|
+
Установка из репозитория [hub.mos.ru](https://hub.mos.ru/ad/obfuscator):
|
36
|
+
|
37
|
+
```bash
|
38
|
+
$ gem install obfuscator-rb --source https://hub.mos.ru/ad/obfuscator.git
|
39
|
+
```
|
40
|
+
|
41
|
+
Код также доступен на [GitHub](https://github.com/amdest/obfuscator) и [Gitverse](https://gitverse.ru/ad-it/obfuscator).
|
42
|
+
|
32
43
|
### Возможности
|
33
44
|
|
34
|
-
|
35
|
-
- Сохраняет
|
36
|
-
-
|
37
|
-
-
|
38
|
-
-
|
39
|
-
-
|
40
|
-
-
|
41
|
-
-
|
45
|
+
#### Обфускация чисел
|
46
|
+
- Сохраняет формат и структуру чисел (десятичные, тысячные разделители)
|
47
|
+
- Обрабатывает IP-подобные последовательности (например, "21.11.234.23")
|
48
|
+
- Поддерживает сохранение ведущих нулей (настраиваемо)
|
49
|
+
- Предоставляет режим для работы только с положительными числами
|
50
|
+
- Сохраняет точность десятичных дробей
|
51
|
+
- Обрабатывает смешанный контент с числами и текстом
|
52
|
+
- Поддерживает латинский и кириллический алфавиты
|
53
|
+
|
54
|
+
#### Обфускация дат
|
55
|
+
- Поддерживает множество форматов дат через пресеты (ISO, EU, US, русский)
|
56
|
+
- Сохраняет структуру и валидность формата
|
57
|
+
- Настраиваемые ограничения:
|
58
|
+
- Диапазон допустимых лет
|
59
|
+
- Сохранение месяца
|
60
|
+
- Сохранение дня недели
|
61
|
+
- Обрабатывает полный формат ISO с временной зоной
|
62
|
+
|
63
|
+
#### Общие возможности
|
64
|
+
- Детерминированный вывод с опциональным сидом
|
65
|
+
- Обфускация с сохранением формата
|
66
|
+
- Поддержка кодировки UTF-8
|
67
|
+
- Комплексная обработка ошибок
|
68
|
+
- Эффективное использование памяти
|
69
|
+
|
70
|
+
#### Многопоточность
|
71
|
+
Отдельные экземпляры обфускаторов не являются потокобезопасными. Для многопоточных операций:
|
72
|
+
- Создавайте отдельный экземпляр для каждого потока
|
73
|
+
- Не используйте один экземпляр в разных потоках
|
74
|
+
- Каждый экземпляр поддерживает свое собственное состояние RNG
|
42
75
|
|
43
76
|
### Использование
|
44
77
|
|
45
78
|
```ruby
|
46
79
|
require 'obfuscator-rb'
|
47
80
|
|
48
|
-
#
|
81
|
+
# Обфускация текста (режим :direct по умолчанию)
|
49
82
|
obfuscator = Obfuscator::Multilang.new
|
50
83
|
text = "Hello, Привет! This is a TEST текст."
|
51
84
|
result = obfuscator.obfuscate(text)
|
@@ -69,6 +102,51 @@ obfuscator = Obfuscator::Multilang.new(seed: 12345)
|
|
69
102
|
result = obfuscator.obfuscate(text)
|
70
103
|
# => Одинаковый ввод + одинаковый сид = одинаковый вывод
|
71
104
|
# => "Cumic, Фяцёне! Okac ub h POWO щюзёс."
|
105
|
+
|
106
|
+
# Обфускация чисел
|
107
|
+
obfuscator = Obfuscator::NumberObfuscator.new
|
108
|
+
obfuscator.obfuscate(123.45) # => 567.89
|
109
|
+
obfuscator.obfuscate("1 234,56") # => "5 678,91"
|
110
|
+
obfuscator.obfuscate("192.168.1.1") # => "234.567.8.9"
|
111
|
+
|
112
|
+
# С настройками
|
113
|
+
obfuscator = Obfuscator::NumberObfuscator.new(
|
114
|
+
preserve_leading_zeros: false, # Не сохранять ведущие нули
|
115
|
+
unsigned: true, # Убрать знаки минус
|
116
|
+
seed: 12345 # Для воспроизводимых результатов
|
117
|
+
)
|
118
|
+
obfuscator.obfuscate("0042") # => "7391"
|
119
|
+
obfuscator.obfuscate("-123") # => "456"
|
120
|
+
|
121
|
+
# Обфускация даты
|
122
|
+
obfuscator = Obfuscator::DateObfuscator.new
|
123
|
+
obfuscator.obfuscate('2024-03-15') # => "2025-08-23"
|
124
|
+
|
125
|
+
# Разные форматы дат
|
126
|
+
eu = Obfuscator::DateObfuscator.new(format: :eu) # Европейский формат
|
127
|
+
eu.obfuscate('15.03.2024') # => "23.08.2025"
|
128
|
+
|
129
|
+
us = Obfuscator::DateObfuscator.new(format: :us) # Американский формат
|
130
|
+
us.obfuscate('03/15/2024') # => "08/23/2025"
|
131
|
+
|
132
|
+
rus_short = Obfuscator::DateObfuscator.new(format: :rus_short) # Короткий формат
|
133
|
+
rus_short.obfuscate('15.03.24') # => "23.08.25"
|
134
|
+
|
135
|
+
# С ограничениями
|
136
|
+
constrained = Obfuscator::DateObfuscator.new(
|
137
|
+
format: :rus,
|
138
|
+
constraints: {
|
139
|
+
min_year: 2020, # Не генерировать даты раньше 2020
|
140
|
+
max_year: 2025, # Не генерировать даты позже 2025
|
141
|
+
preserve_month: true, # Сохранять месяц
|
142
|
+
preserve_weekday: true # Сохранять день недели
|
143
|
+
}
|
144
|
+
)
|
145
|
+
constrained.obfuscate('15.03.2024') # => "14.03.2025" (тот же месяц, тот же день недели)
|
146
|
+
|
147
|
+
# С сидом для воспроизводимых результатов
|
148
|
+
seeded = Obfuscator::DateObfuscator.new(format: :rus, seed: 12345)
|
149
|
+
seeded.obfuscate('15.03.2024') # => "21.07.2025" (постоянный результат)
|
72
150
|
```
|
73
151
|
|
74
152
|
### Доступные режимы
|
@@ -77,30 +155,24 @@ result = obfuscator.obfuscate(text)
|
|
77
155
|
- `:eng_to_eng` - только английский в английский
|
78
156
|
- `:rus_to_rus` - только русский в русский
|
79
157
|
- `:swapped` - английский в русский и наоборот
|
80
|
-
- `:mixed` - использует оба алфавита
|
81
|
-
|
82
|
-
### Обработка входных данных
|
83
|
-
|
84
|
-
Обфускатор обрабатывает разлличные типы данных:
|
158
|
+
- `:mixed` - использует оба алфавита
|
85
159
|
|
86
|
-
|
87
|
-
- Числа → возвращаются без изменений
|
88
|
-
- Объекты с методом `to_s` → обрабатываются нормально
|
89
|
-
- Объекты без базовых методов Ruby → вызывают `InputError`
|
90
|
-
- Неверные кодировки → вызывают `EncodingError`
|
160
|
+
### Доступные пресеты для даты
|
91
161
|
|
92
|
-
|
162
|
+
- `:eu` => `'%d.%m.%Y'` - европейский формат (31.12.2023)
|
163
|
+
- `:eu_short` => `'%d.%m.%y'` - короткий европейский формат (31.12.23)
|
164
|
+
- `:rus` => `'%d.%m.%Y'` - русский формат (31.12.2023)
|
165
|
+
- `:rus_short` => `'%d.%m.%y'` - короткий русский формат (31.12.23)
|
166
|
+
- `:iso` => `'%Y-%m-%d'` - формат ISO (2023-12-31)
|
167
|
+
- `:us` => `'%m/%d/%Y'` - американский формат (12/31/2023)
|
168
|
+
- `:us_short` => `'%m/%d/%y'` - короткий американский формат (12/31/23)
|
169
|
+
- `:iso_full` => `'%Y-%m-%dT%H:%M:%S%z'` - полный формат ISO (2023-12-31T00:00:00+00:00)
|
93
170
|
|
94
|
-
|
95
|
-
- `Obfuscator::InputError` - Возникает при неверном типе входных данных
|
96
|
-
- `Obfuscator::EncodingError` - Возникает при проблемах с кодировкой
|
97
|
-
|
98
|
-
#### Пример использования с обработкой ошибок
|
171
|
+
### Обработка ошибок
|
99
172
|
|
100
173
|
```ruby
|
101
|
-
|
102
174
|
begin
|
103
|
-
obfuscator.obfuscate(
|
175
|
+
obfuscator.obfuscate(text)
|
104
176
|
rescue Obfuscator::InputError => e
|
105
177
|
# Обработка неверного типа входных данных
|
106
178
|
puts "Неверный тип данных: #{e.message}"
|
@@ -115,15 +187,18 @@ end
|
|
115
187
|
|
116
188
|
## English
|
117
189
|
|
118
|
-
A Ruby gem for
|
119
|
-
|
190
|
+
A Ruby gem for data obfuscation that preserves structure and format. Supports:
|
191
|
+
- Text in both English and Russian
|
192
|
+
- Numbers (including IP addresses and mixed content)
|
193
|
+
- Dates in various formats
|
194
|
+
- Reproducible results via seeding
|
120
195
|
|
121
196
|
### Installation
|
122
197
|
|
123
198
|
Add this line to your application's Gemfile:
|
124
199
|
|
125
200
|
```ruby
|
126
|
-
gem 'obfuscator-rb'
|
201
|
+
gem 'obfuscator-rb'
|
127
202
|
```
|
128
203
|
|
129
204
|
And then execute:
|
@@ -138,23 +213,53 @@ Or install it yourself as:
|
|
138
213
|
$ gem install obfuscator-rb
|
139
214
|
```
|
140
215
|
|
216
|
+
Install from [hub.mos.ru](https://hub.mos.ru/ad/obfuscator):
|
217
|
+
|
218
|
+
```bash
|
219
|
+
$ gem install obfuscator-rb --source https://hub.mos.ru/ad/obfuscator.git
|
220
|
+
```
|
221
|
+
|
222
|
+
The code is also available on [GitHub](https://github.com/amdest/obfuscator) and [Gitverse](https://gitverse.ru/ad-it/obfuscator).
|
223
|
+
|
141
224
|
### Features
|
142
225
|
|
143
|
-
|
144
|
-
-
|
145
|
-
-
|
146
|
-
-
|
147
|
-
-
|
148
|
-
-
|
149
|
-
-
|
150
|
-
-
|
226
|
+
#### Number Obfuscation
|
227
|
+
- Preserves number format and structure (decimals, thousand separators)
|
228
|
+
- Handles IP-like sequences (e.g., "21.11.234.23")
|
229
|
+
- Supports leading zeros preservation (configurable)
|
230
|
+
- Provides unsigned mode for positive-only output
|
231
|
+
- Maintains consistent decimal precision
|
232
|
+
- Processes mixed content with numbers and text
|
233
|
+
- Supports both Latin and Cyrillic alphabets
|
234
|
+
|
235
|
+
#### Date Obfuscation
|
236
|
+
- Supports multiple date formats through presets (ISO, EU, US, Russian)
|
237
|
+
- Preserves format structure and validity
|
238
|
+
- Configurable constraints:
|
239
|
+
- Year range limits
|
240
|
+
- Month preservation
|
241
|
+
- Weekday preservation
|
242
|
+
- Handles full ISO datetime format with timezone
|
243
|
+
|
244
|
+
#### General Features
|
245
|
+
- Deterministic output with optional seeding
|
246
|
+
- Format-preserving obfuscation
|
247
|
+
- UTF-8 encoding support
|
248
|
+
- Comprehensive error handling
|
249
|
+
- Memory-efficient processing
|
250
|
+
|
251
|
+
#### Thread Safety
|
252
|
+
Individual obfuscator instances are NOT thread-safe. For concurrent operations:
|
253
|
+
- Create separate instances per thread
|
254
|
+
- Do not share instances across threads
|
255
|
+
- Each instance maintains its own RNG state
|
151
256
|
|
152
257
|
### Usage
|
153
258
|
|
154
259
|
```ruby
|
155
260
|
require 'obfuscator-rb'
|
156
261
|
|
157
|
-
#
|
262
|
+
# Text obfuscation (default :direct mode)
|
158
263
|
obfuscator = Obfuscator::Multilang.new
|
159
264
|
text = "Hello, Привет! This is a TEST текст."
|
160
265
|
result = obfuscator.obfuscate(text)
|
@@ -178,6 +283,51 @@ obfuscator = Obfuscator::Multilang.new(seed: 12345)
|
|
178
283
|
result = obfuscator.obfuscate(text)
|
179
284
|
# => Same input + same seed = same output
|
180
285
|
# => "Cumic, Фяцёне! Okac ub h POWO щюзёс."
|
286
|
+
|
287
|
+
# Number obfuscation
|
288
|
+
obfuscator = Obfuscator::NumberObfuscator.new
|
289
|
+
obfuscator.obfuscate(123.45) # => 567.89
|
290
|
+
obfuscator.obfuscate("1,234.56") # => "5,678.91"
|
291
|
+
obfuscator.obfuscate("192.168.1.1") # => "234.567.8.9"
|
292
|
+
|
293
|
+
# With configuration
|
294
|
+
obfuscator = Obfuscator::NumberObfuscator.new(
|
295
|
+
preserve_leading_zeros: false, # Don't keep leading zeros
|
296
|
+
unsigned: true, # Remove minus signs
|
297
|
+
seed: 12345 # For reproducible results
|
298
|
+
)
|
299
|
+
obfuscator.obfuscate("0042") # => "7391"
|
300
|
+
obfuscator.obfuscate("-123") # => "456"
|
301
|
+
|
302
|
+
# Date obfuscation
|
303
|
+
obfuscator = Obfuscator::DateObfuscator.new
|
304
|
+
obfuscator.obfuscate('2024-03-15') # => "2025-08-23"
|
305
|
+
|
306
|
+
# Different date formats
|
307
|
+
eu = Obfuscator::DateObfuscator.new(format: :eu) # European format
|
308
|
+
eu.obfuscate('15.03.2024') # => "23.08.2025"
|
309
|
+
|
310
|
+
us = Obfuscator::DateObfuscator.new(format: :us) # US format
|
311
|
+
us.obfuscate('03/15/2024') # => "08/23/2025"
|
312
|
+
|
313
|
+
rus_short = Obfuscator::DateObfuscator.new(format: :rus_short) # Short format
|
314
|
+
rus_short.obfuscate('15.03.24') # => "23.08.25"
|
315
|
+
|
316
|
+
# With constraints
|
317
|
+
constrained = Obfuscator::DateObfuscator.new(
|
318
|
+
format: :iso,
|
319
|
+
constraints: {
|
320
|
+
min_year: 2020, # Don't generate dates before 2020
|
321
|
+
max_year: 2025, # Don't generate dates after 2025
|
322
|
+
preserve_month: true, # Keep the same month
|
323
|
+
preserve_weekday: true # Keep the same day of week
|
324
|
+
}
|
325
|
+
)
|
326
|
+
constrained.obfuscate('2024-03-15') # => "2025-03-14" (same month, same weekday)
|
327
|
+
|
328
|
+
# With seed for reproducible results
|
329
|
+
seeded = Obfuscator::DateObfuscator.new(seed: 12345)
|
330
|
+
seeded.obfuscate('2024-03-15') # => "2025-07-21" (consistent output)
|
181
331
|
```
|
182
332
|
|
183
333
|
### Available Modes
|
@@ -186,39 +336,33 @@ result = obfuscator.obfuscate(text)
|
|
186
336
|
- `:eng_to_eng` - English to English only
|
187
337
|
- `:rus_to_rus` - Russian to Russian only
|
188
338
|
- `:swapped` - English to Russian and vice versa
|
189
|
-
- `:mixed` - uses both alphabets
|
190
|
-
|
191
|
-
### Input Handling
|
339
|
+
- `:mixed` - uses both alphabets
|
192
340
|
|
193
|
-
|
341
|
+
### Available Date Presets
|
194
342
|
|
195
|
-
- `
|
196
|
-
-
|
197
|
-
-
|
198
|
-
-
|
199
|
-
-
|
343
|
+
- `:eu` => `'%d.%m.%Y'` - European format (31.12.2023)
|
344
|
+
- `:eu_short` => `'%d.%m.%y'` - Short European format (31.12.23)
|
345
|
+
- `:rus` => `'%d.%m.%Y'` - Russian format (31.12.2023)
|
346
|
+
- `:rus_short` => `'%d.%m.%y'` - Short Russian format (31.12.23)
|
347
|
+
- `:iso` => `'%Y-%m-%d'` - ISO format (2023-12-31)
|
348
|
+
- `:us` => `'%m/%d/%Y'` - US format (12/31/2023)
|
349
|
+
- `:us_short` => `'%m/%d/%y'` - Short US format (12/31/23)
|
350
|
+
- `:iso_full` => `'%Y-%m-%dT%H:%M:%S%z'` - Full ISO format (2023-12-31T00:00:00+00:00)
|
200
351
|
|
201
|
-
|
202
|
-
|
203
|
-
- `Obfuscator::Error` - Base error class for the gem
|
204
|
-
- `Obfuscator::InputError` - Raised for invalid input types
|
205
|
-
- `Obfuscator::EncodingError` - Raised for encoding-related issues
|
206
|
-
|
207
|
-
#### Example Usage with Error Handling
|
352
|
+
### Error Handling
|
208
353
|
|
209
354
|
```ruby
|
210
|
-
|
211
355
|
begin
|
212
356
|
obfuscator.obfuscate(text)
|
213
357
|
rescue Obfuscator::InputError => e
|
214
358
|
# Handle invalid input types
|
215
|
-
puts "Invalid input: #{e.message}"
|
359
|
+
puts "Invalid input type: #{e.message}"
|
216
360
|
rescue Obfuscator::EncodingError => e
|
217
361
|
# Handle encoding issues
|
218
362
|
puts "Encoding error: #{e.message}"
|
219
363
|
rescue Obfuscator::Error => e
|
220
364
|
# Handle other obfuscation errors
|
221
|
-
puts "Obfuscation
|
365
|
+
puts "Obfuscation error: #{e.message}"
|
222
366
|
end
|
223
367
|
```
|
224
368
|
|
@@ -0,0 +1,192 @@
|
|
1
|
+
# Release Checklist
|
2
|
+
|
3
|
+
## Initial Setup
|
4
|
+
|
5
|
+
### RubyGems Configuration
|
6
|
+
|
7
|
+
#### Option 1: Credentials File (Recommended)
|
8
|
+
1. Create/edit `~/.gem/credentials`:
|
9
|
+
```yaml
|
10
|
+
---
|
11
|
+
:rubygems_api_key: your_api_key_here
|
12
|
+
```
|
13
|
+
2. Set proper permissions:
|
14
|
+
```bash
|
15
|
+
chmod 600 ~/.gem/credentials
|
16
|
+
```
|
17
|
+
|
18
|
+
#### Option 2: Environment Variable
|
19
|
+
Set `RUBYGEMS_API_KEY` environment variable:
|
20
|
+
```bash
|
21
|
+
export RUBYGEMS_API_KEY=your_api_key_here
|
22
|
+
```
|
23
|
+
|
24
|
+
### First-time Push with MFA
|
25
|
+
1. Get your MFA code ready
|
26
|
+
2. Push the gem:
|
27
|
+
```bash
|
28
|
+
gem push pkg/obfuscator-rb-X.Y.Z.gem --otp YOUR_MFA_CODE
|
29
|
+
```
|
30
|
+
|
31
|
+
### Common RubyGems CLI commands
|
32
|
+
```bash
|
33
|
+
# Build gem with the gemspec file
|
34
|
+
gem build obfuscator-rb.gemspec # output: obfuscator-rb-X.Y.Z.gem
|
35
|
+
|
36
|
+
# Build gem with the Rakefile
|
37
|
+
rake build # output: pkg/obfuscator-rb-X.Y.Z.gem
|
38
|
+
|
39
|
+
# Push a new version of the gem
|
40
|
+
gem push pkg/obfuscator-rb-X.Y.Z.gem --otp YOUR_MFA_CODE
|
41
|
+
|
42
|
+
# Yank a version of the gem
|
43
|
+
gem yank obfuscator-rb -v X.Y.Z --otp YOUR_MFA_CODE
|
44
|
+
|
45
|
+
# List owned gems
|
46
|
+
gem list -r obfuscator-rb
|
47
|
+
|
48
|
+
# Check gem details
|
49
|
+
gem info obfuscator-rb
|
50
|
+
|
51
|
+
# Install a specific version of the gem locally
|
52
|
+
gem install ./obfuscator-rb-X.Y.Z.gem
|
53
|
+
```
|
54
|
+
|
55
|
+
## Pre-release Checks
|
56
|
+
|
57
|
+
### 1. Code Quality
|
58
|
+
- [ ] Run full test suite:
|
59
|
+
```bash
|
60
|
+
bundle exec rake test
|
61
|
+
```
|
62
|
+
- [ ] Run Rubocop:
|
63
|
+
```bash
|
64
|
+
bundle exec rubocop
|
65
|
+
```
|
66
|
+
|
67
|
+
### 2. Version Update
|
68
|
+
- [ ] Update version in `lib/obfuscator/version.rb`
|
69
|
+
- [ ] Update CHANGELOG.md
|
70
|
+
- Add new version section
|
71
|
+
- Document all changes under appropriate categories
|
72
|
+
- Add release date
|
73
|
+
|
74
|
+
### 3. Local Build & Test
|
75
|
+
- [ ] Build gem using Rake (preferred):
|
76
|
+
```bash
|
77
|
+
bundle exec rake build
|
78
|
+
# Gem will be in pkg/obfuscator-rb-X.Y.Z.gem
|
79
|
+
```
|
80
|
+
OR build directly:
|
81
|
+
```bash
|
82
|
+
gem build obfuscator-rb.gemspec
|
83
|
+
```
|
84
|
+
- [ ] Install and test locally:
|
85
|
+
```bash
|
86
|
+
gem install pkg/obfuscator-rb-X.Y.Z.gem
|
87
|
+
# OR if built directly:
|
88
|
+
# gem install ./obfuscator-rb-X.Y.Z.gem
|
89
|
+
```
|
90
|
+
- [ ] Verify in IRB:
|
91
|
+
```ruby
|
92
|
+
require 'obfuscator-rb'
|
93
|
+
# Test basic functionality
|
94
|
+
```
|
95
|
+
|
96
|
+
### 4. Documentation Check
|
97
|
+
- [ ] Review gemspec
|
98
|
+
- Version number
|
99
|
+
- Dependencies
|
100
|
+
- Metadata
|
101
|
+
- [ ] Check README is up to date
|
102
|
+
- [ ] Verify YARD documentation if changed
|
103
|
+
|
104
|
+
## Release Process
|
105
|
+
|
106
|
+
### 5. Version Control
|
107
|
+
- [ ] Commit changes:
|
108
|
+
```bash
|
109
|
+
git add .
|
110
|
+
git commit -m "feat: release X.Y.Z"
|
111
|
+
```
|
112
|
+
- [ ] Create version tag:
|
113
|
+
```bash
|
114
|
+
git tag -a vX.Y.Z -m "Release X.Y.Z"
|
115
|
+
```
|
116
|
+
- [ ] Push changes and tags:
|
117
|
+
```bash
|
118
|
+
git push origin master
|
119
|
+
git push origin vX.Y.Z
|
120
|
+
```
|
121
|
+
|
122
|
+
### 6. Publish
|
123
|
+
- [ ] Push to RubyGems:
|
124
|
+
```bash
|
125
|
+
gem push pkg/obfuscator-rb-X.Y.Z.gem --otp YOUR_MFA_CODE
|
126
|
+
```
|
127
|
+
- [ ] Verify gem page on RubyGems.org
|
128
|
+
|
129
|
+
## Post-release
|
130
|
+
- [ ] Clean up local gem files:
|
131
|
+
```bash
|
132
|
+
rm -rf pkg/
|
133
|
+
# OR if built directly:
|
134
|
+
# rm obfuscator-rb-*.gem
|
135
|
+
```
|
136
|
+
- [ ] Announce release if significant
|
137
|
+
- [ ] Update any related documentation or wiki pages
|
138
|
+
|
139
|
+
# Git Workflow Guide
|
140
|
+
|
141
|
+
## Branch Structure
|
142
|
+
- `master`: Release branch, contains only tagged releases
|
143
|
+
- `dev`: Main development branch (local only)
|
144
|
+
- Feature branches: Created from and merged back to `dev`
|
145
|
+
|
146
|
+
## Regular Development Flow
|
147
|
+
1. Start work from dev:
|
148
|
+
```bash
|
149
|
+
git checkout dev
|
150
|
+
git pull origin master # sync with latest release if needed
|
151
|
+
git checkout -b feat/my-feature
|
152
|
+
```
|
153
|
+
|
154
|
+
2. Make changes and commit:
|
155
|
+
```bash
|
156
|
+
git add .
|
157
|
+
git commit -m "feat: description"
|
158
|
+
```
|
159
|
+
|
160
|
+
3. Merge back to dev:
|
161
|
+
```bash
|
162
|
+
git checkout dev
|
163
|
+
git merge feat/my-feature
|
164
|
+
git branch -d feat/my-feature
|
165
|
+
```
|
166
|
+
|
167
|
+
## Release Process
|
168
|
+
1. Prepare release in dev:
|
169
|
+
```bash
|
170
|
+
git checkout dev
|
171
|
+
# Update version and CHANGELOG
|
172
|
+
git commit -m "chore: prepare release X.Y.Z"
|
173
|
+
```
|
174
|
+
|
175
|
+
2. Create release:
|
176
|
+
```bash
|
177
|
+
git checkout master
|
178
|
+
git merge --squash dev # squash all dev commits
|
179
|
+
git commit -m "release: version X.Y.Z"
|
180
|
+
git tag -a vX.Y.Z -m "version X.Y.Z"
|
181
|
+
```
|
182
|
+
|
183
|
+
3. Push release:
|
184
|
+
```bash
|
185
|
+
git push origin master --tags
|
186
|
+
```
|
187
|
+
|
188
|
+
4. Update dev:
|
189
|
+
```bash
|
190
|
+
git checkout dev
|
191
|
+
git merge master # keep dev in sync with latest release
|
192
|
+
```
|
@@ -38,6 +38,9 @@ module Obfuscator
|
|
38
38
|
# - :rus => '%d.%m.%Y' # 31.12.2023
|
39
39
|
# - :rus_short => '%d.%m.%y' # 31.12.23
|
40
40
|
# - :iso => '%Y-%m-%d' # 2023-12-31
|
41
|
+
# - :us => '%m/%d/%Y' # 12/31/2023
|
42
|
+
# - :us_short => '%m/%d/%y' # 12/31/23
|
43
|
+
# - :iso_full => '%Y-%m-%dT%H:%M:%S%z' # 2023-12-31T00:00:00+00:00
|
41
44
|
#
|
42
45
|
# @param format [Symbol, String] Preset format name or custom format string (default: :iso)
|
43
46
|
# @param seed [Integer, nil] Optional seed for reproducible results
|
@@ -56,7 +59,10 @@ module Obfuscator
|
|
56
59
|
eu_short: '%d.%m.%y', # 31.12.23
|
57
60
|
rus: '%d.%m.%Y', # 31.12.2023
|
58
61
|
rus_short: '%d.%m.%y', # 31.12.23
|
59
|
-
iso: '%Y-%m-%d' # 2023-12-31
|
62
|
+
iso: '%Y-%m-%d', # 2023-12-31
|
63
|
+
us: '%m/%d/%Y', # 12/31/2023
|
64
|
+
us_short: '%m/%d/%y', # 12/31/23
|
65
|
+
iso_full: '%Y-%m-%dT%H:%M:%S%z' # 2023-12-31T00:00:00+00:00
|
60
66
|
}.freeze
|
61
67
|
|
62
68
|
def initialize(format: :iso, seed: nil, constraints: {})
|
@@ -67,13 +73,17 @@ module Obfuscator
|
|
67
73
|
end
|
68
74
|
|
69
75
|
def obfuscate(date_string)
|
70
|
-
|
71
|
-
setup_rng(@seed) if @seed
|
72
|
-
|
73
|
-
return date_string if date_string.nil? || date_string.empty?
|
76
|
+
return date_string if !date_string.is_a?(Date) && (date_string.nil? || date_string.empty?)
|
74
77
|
|
75
78
|
begin
|
76
|
-
date = ::Date.strptime(date_string, @format)
|
79
|
+
date = date_string.is_a?(Date) ? date_string : ::Date.strptime(date_string, @format)
|
80
|
+
|
81
|
+
# Create a unique seed based on both the original seed and input date
|
82
|
+
if @seed
|
83
|
+
combined_seed = [@seed, date.to_time.to_i].hash
|
84
|
+
setup_rng(combined_seed)
|
85
|
+
end
|
86
|
+
|
77
87
|
obfuscated_date = generate_date(date)
|
78
88
|
obfuscated_date.strftime(@format)
|
79
89
|
rescue ArgumentError => e
|
@@ -32,6 +32,9 @@ module Obfuscator
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def random_sample(array)
|
35
|
+
raise ArgumentError, "Expected Array, got #{array.class}" unless array.is_a?(Array)
|
36
|
+
raise ArgumentError, 'Array cannot be empty' if array.empty?
|
37
|
+
|
35
38
|
array.sample(random: @rng)
|
36
39
|
end
|
37
40
|
|
@@ -40,6 +43,9 @@ module Obfuscator
|
|
40
43
|
end
|
41
44
|
|
42
45
|
def random_range(range)
|
46
|
+
raise ArgumentError, "Expected Range, got #{range.class}" unless range.is_a?(Range)
|
47
|
+
raise ArgumentError, 'Range cannot be infinite' if range.end.nil?
|
48
|
+
|
43
49
|
@rng.rand(range)
|
44
50
|
end
|
45
51
|
end
|
@@ -0,0 +1,323 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Obfuscator
|
4
|
+
# Class for obfuscating numbers while preserving their format and structure.
|
5
|
+
# Handles pure numbers (integers, floats), formatted strings, and mixed content
|
6
|
+
# including multilingual text (Latin and Cyrillic alphabets).
|
7
|
+
#
|
8
|
+
# @example Basic usage with numbers
|
9
|
+
# obfuscator = NumberObfuscator.new
|
10
|
+
# obfuscator.obfuscate(123.45) # => 567.89
|
11
|
+
# obfuscator.obfuscate(-42) # => -73
|
12
|
+
#
|
13
|
+
# @example With string numbers and leading zeros
|
14
|
+
# obfuscator.obfuscate("0042") # => "0073"
|
15
|
+
# obfuscator.obfuscate("00123.40") # => "00567.80"
|
16
|
+
#
|
17
|
+
# @example With formatted numbers
|
18
|
+
# obfuscator.obfuscate("1 234,56") # => "5 678,91"
|
19
|
+
# obfuscator.obfuscate("1_000_000") # => "5_678_912"
|
20
|
+
#
|
21
|
+
# @example With mixed content (Latin and Cyrillic)
|
22
|
+
# obfuscator.obfuscate("ABC-42XY") # => "DEF-73ZW"
|
23
|
+
# obfuscator.obfuscate("АБВ-42ЩЮ") # => "ГДЕ-73ЖЗ"
|
24
|
+
# obfuscator.obfuscate("21.11.234.23") # => "65.87.891.45"
|
25
|
+
#
|
26
|
+
# @example With options
|
27
|
+
# # Don't preserve leading zeros
|
28
|
+
# obfuscator = NumberObfuscator.new(preserve_leading_zeros: false)
|
29
|
+
# obfuscator.obfuscate("0042") # => "7391"
|
30
|
+
#
|
31
|
+
# # Remove signs from negative numbers
|
32
|
+
# obfuscator = NumberObfuscator.new(unsigned: true)
|
33
|
+
# obfuscator.obfuscate("-123") # => "456"
|
34
|
+
#
|
35
|
+
# # Use seed for reproducible results
|
36
|
+
# obfuscator = NumberObfuscator.new(seed: 12345)
|
37
|
+
# obfuscator.obfuscate("123.45") # => Same result for same seed
|
38
|
+
#
|
39
|
+
# Features:
|
40
|
+
# - Preserves number format (decimal separators, thousand separators)
|
41
|
+
# - Maintains leading zeros (optional)
|
42
|
+
# - Handles mixed content with letters and numbers
|
43
|
+
# - Supports both Latin and Cyrillic alphabets
|
44
|
+
# - Provides reproducible results with seeds
|
45
|
+
# - UTF-8 encoding support
|
46
|
+
#
|
47
|
+
# @param preserve_leading_zeros [Boolean] Whether to keep leading zeros in string numbers (default: true)
|
48
|
+
# @param unsigned [Boolean] Whether to remove signs from negative numbers (default: false)
|
49
|
+
# @param seed [Integer, nil] Optional seed for reproducible results
|
50
|
+
#
|
51
|
+
# @raise [Error] If number obfuscation fails
|
52
|
+
# @raise [InputError] If input is neither Numeric nor String
|
53
|
+
#
|
54
|
+
# @note This method is optimized for single-use. For bulk operations,
|
55
|
+
# consider creating a single instance and reusing it.
|
56
|
+
#
|
57
|
+
# @note This class is not thread-safe. For concurrent usage,
|
58
|
+
# create separate instances per thread.
|
59
|
+
#
|
60
|
+
# @note Requires Ruby 3.0+ for pattern matching features
|
61
|
+
class NumberObfuscator
|
62
|
+
include Internal::RNG
|
63
|
+
|
64
|
+
FormatError = Class.new(Error)
|
65
|
+
RangeError = Class.new(Error)
|
66
|
+
|
67
|
+
# Consider memoizing character sets
|
68
|
+
UPPERCASE_CYRILLIC = ('А'..'Я').to_a - ['Ё']
|
69
|
+
LOWERCASE_CYRILLIC = ('а'..'я').to_a - ['ё']
|
70
|
+
|
71
|
+
def initialize(preserve_leading_zeros: true, unsigned: false, seed: nil)
|
72
|
+
@preserve_leading_zeros = preserve_leading_zeros
|
73
|
+
@unsigned = unsigned
|
74
|
+
@seed = seed # Store seed for reuse
|
75
|
+
setup_rng(seed)
|
76
|
+
end
|
77
|
+
|
78
|
+
def obfuscate(input)
|
79
|
+
return input if input.nil? || (input.is_a?(String) && input.empty?)
|
80
|
+
|
81
|
+
# Create a unique seed based on both the original seed and input
|
82
|
+
if @seed
|
83
|
+
combined_seed = [@seed, input.to_s].hash
|
84
|
+
setup_rng(combined_seed)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Handle unsigned option at the entry point
|
88
|
+
input = if @unsigned && input.is_a?(String)
|
89
|
+
input.gsub(/^-/, '')
|
90
|
+
elsif @unsigned && input.is_a?(Numeric)
|
91
|
+
input.abs
|
92
|
+
else
|
93
|
+
input
|
94
|
+
end
|
95
|
+
|
96
|
+
case input
|
97
|
+
when Numeric
|
98
|
+
obfuscate_numeric(input)
|
99
|
+
when String
|
100
|
+
obfuscate_string(input)
|
101
|
+
else
|
102
|
+
raise InputError, "Input must be Numeric or String, got: #{input.class}"
|
103
|
+
end
|
104
|
+
rescue InputError
|
105
|
+
raise
|
106
|
+
rescue StandardError => e
|
107
|
+
raise Error, "Number obfuscation error: #{e.message}"
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def preserve_format(original, new_number)
|
113
|
+
return new_number if original.nil?
|
114
|
+
return original if original.to_s.match?(/^-?0+\.?0*$/)
|
115
|
+
|
116
|
+
original_str = original.to_s
|
117
|
+
is_negative = original_str.start_with?('-') || new_number.negative?
|
118
|
+
new_str = new_number.abs.to_s
|
119
|
+
|
120
|
+
# Split into parts for IP-like numbers
|
121
|
+
if original_str.count('.') > 1
|
122
|
+
parts = original_str.gsub(/^-/, '').split('.')
|
123
|
+
new_parts = parts.map do |part|
|
124
|
+
part_len = part.length
|
125
|
+
new_part = new_number.abs.to_s[-part_len..]
|
126
|
+
new_part.rjust(part_len, '0')
|
127
|
+
end
|
128
|
+
new_str = new_parts.join('.')
|
129
|
+
else
|
130
|
+
# Handle decimal places
|
131
|
+
if original_str.include?('.') || original_str.include?(',')
|
132
|
+
separator = original_str.include?(',') ? ',' : '.'
|
133
|
+
decimal_places = original_str.gsub(/^-/, '').split(/[.,]/).last&.length || 0
|
134
|
+
new_str = format("%.#{decimal_places}f", new_number.abs).tr('.', separator)
|
135
|
+
end
|
136
|
+
|
137
|
+
# Handle leading zeros
|
138
|
+
if @preserve_leading_zeros
|
139
|
+
leading_zeros = original_str.gsub(/^-/, '').match(/^0+/)&.[](0)
|
140
|
+
new_str = "#{leading_zeros}#{new_str}" if leading_zeros
|
141
|
+
end
|
142
|
+
|
143
|
+
# Ensure exact length match
|
144
|
+
new_str = new_str.rjust(original_str.gsub(/^-/, '').length, '0')
|
145
|
+
end
|
146
|
+
|
147
|
+
# Add sign only if input was negative
|
148
|
+
is_negative ? "-#{new_str}" : new_str
|
149
|
+
end
|
150
|
+
|
151
|
+
def obfuscate_string(text)
|
152
|
+
return text if text.empty?
|
153
|
+
|
154
|
+
# Split into numbers and separators while preserving positions
|
155
|
+
parts = text.scan(/(\d+(?:\.\d+)?)|([^\d]+)/)
|
156
|
+
parts.flatten!
|
157
|
+
parts.compact!
|
158
|
+
|
159
|
+
# Generate base seed for the entire string
|
160
|
+
base_seed = if @seed
|
161
|
+
"#{@seed}:#{text}".hash
|
162
|
+
else
|
163
|
+
Random.new_seed
|
164
|
+
end
|
165
|
+
|
166
|
+
result = ''
|
167
|
+
numbers_count = parts.count { |p| p.match?(/\d/) }
|
168
|
+
number_index = 0
|
169
|
+
|
170
|
+
parts.each do |part|
|
171
|
+
result += if part.match?(/\d/)
|
172
|
+
position_seed = "#{base_seed}:#{number_index}:#{numbers_count}".hash
|
173
|
+
local_rng = Random.new(position_seed)
|
174
|
+
|
175
|
+
number_result = with_temporary_rng(local_rng) do
|
176
|
+
original_str = @unsigned ? part.gsub(/^-/, '') : part
|
177
|
+
original = if original_str.include?('.')
|
178
|
+
original_str.to_f
|
179
|
+
else
|
180
|
+
original_str.to_i
|
181
|
+
end
|
182
|
+
|
183
|
+
new_number = generate_similar_number(original)
|
184
|
+
preserve_format(original_str, new_number)
|
185
|
+
end
|
186
|
+
|
187
|
+
number_index += 1
|
188
|
+
number_result
|
189
|
+
else
|
190
|
+
part
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
result
|
195
|
+
end
|
196
|
+
|
197
|
+
def obfuscate_mixed_string(text)
|
198
|
+
# Split into tokens preserving all separators and non-numeric parts
|
199
|
+
# \p{L} matches any kind of letter from any language
|
200
|
+
# \p{N} matches any kind of numeric character in any script
|
201
|
+
tokens = text.split(/(\d+(?:\.\d+)?|\s+|[[:punct:]]|[\p{L}]+)/)
|
202
|
+
|
203
|
+
tokens.map do |token|
|
204
|
+
case token
|
205
|
+
when /^\d+(?:\.\d+)?$/ # Pure number
|
206
|
+
new_number = generate_similar_number(token.to_f)
|
207
|
+
preserve_format(token, new_number)
|
208
|
+
when /\p{L}+/ # Letters (any script)
|
209
|
+
obfuscate_letters(token)
|
210
|
+
else # Spaces and punctuation
|
211
|
+
token
|
212
|
+
end
|
213
|
+
end.join
|
214
|
+
end
|
215
|
+
|
216
|
+
def obfuscate_letters(text)
|
217
|
+
# Ensure UTF-8 encoding
|
218
|
+
text = text.encode('UTF-8') unless text.encoding == Encoding::UTF_8
|
219
|
+
|
220
|
+
text.chars.map do |char|
|
221
|
+
case char
|
222
|
+
when /[A-Z]/
|
223
|
+
(((char.ord - 'A'.ord + random_integer(1, 25)) % 26) + 'A'.ord).chr
|
224
|
+
when /[a-z]/
|
225
|
+
(((char.ord - 'a'.ord + random_integer(1, 25)) % 26) + 'a'.ord).chr
|
226
|
+
when /[А-Я]/
|
227
|
+
if char == 'Ё'
|
228
|
+
'Е'
|
229
|
+
else
|
230
|
+
random_sample(UPPERCASE_CYRILLIC)
|
231
|
+
end
|
232
|
+
when /[а-я]/
|
233
|
+
if char == 'ё'
|
234
|
+
'е'
|
235
|
+
else
|
236
|
+
random_sample(LOWERCASE_CYRILLIC)
|
237
|
+
end
|
238
|
+
else
|
239
|
+
char
|
240
|
+
end
|
241
|
+
end.join.force_encoding('UTF-8')
|
242
|
+
end
|
243
|
+
|
244
|
+
def obfuscate_numeric(number)
|
245
|
+
return number if number.zero?
|
246
|
+
|
247
|
+
case number
|
248
|
+
when Integer
|
249
|
+
# For integers, maintain the same number of digits
|
250
|
+
digits = number.abs.to_s.length
|
251
|
+
base = 10**(digits - 1)
|
252
|
+
new_number = ((random_probability * 9) + 1) * base
|
253
|
+
new_number = new_number.to_i
|
254
|
+
|
255
|
+
# Handle sign based on unsigned option
|
256
|
+
if @unsigned
|
257
|
+
new_number.abs
|
258
|
+
else
|
259
|
+
number.negative? ? -new_number.abs : new_number.abs
|
260
|
+
end
|
261
|
+
|
262
|
+
when Float, BigDecimal
|
263
|
+
# For floating point, maintain similar magnitude and precision
|
264
|
+
str_number = number.to_s
|
265
|
+
decimal_places = str_number.split('.')[1]&.length || 0
|
266
|
+
new_number = generate_similar_number(number)
|
267
|
+
|
268
|
+
# Ensure consistent decimal places
|
269
|
+
new_number = new_number.round(decimal_places) if decimal_places.positive?
|
270
|
+
|
271
|
+
# Handle sign based on unsigned option
|
272
|
+
if @unsigned
|
273
|
+
new_number.abs
|
274
|
+
else
|
275
|
+
number.negative? ? -new_number.abs : new_number.abs
|
276
|
+
end
|
277
|
+
|
278
|
+
else
|
279
|
+
raise InputError, "Unsupported numeric type: #{number.class}"
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def generate_similar_number(number)
|
284
|
+
return 0 if number.zero?
|
285
|
+
|
286
|
+
# Work with absolute value
|
287
|
+
abs_num = number.abs
|
288
|
+
str_num = abs_num.to_s
|
289
|
+
is_float = number.is_a?(Float) || str_num.include?('.')
|
290
|
+
num_digits = str_num.gsub(/[^\d]/, '').length
|
291
|
+
|
292
|
+
# Generate base number with correct digits
|
293
|
+
base = 10**(num_digits - 1)
|
294
|
+
max = (10**num_digits) - 1
|
295
|
+
new_number = base + (random_probability * (max - base)).to_i
|
296
|
+
|
297
|
+
# Handle float conversion and decimal places
|
298
|
+
if is_float
|
299
|
+
decimal_places = str_num.split('.')[1]&.length || 0
|
300
|
+
new_number = (new_number.to_f / (10**decimal_places)).round(decimal_places)
|
301
|
+
end
|
302
|
+
|
303
|
+
# Handle sign based on unsigned option
|
304
|
+
if @unsigned
|
305
|
+
new_number.abs
|
306
|
+
else
|
307
|
+
number.negative? ? -new_number.abs : new_number.abs
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def random_integer(min, max)
|
312
|
+
(random_probability * (max - min + 1)).to_i + min
|
313
|
+
end
|
314
|
+
|
315
|
+
def with_temporary_rng(temp_rng)
|
316
|
+
original_rng = @rng
|
317
|
+
@rng = temp_rng
|
318
|
+
yield
|
319
|
+
ensure
|
320
|
+
@rng = original_rng
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
data/lib/obfuscator/version.rb
CHANGED
data/lib/obfuscator-rb.rb
CHANGED
@@ -1,21 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Obfuscator is a text obfuscation library that preserves text structure while replacing content
|
4
|
-
# with meaningless but natural-looking words. It supports both English and Russian languages
|
4
|
+
# with meaningless but natural-looking words. It supports both English and Russian languages,
|
5
|
+
# as well as numbers and dates with format preservation.
|
5
6
|
#
|
6
|
-
# The gem provides
|
7
|
+
# The gem provides three main obfuscators:
|
7
8
|
# - {Multilang} for text obfuscation with multiple language support
|
9
|
+
# - {NumberObfuscator} for number and mixed content obfuscation
|
8
10
|
# - {DateObfuscator} for date obfuscation with format preservation
|
9
11
|
#
|
12
|
+
# THREAD SAFETY:
|
13
|
+
# Individual obfuscator instances are NOT thread-safe. For concurrent operations:
|
14
|
+
# - Create separate instances per thread
|
15
|
+
# - Do not share instances across threads
|
16
|
+
# - Each instance maintains its own RNG state
|
17
|
+
#
|
10
18
|
# @example Basic text obfuscation
|
11
19
|
# require 'obfuscator-rb'
|
12
20
|
#
|
13
21
|
# obfuscator = Obfuscator::Multilang.new
|
14
22
|
# obfuscator.obfuscate("Hello, World!") # => "Kites, Mefal!"
|
15
23
|
#
|
16
|
-
# @example
|
17
|
-
#
|
18
|
-
#
|
24
|
+
# @example Number obfuscation with format preservation
|
25
|
+
# num_obf = Obfuscator::NumberObfuscator.new
|
26
|
+
# num_obf.obfuscate(123.45) # => 567.89
|
27
|
+
# num_obf.obfuscate("1,234.56") # => "5,678.91"
|
28
|
+
# num_obf.obfuscate("192.168.1.1") # => "234.567.8.9"
|
29
|
+
# num_obf.obfuscate("ABC-42XY") # => "DEF-73ZW"
|
30
|
+
#
|
31
|
+
# @example Date obfuscation with constraints
|
32
|
+
# date_obf = Obfuscator::DateObfuscator.new(
|
33
|
+
# format: :iso,
|
34
|
+
# constraints: {
|
35
|
+
# min_year: 2020,
|
36
|
+
# max_year: 2025,
|
37
|
+
# preserve_month: true
|
38
|
+
# }
|
39
|
+
# )
|
40
|
+
# date_obf.obfuscate("2023-12-31") # => "2025-12-15"
|
41
|
+
#
|
42
|
+
# @example Error handling
|
43
|
+
# begin
|
44
|
+
# obfuscator.obfuscate(input)
|
45
|
+
# rescue Obfuscator::InputError => e
|
46
|
+
# # Handle invalid input types
|
47
|
+
# rescue Obfuscator::EncodingError => e
|
48
|
+
# # Handle encoding issues
|
49
|
+
# rescue Obfuscator::Error => e
|
50
|
+
# # Handle other obfuscation errors
|
51
|
+
# end
|
19
52
|
#
|
20
53
|
# Error handling is provided through specific error classes:
|
21
54
|
# - {Error} Base error class for the gem
|
@@ -23,6 +56,7 @@
|
|
23
56
|
# - {EncodingError} Raised for encoding-related issues
|
24
57
|
#
|
25
58
|
# @see Multilang For text obfuscation functionality
|
59
|
+
# @see NumberObfuscator For number and mixed content obfuscation
|
26
60
|
# @see DateObfuscator For date obfuscation functionality
|
27
61
|
# @see Internal::RNG For random number generation utilities
|
28
62
|
|
@@ -30,6 +64,23 @@ module Obfuscator
|
|
30
64
|
class Error < StandardError; end
|
31
65
|
class EncodingError < Error; end
|
32
66
|
class InputError < Error; end
|
67
|
+
|
68
|
+
# Base module for the Obfuscator gem.
|
69
|
+
#
|
70
|
+
# THREAD SAFETY:
|
71
|
+
# Individual obfuscator instances are NOT thread-safe. For concurrent operations:
|
72
|
+
# - Create separate instances per thread
|
73
|
+
# - Do not share instances across threads
|
74
|
+
# - Each instance maintains its own RNG state
|
75
|
+
#
|
76
|
+
# @example Thread-safe usage
|
77
|
+
# threads = 4.times.map do
|
78
|
+
# Thread.new do
|
79
|
+
# # Create a new instance for each thread
|
80
|
+
# obfuscator = Obfuscator::NumberObfuscator.new(seed: 12345)
|
81
|
+
# obfuscator.obfuscate("123.45")
|
82
|
+
# end
|
83
|
+
# end
|
33
84
|
end
|
34
85
|
|
35
86
|
require_relative 'obfuscator/version'
|
@@ -38,6 +89,7 @@ require_relative 'obfuscator/internal/rng'
|
|
38
89
|
require_relative 'obfuscator/naturalizer'
|
39
90
|
require_relative 'obfuscator/multilang'
|
40
91
|
require_relative 'obfuscator/date_obfuscator'
|
92
|
+
require_relative 'obfuscator/number_obfuscator'
|
41
93
|
|
42
94
|
# Usage example:
|
43
95
|
if __FILE__ == $PROGRAM_NAME
|
metadata
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obfuscator-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Aleksandr Dryzhuk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-02-
|
11
|
+
date: 2025-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
|
-
A Ruby
|
15
|
-
|
16
|
-
|
14
|
+
A Ruby library for data obfuscation that:
|
15
|
+
- Preserves original data format and structure as much as possible
|
16
|
+
- Supports numbers (including IP-like sequences), dates, and text
|
17
|
+
- Maintains text structure while replacing content with meaningless but natural-looking words in English and Russian
|
18
|
+
- Maintains data type consistency and decimal precision
|
19
|
+
- Offers seeded randomization for reproducible results
|
20
|
+
- Handles various number formats (leading zeros, separators)
|
21
|
+
- Provides configurable options (unsigned mode, format preservation)
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
натурализацию текста.
|
23
|
+
Note: Individual obfuscator instances are not thread-safe.
|
24
|
+
For concurrent operations, create separate instances per thread.
|
21
25
|
email:
|
22
26
|
- dev@ad-it.pro
|
23
27
|
executables: []
|
@@ -28,6 +32,7 @@ files:
|
|
28
32
|
- CHANGELOG.md
|
29
33
|
- LICENSE.txt
|
30
34
|
- README.md
|
35
|
+
- RELEASE_CHECKLIST.md
|
31
36
|
- Rakefile
|
32
37
|
- lib/obfuscator-rb.rb
|
33
38
|
- lib/obfuscator/constants.rb
|
@@ -35,6 +40,7 @@ files:
|
|
35
40
|
- lib/obfuscator/internal/rng.rb
|
36
41
|
- lib/obfuscator/multilang.rb
|
37
42
|
- lib/obfuscator/naturalizer.rb
|
43
|
+
- lib/obfuscator/number_obfuscator.rb
|
38
44
|
- lib/obfuscator/version.rb
|
39
45
|
homepage: https://hub.mos.ru/ad/obfuscator
|
40
46
|
licenses:
|
@@ -62,6 +68,5 @@ requirements: []
|
|
62
68
|
rubygems_version: 3.5.22
|
63
69
|
signing_key:
|
64
70
|
specification_version: 4
|
65
|
-
summary:
|
66
|
-
and Russian languages
|
71
|
+
summary: A robust data obfuscator for numbers, dates, and text with format preservation
|
67
72
|
test_files: []
|