Хеширование целого числа в Python для соответствия стандарту ORACLE_HASH

#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 байтами — намного больше, чем мог бы хранить Oracle NUMBER .

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"