#python #oracle #hash
#python #Oracle #хэш
Вопрос:
В Oracle мои данные были хэшированы путем передачи целого числа в `STANDARD_HASH’ следующим образом. Как я могу получить то же значение хэша с помощью Python?
Результат в Oracle, когда целое число передается в STANDARD_HASH:
SELECT STANDARD_HASH(123, 'SHA256') FROM DUAL;
# A0740C0829EC3314E5318E1F060266479AA31F8BBBC1868DA42B9E608F52A09F
Результат в Python при передаче строки в:
import hashlib
hashlib.sha256(str.encode(str(123))).hexdigest().upper()
# A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3
# I want to modify this function to get the hash value above.
Возможно, эта информация также поможет. Я не могу ничего изменить на стороне Oracle, но если бы я мог, я бы преобразовал столбец в CHAR
, и это дало бы то же значение, что и моя текущая реализация Python. Ниже приведен пример.
Результат в Oracle при передаче строки в STANDARD_HASH:
SELECT STANDARD_HASH('123', 'SHA256') FROM DUAL;
# A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3 (matches Python result)
Я предпринял несколько попыток, например, просто передал целое число в Python, но это приводит к ошибке, что требуется строка. Я также искал способ кодирования целого числа, но не добился никакого прогресса.
Комментарии:
1. Реализация хэш-функции должна быть одинаковой для обоих. Пока вы не сможете увидеть код обоих, невозможно хэшировать все входные данные для одних и тех же выходных данных.
2. @Golden_flash, я думаю, что мой вопрос по сути заключается в следующем: в Oracle я хэшировал целое число. Как мне также хешировать целое число в Python?
3. Вы отключили рандомизацию хэша в python?
PYTHONHASHSEED=0 python YOURSCRIPT.py
это делается для того, чтобы избежать варианта использования DOS, где используется наихудшая возможная производительность хэша.4. @Bobby В python 3 хэш-функция по умолчанию для integer просто возвращает 123. Поиск других библиотек, поддерживающих аналогичную функцию хеширования, может быть единственным вариантом, поскольку hashlib принимает только строку или байты
5. Одна из проблем заключается в том, что Oracle не использует целые числа, они используют свой собственный тип NUMBER , который представляет собой тип данных переменной длины с совершенно другим двоичным представлением. Было бы интересной задачей реализовать это на Python. amitzil.wordpress.com/2015/03/24 /…
Ответ №1:
Oracle представляет числа в своем собственном внутреннем формате, который можно увидеть с помощью dump()
функции в Oracle. Например.,
SELECT dump(123) FROM dual;
Typ=2 Len=3: 194,2,24
Итак, чтобы хешировать число в Python и получить тот же результат, что и в Oracle, вам нужно преобразовать число Python в набор байтов таким же образом, как Oracle делает это в своих внутренних компонентах.
Хороший анализ внутренней логики, используемой Oracle, можно найти здесь. Это правильно с одним незначительным упущением, связанным с завершением отрицательных чисел. Кроме того, оно написано с точки зрения декодирования числа Oracle из его байтов. В нашем случае нам нужно закодировать число Oracle в его внутренний байтовый формат. Тем не менее, я широко использовал его при формировании этого ответа.
Приведенный ниже код показывает функцию Python, to_oracle_number()
которая вернет массив целых чисел, имеющих то же байтовое представление числа, что и вычисляемое базой данных Oracle. Оно должно обрабатывать любое число (положительное, отрицательное, дробное, нулевое и т.д.).
Код в самом низу также показывает, как вызвать эту функцию и хэшировать ее результаты, чтобы получить то же значение хэша, которое было вычислено в базе данных Oracle, что, я полагаю, является основой вашего вопроса.
ПРИМЕЧАНИЕ: Функция ожидает, что число, которое вы хотите преобразовать, будет передано в виде строки, чтобы избежать потерь точности.
import math
import decimal
import hashlib
def to_oracle_number( nstr ):
# define number n that we want to convert
n = decimal.Decimal(nstr)
# compute exponent (base 100) and convert to Oracle byte along with sign
#print (abs(n))
l_exp = 0
l_len = 0
l_abs_n = abs(n)
if l_abs_n != 0:
l_exp = math.floor(math.log(l_abs_n,100))
# Oracle adds 1 to all bytes when encoding
l_exp = l_exp 1
# Oracle adds 64 to exponent whe encoding
l_exp = l_exp 64
if n < 0:
# take 1's complement of exponent so far (bitwise xor)
l_exp = (l_exp ^ 127)
if n >= 0:
# add sign bit. zero is considered positive.
l_exp = l_exp 128
l_bytes = []
l_bytes.append(l_exp)
l_len = l_len 1 # exponent and sign take 1 byte
l_whole_part = str(int(l_abs_n))
# make sure there is an even number of digits in the whole part
if len(l_whole_part) % 2 == 1:
l_whole_part = '0' l_whole_part
# get the fractional digits, so if 0.01234, just 01234
l_frac_part = str(l_abs_n - int(l_abs_n))[2:]
# make sure there is an even number of digits in the fractional part
if len(l_frac_part) % 2 == 1:
l_frac_part = l_frac_part '0'
l_mantissa = l_whole_part l_frac_part
# chop off leading 00 pairs
while l_mantissa[0:2] == '00':
l_mantissa = l_mantissa[2:]
# chop off trailing 00 pairs
while l_mantissa[-2:] == '00':
l_mantissa = l_mantissa[:-2]
# compute number of 2-character chunks
l_chunk_count = int(len(l_mantissa) / 2)
l_chunks = '';
for i in range(0, l_chunk_count):
l_chunk = int(l_mantissa[i*2:i*2 2])
if n < 0:
# for negative numbers, we subtract from 100
l_chunk = 100-l_chunk
# Oracle adds 1 to all bytes
l_chunk = l_chunk 1
# Add the chunk to our answer
l_chunks = l_chunks ',' str(l_chunk)
l_bytes.append(l_chunk)
l_len = l_len 1 # we have computed one more byte
#print (str(i) ':' str(l_chunk))
if n < 0 and l_len < 21:
# terminating negative numbers always end in byte 102 (do not know why)
l_chunks = l_chunks ',102'
l_bytes.append(102)
l_len = l_len 1
l_computed_dump = 'Typ=2 Len=' str(l_len) ': ' str(l_exp) l_chunks
print (l_computed_dump)
print (l_bytes)
return l_bytes
# test it
m = hashlib.sha256()
b = bytes(to_oracle_number('123')) # pass a string so no precision errors
m.update(b)
print(m.hexdigest().upper())
ВЫВОД
Typ=2 Len=3: 194,2,24 [194, 2, 24] A0740C0829EC3314E5318E1F060266479AA31F8BBBC1868DA42B9E608F52A09F
Комментарии:
1. Это выглядит многообещающе, но не могли бы вы написать это автономно в Python? Поскольку лучше всего использовать функцию Python, где входным значением является число, подлежащее хэшированию, а выходным — хэшированное значение, подобное тому, что вы показали. У меня не будет доступа к исходным значениям в Oracle.
2. Ну, нет. В этом проблема, не так ли? Вам понадобится реализация на Python любой логики, используемой Oracle для генерации байтовых представлений
NUMBER
значений. По этому поводу есть некоторая информация. Вот, например: amitzil.wordpress.com/2015/03/24 /… . Но я не уверен, что было бы разумно пытаться реализовать такую вещь, поскольку это может измениться при следующем обновлении Oracle (даже если вы достаточно осторожны, чтобы даже с самого начала получить логику на 100% правильную).3. Я понимаю вашу точку зрения, и это ценный вклад. Тем не менее, только ответ, полностью реализованный на Python, мог бы ответить на мой вопрос. Итак, я тоже начну щедрость, посмотрим, поможет ли это.
4. Сначала я написал это на PL / SQL, поэтому смог тщательно протестировать его в функции Oracle
dump()
. Но, тем не менее, вы должны быть осторожны. Например, я только сейчас понимаю, что версия Python никогда не останавливается. Если бы он получил строку из 200 символов, он сгенерировал бы массив байтов с более чем 100 байтами — намного больше, чем мог бы хранить OracleNUMBER
.5. Нет, версия PL / SQL вообще никогда не давала сбоев, но для меня было невозможно протестировать число с более значащими цифрами, чем Oracle,
NUMBER
поскольку я использовалNUMBER
выражения для генерации тестовых примеров. Если вы используете целочисленные значения менее 30 цифр, все должно быть в порядке.
Ответ №2:
ПРЕДУПРЕЖДЕНИЕ: Оригинальное решение для потока исходит от @Matthew McPeak, и за этот ответ следует вознаградить, ниже вы найдете слегка измененную версию, где я добавил немного рефакторинга к его алгоритму, хотя:
import math
import decimal
import hashlib
def to_oracle_number(nstr):
n = decimal.Decimal(nstr)
# compute exponent (base 100) and convert to Oracle byte along with sign
l_exp, l_len, l_abs_n = 0, 0, abs(n)
if l_abs_n != 0:
l_exp = math.floor(math.log(l_abs_n, 100)) 65
l_exp = (l_exp ^ 127) if n < 0 else l_exp 128
l_bytes = [l_exp]
l_len = 1 # exponent and sign take 1 byte
l_whole_part = str(int(l_abs_n))
# make sure there is an even number of digits in the whole part
if len(l_whole_part) % 2 == 1:
l_whole_part = '0' l_whole_part
# get the fractional digits, so if 0.01234, just 01234
l_frac_part = str(l_abs_n - int(l_abs_n))[2:]
# make sure there is an even number of digits in the fractional part
if len(l_frac_part) % 2 == 1:
l_frac_part = '0'
l_mantissa = l_whole_part l_frac_part
# chop off leading 00 pairs
while l_mantissa[0:2] == '00':
l_mantissa = l_mantissa[2:]
# chop off trailing 00 pairs
while l_mantissa[-2:] == '00':
l_mantissa = l_mantissa[:-2]
# compute number of 2-character chunks
l_chunks = ''
for i in range(0, int(len(l_mantissa) / 2)):
l_chunk = int(l_mantissa[i * 2:i * 2 2])
if n < 0:
l_chunk = 100 - l_chunk
l_chunk = 1
l_chunks = f"{l_chunks},l_chunk"
l_bytes.append(l_chunk)
l_len = 1
if n < 0 and l_len < 21:
# terminating negative numbers always end in byte 102 (do not know why)
l_chunks = ',102'
l_bytes.append(102)
l_len = 1
# bytes(l_bytes)l_computed_dump = f"Typ=2 Len={l_len}: {l_exp}{l_chunks}"
m = hashlib.sha256()
m.update(bytes(l_bytes))
return m.hexdigest().upper()
if __name__ == '__main__':
assert to_oracle_number('123') == "A0740C0829EC3314E5318E1F060266479AA31F8BBBC1868DA42B9E608F52A09F"