【问题标题】:Hash an integer in Python to match Oracle's STANDARD_HASH在 Python 中散列一个整数以匹配 Oracle 的 STANDARD_HASH
【发布时间】:2019-08-26 10:31:30
【问题描述】:

在 Oracle 中,我的数据已通过将整数传递到“STANDARD_HASH”进行哈希处理,如下所示。如何使用 Python 获得相同的哈希值?

将整数传递给 STANDARD_HASH 时在 Oracle 中的结果:

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 实现相同的值。下面是一个例子。

将字符串传递给 STANDARD_HASH 时在 Oracle 中的结果:

SELECT STANDARD_HASH('123', 'SHA256') FROM DUAL;
# A665A45920422F9D417E4867EFDC4FB8A04A1F3FFF1FA07E998E86F7F7A27AE3 (matches Python result)

我已经做了几次尝试,比如简单地将一个整数传递给 Python,但这会导致需要字符串的错误。我还搜索了一种对整数进行编码的方法,但没有取得任何进展。

【问题讨论】:

  • 两者的哈希函数实现应该相同。在您看到两者的代码之前,不可能将所有输入散列到相同的输出。
  • @Golden_flash,我认为我的问题本质上是这样的:在 Oracle 中,我已经散列了一个整数。如何在 Python 中也散列一个整数?
  • 你是否在 python 中禁用了哈希随机化? PYTHONHASHSEED=0 python YOURSCRIPT.py 这是为了避免 DOS 用例利用最差的哈希性能。
  • @Bobby 在 python 3 中,整数的默认哈希函数只会返回 123。搜索支持类似哈希函数的其他库可能是唯一的选择,因为 hashlib 只需要字符串或字节
  • 一个问题是Oracle不使用整数,他们使用自己的NUMBER类型,这是一种具有完全不同二进制表示的可变长度数据类型。在 Python 中实现它是一个有趣的挑战。 amitzil.wordpress.com/2015/03/24/…

标签: python oracle hash


【解决方案1】:

Oracle 以自己的内部格式表示数字,可以使用 Oracle 中的dump() 函数查看。例如,

SELECT dump(123) FROM dual;
Typ=2 Len=3: 194,2,24

因此,要在 Python 中散列一个数字并获得与 Oracle 中相同的结果,您需要将 Python 数字转换为一组字节,就像 Oracle 在其内部进行操作一样。

可以在here 找到对 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

【讨论】:

  • 这看起来很有希望,但是你能用 Python 中自包含的方式来编写它吗?作为 Python 函数,最好输入是要散列的数字,输出是类似于您所显示的散列值。我将无法访问 Oracle 中的原始值。
  • 嗯,不。这就是问题所在,不是吗?您需要一个 Python 实现 Oracle 用来生成 NUMBER 值的字节表示的任何逻辑。有一些关于这方面的信息。例如:amitzil.wordpress.com/2015/03/24/…。但是我不确定尝试实施这样的事情是否明智,因为它可能会在您下次升级 Oracle 时发生变化(即使您足够小心,甚至可以 100% 正确地开始逻辑)。
  • 我明白你的意思,这是很有价值的意见。也就是说,只有在 Python 中完全实现的答案才能回答我的问题。所以,我也会开始赏金,看看是否有帮助。
  • 我首先用 PL/SQL 编写了它,因此我能够针对 Oracle dump() 函数对其进行彻底测试。但是您仍然应该小心。例如,我现在才意识到 Python 版本永远不会停止。如果它有一个 200 个字符的字符串,它将生成一个超过 100 个字节的字节数组——远远超过 Oracle NUMBER 存储的字节数。
  • 不,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"

【讨论】:

    猜你喜欢
    • 2016-05-02
    • 1970-01-01
    • 1970-01-01
    • 2019-02-19
    • 1970-01-01
    • 2021-11-18
    • 2023-03-24
    • 2013-10-06
    • 1970-01-01
    相关资源
    最近更新 更多