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

Модель получает текст, который выглядит естественно, и качество ответа страдает меньше, чем от * звёздочек. Значения детерминированы: одно и то же исходное значение всегда даёт одну и ту же * подстановку. */ 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 = value.hashCode() & Integer.MAX_VALUE; return switch (type) { case PdTypes.FIO -> pick(SURNAMES, seed) + " " + pick(NAMES, seed >> 3) + " " + pick(PATRONYMICS, seed >> 6); case PdTypes.CARDHOLDER -> "IVAN PETROV"; case PdTypes.EMAIL -> "user" + ordinal + "@" + pick(DOMAINS, seed); case PdTypes.PHONE -> "+7 9" + digits(seed, 2) + " " + digits(seed >> 4, 3) + "-" + digits(seed >> 8, 2) + "-" + digits(seed >> 12, 2); case PdTypes.CARD -> luhnCard(seed); case PdTypes.PASSPORT, PdTypes.DRIVER_LICENSE, PdTypes.FOREIGN_PASSPORT, PdTypes.MILITARY_ID -> digits(seed, 4) + " " + digits(seed >> 6, 6); case PdTypes.INN -> digits(seed, 12); case PdTypes.MEDICAL_POLICY -> digits(seed, 16); case PdTypes.SNILS -> digits(seed, 3) + "-" + digits(seed >> 4, 3) + "-" + digits(seed >> 8, 3) + " " + digits(seed >> 12, 2); case PdTypes.BIRTH_DATE, PdTypes.PASSPORT_DATE, PdTypes.DATE -> syntheticDate(seed); case PdTypes.ADDRESS_CITY -> "Зареченск"; case PdTypes.ADDRESS_STREET -> "Сосновая"; case PdTypes.ADDRESS_HOUSE -> String.valueOf(1 + Math.floorMod(seed, 90)); case PdTypes.ADDRESS_FLAT -> String.valueOf(1 + Math.floorMod(seed, 200)); case PdTypes.ADDRESS_POSTCODE -> digits(seed, 6); case PdTypes.ADDRESS_COUNTRY -> "Заречье"; case PdTypes.ADDRESS_REGION -> "Заречная область"; case PdTypes.ADDRESS_DISTRICT -> "Сосновый район"; case PdTypes.CVV -> digits(seed, 3); case PdTypes.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)); body.append(Validators.luhnCheckDigit(body.toString())); return body.substring(0, 4) + " " + body.substring(4, 8) + " " + body.substring(8, 12) + " " + body.substring(12); } }