Перевести алгоритм хеширования с C на Python
Мой клиент - программист на Python, и я создал для него бэкэнд C++, который включает генерацию и проверку лицензий. Для дополнительной безопасности интерфейс Python также выполнит проверку действительности лицензии.
Однако алгоритм генерации и проверки лицензии основан на методах хэширования, которые основаны на том факте, что целое число имеет фиксированный размер байта, а смещение битов не приведет к увеличению числа байтов целых чисел.
Это упрощенный пример кода:
unsigned int HashString(const char* str) {
unsigned int hash = 3151;
while (*str != 0) {
hash = (hash << 3) + (*str << 2) * 3;
str++;
}
return hash;
}
Как это можно перевести на Python? Прямой перевод, очевидно, дает другой результат:
def hash_string(str):
hash = 3151
for c in str:
hash = (hash << 3) + (ord(c) << 2) * 3
return hash
Например:
hash_string("foo bar spam") # 228667414299004
HashString("foo bar spam") // 3355459964
Изменить: То же самое будет необходимо для PHP, так как интернет-магазин должен иметь возможность генерировать действительные лицензии тоже.
2 ответа
Проблема здесь в том, что С unsigned int
автоматически переворачивается, когда он проходит UINT_MAX
в то время как питон int
просто становится больше
Самый простой способ исправить это просто исправить в конце:
return hash % (1 << 32)
Для очень больших струн может быть немного быстрее маскироваться после каждой операции, чтобы избежать попадания в огромные строки int
значения, с которыми медленно работать. Но для небольших строк это, вероятно, будет медленнее, потому что стоимость звонка %
12 раз вместо 1 легко перевесят стоимость работы с 48-битным int.
У PHP может быть та же проблема или другая.
Целочисленный тип PHP по умолчанию - C long. На 64-битной платформе Unix это больше, чем unsigned int
, так что вам придется использовать тот же трюк, что и на Python (либо %
или же &
в зависимости от того, что имеет больше смысла для вас.)
Но на 32-битной платформе Unix или в Windows этот размер равен unsigned int
но подписано, что означает, что вам нужен другой трюк. Вы не можете на самом деле представлять, скажем, 4294967293
напрямую (попробуйте, и вы получите -3
вместо). Вы можете использовать GMP
или же BCMath
целое число вместо типа по умолчанию (в этом случае он в основном такой же, как в Python), или вы можете просто написать собственный код для печати, сравнения и т. д., который будет обрабатывать это -3
как будто это было 4294967293
,
Обратите внимание, что я просто предполагаю, что int
составляет 32 бита, и long
либо 32, либо 64, потому что это происходит на всех популярных сегодня платформах. Но стандарт С требует только int
быть длиной не менее 16 бит и long
быть не менее 32 бит и не короче int
, Если вам нужно иметь дело с очень старыми платформами, где int
может быть 16 бит (или 18!) или будущие платформы, где может быть 64 или более, вы должны корректировать свой код соответствующим образом.
Маскировать значение хеша &
:
def hash_string(str, _width=2**32-1):
hash = 3151
for c in str:
hash = ((hash << 3) + (ord(c) << 2) * 3)
return hash & _width
Это вручную сокращает хэш до размера. Вам нужно ограничить результат только один раз; это не так, как будто эти старшие биты имеют значение для конечного результата.
Демо-версия:
>>> hash_string("foo bar spam")
3355459964