package ru.pdguard.mask; import ru.pdguard.detect.RuleRegistry; /** * Правдоподобные подставные значения вместо настоящих. * *

Модель получает текст, который выглядит естественно, и качество ответа * страдает меньше, чем от звёздочек. Значения детерминированы: одно и то же * исходное значение всегда даёт одну и ту же подстановку. */ final class Synthetic { private static final String[] SURNAMES = {"Лаврентьев", "Мещеряков", "Тихомиров", "Ясенев", "Бурмистров", "Кольцов"}; private static final String[] NAMES = {"Артём", "Никита", "Глеб", "Тимур", "Марк", "Лев"}; private static final String[] PATRONYMICS = {"Артёмович", "Никитич", "Глебович", "Тимурович", "Маркович", "Львович"}; private static final String[] DOMAINS = {"example.com", "example.org", "example.net"}; private Synthetic() { } static String forType(String type, String value, int ordinal) { int seed = Math.abs(value.hashCode()); return switch (type) { case RuleRegistry.FIO -> pick(SURNAMES, seed) + " " + pick(NAMES, seed >> 3) + " " + pick(PATRONYMICS, seed >> 6); case RuleRegistry.CARDHOLDER -> "IVAN PETROV"; case RuleRegistry.EMAIL -> "user" + ordinal + "@" + pick(DOMAINS, seed); case RuleRegistry.PHONE -> "+7 9" + digits(seed, 2) + " " + digits(seed >> 4, 3) + "-" + digits(seed >> 8, 2) + "-" + digits(seed >> 12, 2); case RuleRegistry.CARD -> luhnCard(seed); case RuleRegistry.PASSPORT, RuleRegistry.DRIVER_LICENSE, RuleRegistry.FOREIGN_PASSPORT, RuleRegistry.MILITARY_ID -> digits(seed, 4) + " " + digits(seed >> 6, 6); case RuleRegistry.INN -> digits(seed, 12); case RuleRegistry.MEDICAL_POLICY -> digits(seed, 16); case RuleRegistry.SNILS -> digits(seed, 3) + "-" + digits(seed >> 4, 3) + "-" + digits(seed >> 8, 3) + " " + digits(seed >> 12, 2); case RuleRegistry.BIRTH_DATE, RuleRegistry.PASSPORT_DATE, RuleRegistry.DATE -> syntheticDate(seed); case RuleRegistry.ADDRESS_CITY -> "Зареченск"; case RuleRegistry.ADDRESS_STREET -> "Сосновая"; case RuleRegistry.ADDRESS_HOUSE -> String.valueOf(1 + Math.floorMod(seed, 90)); case RuleRegistry.ADDRESS_FLAT -> String.valueOf(1 + Math.floorMod(seed, 200)); case RuleRegistry.ADDRESS_POSTCODE -> digits(seed, 6); case RuleRegistry.ADDRESS_COUNTRY -> "Заречье"; case RuleRegistry.CVV -> digits(seed, 3); case RuleRegistry.PIN -> digits(seed, 4); // Для остальных типов правдоподобной замены нет — отдаём токен. default -> "[" + type + "_" + ordinal + "]"; }; } private static String pick(String[] options, int seed) { return options[Math.floorMod(seed, options.length)]; } private static String syntheticDate(int seed) { int day = 1 + Math.floorMod(seed, 28); int month = 1 + Math.floorMod(seed >> 5, 12); int year = 1960 + Math.floorMod(seed >> 9, 45); return String.format("%02d.%02d.%d", day, month, year); } private static String digits(int seed, int count) { StringBuilder sb = new StringBuilder(count); int value = Math.abs(seed); for (int i = 0; i < count; i++) { sb.append((char) ('0' + Math.floorMod(value, 10))); value = value / 10 + (i + 1) * 7; } return sb.toString(); } /** Номер карты, проходящий проверку алгоритмом Луна: подстановка должна выглядеть настоящей. */ private static String luhnCard(int seed) { StringBuilder body = new StringBuilder("4").append(digits(seed, 14)); int sum = 0; boolean doubled = true; for (int i = body.length() - 1; i >= 0; i--) { int d = body.charAt(i) - '0'; if (doubled) { d *= 2; if (d > 9) { d -= 9; } } sum += d; doubled = !doubled; } body.append((10 - sum % 10) % 10); return body.substring(0, 4) + " " + body.substring(4, 8) + " " + body.substring(8, 12) + " " + body.substring(12); } }