Как я могу зашифровать / расшифровать 12-значные десятичные числа для других, используя пароль и Java? - PullRequest
1 голос
/ 13 мая 2009

Я уже прочитал Использование Java для шифрования целых чисел и Шифрование с помощью DES с использованием фразы-пароля .

Все, что мне нужно, - это простой Encrypter, который преобразует 12-значное число в 12-значное число со следующими ограничениями:

  1. Шифрование должно зависеть от пароля (который будет постоянным в течение всего времени жизни приложения) и ничего больше.
  2. Отображение должно быть 1-1 (без хеширования и нескольких входов, дающих одинаковый выход и наоборот).
  3. Сопоставление не должно изменяться между разными виртуальными машинами или при запуске виртуальной машины (например, при перезапуске Java утилита должна выдавать вам те же сопоставления, что означает, что она должна зависеть только от предоставленного пароля).
  4. Числа, начинающиеся с 0, не являются действительными 12-значными числами (также вводимые числа не начинаются с 0).
  5. Ключ / пароль никогда не должен быть угадываемым. Например, запуск утилиты с несколькими входами и анализ выходов не должен позволять угадывать ключ / pwd / hash или что-либо еще.
  6. Все входные данные будут состоять ровно из 12 цифр и состоят из 12-значного простого числа (что означает, что мы можем использовать арифметику по модулю).

Пролистав литературу, у меня есть этот код

public void mytestSimple(long code, String password) throws Exception {
    SecretKey key = new SecretKeySpec(password.getBytes(), "DES");
    Cipher ecipher = Cipher.getInstance("DES");
    ecipher.init(Cipher.ENCRYPT_MODE, key);
    System.out.println(ecipher.getOutputSize(8));

    byte[] encrypted = ecipher.doFinal(numberToBytes(code));
    System.out.println(encrypted + "--" + encrypted.length);

    Cipher dcipher = Cipher.getInstance("DES");
    dcipher.init(Cipher.DECRYPT_MODE, key);
    byte[] decrypted = dcipher.doFinal(encrypted);
    System.out.println(bytesToNumber(decrypted) + "--" + decrypted.length);
}

public void testSimple() throws Exception {
    mytestSimple(981762654986L, "password");
}

У меня проблемы с

  1. Как преобразовать 16 байтов в 12-значное число.
  2. Поддерживать отображение 1-1.
  3. Сохраняйте одинаковое шифрование / дешифрование при нескольких вызовах виртуальных машин.

**** Ответ, добавленный мной ниже ****

Я добавил один ответ - 40-битный RSA, извлеченный из стандартной логики пар ключей Java RSA. Я все еще должен работать на крайних случаях. Я собираюсь принять ответ и сказать «Тадмас», который, как мне кажется, приведет меня к ответу. Может кто-нибудь сказать мне, будет ли мой алгоритм слабым / уязвимым?

Ответы [ 12 ]

6 голосов
/ 13 мая 2009

Если строгое отображение 1: 1 важнее, чем защита от криптоанализа, тогда вы можете преобразовать пароль в 12-значное число (с помощью хэша или иным образом) и просто добавить к своему исходному числу мод 10 ^ 12. Если вам абсолютно необходимо удалить начальные нули из выходных данных, вы можете вычесть 10 ^ 11, выполнить математический мод (10 ^ 12 - 10 ^ 11), а затем снова добавить 10 ^ 11. Конечно, это крайне небезопасно, но довольно просто. :)

Если диапазон входных данных ограничен простым числом меньше (10 ^ 12 - 10 ^ 11), вы можете использовать message ^ password mod prime, чтобы сформировать кольцо, которое удовлетворит ваши требования и будет немного сложнее взломать. (Это похоже на работу RSA.) Я думаю, что это может сработать, если вам не нужно расшифровывать его.

Я согласен с Джоном Скитом: требование строгого отображения 1: 1 без выходного диапазона, превышающего входной домен, - это то, что большинство библиотек шифрования не будет обрабатывать.

6 голосов
/ 13 мая 2009

Вы не сможете преобразовать 16 байтов в 12-значное число без потери информации. 256 ^ 16> 10 ^ 12. (Не то чтобы у вас было даже 10 ^ 12 опций, поскольку у вас есть только диапазон [100000000000, 999999999999].

Я сомневаюсь, что вы сможете использовать любые традиционные библиотеки шифрования, поскольку ваши требования несколько странны.

3 голосов
/ 14 мая 2009

Одно потенциальное решение может быть построено на шифрах Фейстеля . Эта конструкция позволяет построить псевдослучайную перестановку на основе псевдослучайных функций. Например. псевдослучайные функции могут быть построены из соответствующего блочного шифра путем усечения результата до 6-значных чисел.

Эта конструкция была проанализирована в следующей статье M. Luby и C. Rackoff, «Как построить псевдослучайные перестановки из псевдослучайных функций» SIAM Journal of Computing, Vol.17, No.2, pp.373--386, 1988


Конкретным предложением является Режим шифрования конечного набора Feistel , который был представлен в NIST для возможного включения в следующий стандарт. Это предложение также решает проблему шифрования диапазонов, которые не являются степенью 2.

2 голосов
/ 13 мая 2009

Если цифры для идентификаторов пользователей, я бы сделал следующее:

(1) Сгенерируйте ключ AES из пароля. Простой вызов getBytes () - это нормально, если вы доверяете администратору использовать действительно очень надежный пароль. В идеале, используйте стандартную технику «шифрования на основе пароля» для хеширования байтов, скажем, несколько тысяч раз, каждый раз добавляя случайные «соленые» байты, которые вы изначально сгенерировали, чтобы избежать атак по словарю.

(2) Зашифруйте соответствующий номер с помощью этого ключа AES.

(3) Отрезать бит из 12 цифр из полученного зашифрованного блока, преобразовать его в десятичную и представить это число пользователю. (Для этого вы можете обернуть BigInteger вокруг байтов, вызвать toString () и вытащить, скажем, байты между позициями 4 и 16.) Экспериментально похоже, что вы не должны брать цифры из крайний правый конец.

[ Обновление : я думаю это, вероятно, потому, что BigInteger буквально распределяет свои числа слева направо - но я не проверял - так что потенциально «запасные» биты в самом правом байте и, следовательно, меньшее количество возможных чисел, если вы включите самый последний байт.]

Теперь, я слышу, как ты плачешь, это, очевидно, не отображение 1-1. Но если у вас не будет более десятков тысяч пользователей, этого действительно достаточно. С 12-значным числом вы ожидаете в среднем зашифровать около 300 000 номеров, прежде чем столкнетесь. Так что, хотя у вас нет строго сопоставления 1-1, на практике это почти так же, как черт.

(В любом случае, если ваше приложение действительно имеет сотни пользователей и безопасность имеет решающее значение, вам, вероятно, захочется инвестировать в серьезные консультации по этому вопросу ...)

Просто чтобы убедить себя в том, что действительно можно утверждать, что это отображение 1-1, вы можете запустить симуляцию, которая неоднократно пытается выделить, скажем, 200 000 идентификаторов пользователя со случайными ключами, и распечатать, сколько коллизий было на каждый прогон:

 next_pass :
        for (int pass = 0; pass < 100; pass++) {
          byte[] key = new byte[16];
          (new SecureRandom()).nextBytes(key);
          Cipher ciph = Cipher.getInstance("AES");
          SecretKeySpec ks = new SecretKeySpec(key, "AES");
          ByteBuffer bb = ByteBuffer.allocate(16);
          Set<String> already = new HashSet<String>(100000);
          int colls = 0;
          for (int i = 0; i < 200000; i++) {
            bb.putLong(0, i);
            ciph.init(Cipher.ENCRYPT_MODE, ks);
            byte[] encr = ciph.doFinal(bb.array());
            encr[0] &= 0x7f; // make all numbers positive
            BigInteger bigint = new BigInteger(encr);
            String userNo = bigint.toString();
            userNo = userNo.substring(4, 16);
            if (!already.add(userNo)) {
              System.out.println("Coll after " + i);
              continue next_pass;
            }
          }
          System.out.println("No collision.");
        }
2 голосов
/ 13 мая 2009

Я предлагаю очень простой алгоритм.

  1. Введите пароль в хеш-функцию.
  2. Инициализируйте генератор случайных чисел с помощью хеша или чего-то, что вы получили из хеша.
  3. Создание 12-значного случайного числа.
  4. Добавьте это случайное число к входной цифре за цифрой по модулю 10 для шифрования.

Чтобы расшифровать, вычтите случайное число по модулю 10. На самом деле это форма One Time Pad . Из-за комментариев к этому ответу я понял, что ссылаясь на One Time Pad был плохим выбором. Лучшим эталоном является Полиалфабетический шифр - в то время как One Time Pad использует полиалфабетическое замещение, его главная характеристика - не использовать бит ключа дважды.

   Input           1234 1234 1234
   Random number   6710 3987 2154
   Output          7944 4111 3388

Осталась одна проблема - алгоритм может создавать начальные нули. Для решения этой проблемы можно использовать XOR вместо сложения и вычитания. Просто преобразуйте цифры с помощью XOR. Если первая цифра превращается в ноль, не шифруйте первую цифру. Когда вы снова расшифруете с помощью XOR, первая цифра превратится в ноль, и вы знаете, что первая цифра не была зашифрована.

UPDATE

Простой XOR не является решением, потому что он выдаст большое количество - например, 2 XOR 9 = 11. Собираюсь переосмыслить это ...

UPDATE

Хорошие свойства XOR: XOR(a, b) = XOR(b, a) и XOR(XOR(a, b), b) = a. Это делает шифрование и дешифрование одинаковыми и позволяет обнаруживать незашифрованную первую цифру. Кроме того, требуется, чтобы наша функция возвращала значения только в диапазоне от 0 до 9, чего не делает XOR.
Но, возможно, мы сможем создать пользовательскую функцию со всеми необходимыми свойствами. Поэтому мы создаем массив FUNC с 10 столбцами и 10 строками и используем его в качестве таблицы поиска для нашей функции. Какие значения, но в? Я на самом деле не знаю - я даже не уверен, что это возможно. Но если мы выберем три числа из диапазона от 0 до 9, мы должны сделать следующие шесть записей. (Это симметричная матрица.)

FUNC[x,y] = z   FUNC[x,z] = y   FUNC[y,z] = x
FUNC[y,x] = z   FUNC[z,x] = y   FUNC[z,y] = x

Так что, возможно, можно создать такую ​​функцию, многократно выбирая случайные числа и заполняя шесть записей, если нет конфликта. Возможно это не так. Я хотел бы увидеть таблицу, если найдется решение.

1 голос
/ 04 апреля 2013

Этой теме 4 года, но для тех, кто нашел ее в Google: взгляните на сохраняющие формат шифры: http://en.wikipedia.org/wiki/Format-preserving_encryption

1 голос
/ 15 октября 2009

Я бы использовал поток шифр . N байтов входит, N байтов выходит.

1 голос
/ 15 мая 2009

Мне кажется, что ответ, данный Тадмасом ниже, был очень полезным, и я хочу, чтобы вы, ребята, взломали / запугали мою реализацию ниже. Как указывает Тэдмас, все мои числа 40-битные (12-значное число 10 ^ 12, что примерно 2 ^ 40).

Я скопировал sun.security.rsa.RSAKeyPairGenerator (ссылка) и создал свой собственный генератор для 40-битного алгоритма RSA. Стандартное значение должно составлять 512-1024 бита, поэтому я убрал проверку входных данных. Как только я создаю подходящие значения n, e, d (по данным alog, e равно 65537). Следующий код обслуживается нормально,

public void testSimple() throws NoSuchAlgorithmException {
    MyKeyPairGenerator x = new MyKeyPairGenerator();
    x.initialize(40, new SecureRandom("password".getBytes()));

    MyPublicPrivateKey keypair = x.generateKeyPair();
    System.out.println(keypair);

    BigInteger message = new BigInteger("167890871234");
    BigInteger encoded = message.modPow(keypair.e, keypair.n);
    System.out.println(encoded); //gives some encoded value
    BigInteger decoded = encoded.modPow(keypair.d, keypair.n);
    System.out.println(decoded); //gives back original value
}

Недостатки

  1. Кодированный код не всегда может содержать 12 цифр (иногда он может начинаться с 0, что означает только 11 цифр). Я думаю, что всегда добавляйте 0 нулей впереди и добавляйте несколько цифр CHECKSUM в начале, которые могут облегчить эту проблему. Так что 13 цифр всегда ...
  2. 40-битный RSA слабее, чем 512-битный (не только 512/40 раз, но и экспоненциальный коэффициент раз). Можете ли вы, эксперты, указать мне ссылки на то, насколько безопасен 40-битный RSA по сравнению с 512-битным RSA (я могу видеть некоторые вещи в вики, но не могу конкретно подтвердить возможность атак)? Любые ссылки (вики?) О вероятностях / количестве попыток, необходимых для взлома RSA как функции N, где n - это количество используемых битов, будут отличными!
0 голосов
/ 15 мая 2009

Если вы готовы принять довольно слабое решение ...

Обрабатывать 12-значное число как два 6-значных. Затем используйте хэш пароля в качестве начального числа для генератора случайных чисел, который перемешивает таблицу из 999 990 последовательных значений. Затем используйте два 6-значных числа для поиска записей в таблице. Объединение двух результатов - это ваш 12-значный «зашифрованный» ввод с отображением 1: 1 на основе пароля.

Давайте сделаем пример с 4 цифрами вместо 12 ...

Input: 4852
Password: passw0rd1 => hashcode = 37592

Теперь возьмите этот массив ..

a = [10, 11, 12, 13, .. 98, 99]

И перемешать это с 37592 в качестве случайного семени ...

b = [45, 15, 56, 49, .. 33, 88]

Теперь разделите ввод: 48, 52 и найдите эти индексы в перемешанном массиве, скажем ...

b[48] => 23
b[52] => 96

Итак, наша зашифрованная версия 4852 - 2396

.

Это действительно не сильное решение, но ограничения в вашем вопросе не приведут к сильному решению. Возможно, вам придется немного ослабить эти ограничения.

0 голосов
/ 13 мая 2009

Переосмысливая проблему, я придумал следующее. В основном вам нужен симметричный шифр, чтобы получить однозначное сопоставление. И отметив, что 10^12 почти 2^40 (2^39.863), кажется естественным преобразовать ваше 12-значное число в 40-битное целое и передать его в блочный шифр с длиной блока 40 бит. Хорошим выбором может быть Blowfish с поддержкой длины блока от 32 до 448 бит с шагом 8 бит - так что поддерживается 40 бит.


UPDATE

Как указал Accipitridae, Blowfish имеет переменный размер ключа, но фиксированный размер блока, поэтому это не вариант. Похоже, что более продолжительный поиск в Интернете указывает на то, что мало или нет шифров с размерами блоков 40 бит или менее, что делает эту идею недействительной. Оставьте оставшуюся часть ответа - возможно, можно найти подходящий шифр.


Оставшаяся проблема заключается в том, что Blowfish может возвращать число до 1,099,511,627,775 с 13 цифрами и что возвращаемое число может содержать начальные нули, но я считаю, что это можно решить на втором шаге , Моей первой мыслью было применить что-то вроде преобразования Берроуза-Уилера к строковому представлению числа, чтобы получить хотя бы один ноль в начале числа, обозначающего 13-ю цифру, и затем изменить все оставшиеся цифры ( например 0 <-> 9, 1 <-> 8, 2 <-> 7, ...), чтобы превратить дополнительные начальные нули в другие цифры.
Через несколько минут я понял, что это не сработает - вход имеет размер 2^40, а выход имеет только размер 2^39.863. Решением было бы использовать 39-битный блочный шифр и ограничить ввод цифрами до 549,755,813,887. Я не знаю, существует ли шифр, который может работать с длиной блока в 39 бит, но в этой статье о эластичных блочных шифрах описано, как создать блочный шифр, который может работать с каждым размером блока из * От 1029 * до 2n с учетом блочного шифра, который может обрабатывать блоки размером n. В результате можно создать 39-битный блочный шифр из 32-битного Blowfish.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...