#c# #java #cryptography
#c# #java #криптография
Вопрос:
Я переношу несколько тысяч строк криптографических функций C # в проект Java. Код C # широко использует значения без знака и побитовые операции.
Я знаю о необходимых обходных путях Java для поддержки значений без знака. Однако было бы намного удобнее, если бы существовали реализации 32-битных и 64-битных целых чисел без знака, которые я мог бы вставить в свой код. Пожалуйста, дайте ссылку на такую библиотеку.
Быстрые запросы Google показывают несколько, которые являются частью коммерческих приложений:
Комментарии:
1. Кстати, я забыл спросить: какую именно функцию вы ищете в этих библиотеках, которую нельзя выполнить с помощью чего-то подобного тому, что я написал ниже?
2. @Mehrdad Мои неправильные представления о Java, возможно, также исказили то, что я предполагал возможным. Я, вероятно, приму ответ Томаса Порнина за объединение общих методов — хотя это именно то, чего я пытался избежать.
3. Честно говоря, в побитовой арифметике задействовано не так уж много «опыта»; вы действительно можете сделать это самостоятельно. Умный в каком смысле, однако? Если вы имеете в виду скорость, это одно, но в остальном, я думаю, проблема в правильности, а не в сообразительности. (А также, очевидно, примите ответ Томаса, если вы нашли это более полезным! :] )
Ответ №1:
Операции с целыми числами со знаком и без знака в основном идентичны, когда используются обозначения дополнения two, что и делает Java. Это означает, что если у вас есть два 32-разрядных слова a
и b
и вы хотите вычислить их сумму a b
, одна и та же внутренняя операция выдаст правильный ответ, независимо от того, считаете ли вы слова подписанными или беззнаковыми. Это будет работать должным образом для сложений, вычитаний и умножений.
Операции, которые должны учитывать знак, включают:
- Сдвиги вправо: сдвиг вправо со знаком дублирует знаковый бит, в то время как сдвиг вправо без знака всегда вставляет нули. Java предоставляет оператор «
>>>
» для сдвига вправо без знака. - Деления: деление без знака отличается от деления со знаком. При использовании 32-разрядных целых чисел вы можете преобразовать значения в 64-разрядный
long
тип («x amp; 0xFFFFFFFFL
» выполняет трюк с «преобразованием без знака»). -
Сравнения: если вы хотите сравнить
a
сb
как два 32-разрядных слова без знака, тогда у вас есть две стандартные идиомы:if ((a Integer.MIN_VALUE) < (b Integer.MIN_VALUE)) { ... }
if ((a amp; 0xFFFFFFFFL) < (b amp; 0xFFFFFFFFL)) { ... }
Зная это, подписанные типы Java не представляют большой проблемы для криптографического кода. Я реализовал много криптографических примитивов на Java, и подписанные типы не являются проблемой при условии, что вы понимаете, что пишете. Например, взгляните на sphlib: это библиотека с открытым исходным кодом, которая реализует множество криптографических хэш-функций как на C, так и на Java. Java-код использует подписанные типы Java ( int
, long
…) довольно плавно, и это просто работает.
В Java нет перегрузки операторов, поэтому «решения» только для Java для получения неподписанных типов будут включать пользовательские классы (такие как UInt64
класс, на который вы ссылаетесь), что приведет к значительному снижению производительности. Вы действительно не хотите этого делать.
Теоретически, можно было бы определить Java-подобный язык с неподписанными типами и реализовать компилятор, который создает байт-код для JVM (внутренне используя приемы, которые я подробно описал выше для сдвигов, делений и сравнений). Я не знаю ни одного доступного инструмента, который делает это; и, как я сказал выше, подписанные типы Java просто хороши для криптографического кода (другими словами, если у вас возникли проблемы с такими подписанными типами, то осмелюсь предположить, что вы недостаточно знаете для безопасной реализации криптографического кода, и вам следует воздержаться от этого; вместо этого используйте существующие библиотеки с открытым исходным кодом).
Ответ №2:
Это языковая функция, а не библиотечная, поэтому нет способа расширить Java для поддержки этой функциональности, если вы не измените сам язык, и в этом случае вам нужно будет создать свой собственный компилятор.
Однако, если вам нужен сдвиг вправо без знака, Java поддерживает >>>
оператор, который работает аналогично >>
оператору для неподписанных типов.
Однако вы можете создать свои собственные методы для выполнения арифметики со знаковыми типами, как если бы они были беззнаковыми; это должно сработать, например:
static int multiplyUnsigned(int a, int b)
{
final bool highBitA = a < 0, highBitB = b < 0;
final long a2 = a amp; ~(1 << 31), b2 = b amp; ~(1 << 31);
final long result = (highBitA ? a2 | (1 << 31) : a2)
* (highBitB ? b2 | (1 << 31) : b2);
return (int)resu<
}
Редактировать:
Благодаря комментарию @Ben’s мы можем упростить это:
static int multiplyUnsigned(int a, int b)
{
final long mask = (1L << 32) - 1;
return (int)((a amp; mask) * (b amp; mask));
}
Однако ни один из этих методов не работает для long
типа. В этом случае вам пришлось бы привести к double
, отрицать, умножать и приводить его обратно, что, вероятно, уничтожило бы все ваши оптимизации.
Комментарии:
1. Спасибо за ваш ответ, но я хорошо знаком с
>>>
оператором. Я действительно ожидал бы, что было бы возможно реализовать и целочисленный объект без знака в Java, включая необходимые перегрузки оператора. Документы для коммерческих проектов, таких как этот , дают мне надежду.2. Я не думаю, что функция multiply возвращает что-либо похожее на правильные результаты, когда установлен старший бит.
3. @Karl: Java не поддерживает перегрузку операторов. И для криптографических целей объекты были бы очень медленными; вам понадобились бы примитивные типы без знака, которых не существует в Java.
4. @Ben: Да, вы были правы; Я отредактировал это, это должно сработать.
5. @Mehrdad: Если у вас более широкий целочисленный тип, вы можете сделать просто
long mask = (1L << 32) - 1; return (a amp; mask) * (b amp; mask);
, но это не распространяется на самый широкий целочисленный тип.