【问题标题】:Algorithm to turn numeric IDs in to short, different alphanumeric codes将数字 ID 转换为不同字母数字代码的算法
【发布时间】:2019-12-28 15:28:15
【问题描述】:

我有来自数据库的 ID,我希望它们简短且易于通过肉眼区分(即,两个相近的数字看起来不同)。

像这样:

13892359163211 -> ALO2WE7
13992351216421 -> 52NBEK3

或类似的,算法。有点像哈希,除了它需要是可逆的?像 AES 这样的加密算法几乎是理想的,只是它的输出太长了。 (而且矫枉过正)。

我正在使用 Python (3),尽管我认为这并不重要

【问题讨论】:

  • 为什么要投反对票? “这个问题没有显示任何研究成果;它不清楚或没有用”。非常清晰有用,我已经研究过(参见:AES 和散列)
  • 要显着缩短它们需要在右侧使用比 A-Z0-9 更大的字母表。
  • 旁注,并不是我的问题的真正答案,而是另一种方法:您可以简单地生成随机字符串并将它们附加到数据库中的行/文档中。
  • 那很好,但您仍然需要使用不会产生重复的随机技术。

标签: python database encryption hash primary-key


【解决方案1】:

新答案“接近”的数字看起来不同

您可以使用 RSA 加密(然后解密)您的号码。这绝对是矫枉过正 - 但是......这是一个例子: 安装https://github.com/sybrenstuvel/python-rsa(pip install rsa)

import rsa
import rsa.core
# (pubkey, privkey) = rsa.newkeys(64) # Generate key pair
pubkey = rsa.PublicKey(n=9645943279888986023, e=65537)
privkey = rsa.PrivateKey(n=9645943279888986023, e=65537, d=7507666207464026273, p=9255782423, q=1042153201)

print("1st", rsa.core.encrypt_int(13892359163211, pubkey.e, pubkey.n))
print("2nd", rsa.core.encrypt_int(13992351216421, pubkey.e, pubkey.n))
print("1st", hex(rsa.core.encrypt_int(13892359163211, pubkey.e, pubkey.n))[2:])
print("2nd", hex(rsa.core.encrypt_int(13992351216421, pubkey.e, pubkey.n))[2:])

# If you want to compare a couple of numbers that are similar
for i in range (13892359163211, 13892359163251):
  encrypted = rsa.core.encrypt_int(i, pubkey.e, pubkey.n)
  # decrypted = rsa.core.decrypt_int(encrypted, privkey.d, privkey.n)
  print (i, hex(encrypted)[2:], encrypted)

请注意,您不能加密大于 pubkey.n 的数字。这是 RSA 相关的限制。通过生成具有更高n 的不同密钥对,您可以规避此问题。如果您希望所有生成的数字具有相同的长度,请在它们前面加上前导零。您还可以考虑将它们设为大写以提高可读性。要使显示的字符串更短,请考虑使用下面我的旧答案中提到的 base62 编码。

输出

1st 5427392181794576250
2nd 7543432434424555966
1st 4b51f86f0c99177a
2nd 68afa7d5110929be

input          hex(encrypted)   encrypted
13892359163211 4b51f86f0c99177a 5427392181794576250
13892359163212 2039f9a3f5cf5d46 2322161565485194566
13892359163213 173997b57918a6c3 1673535542221383363
13892359163214 36644663653bbb4  244958435527080884
13892359163215 c2eeec0c054e633  877901489011746355
...

旧答案 与显示数字有点短有关,不知道它们应该看起来有很大不同

您想将数字的底数从 10 更改为更大的数字以使用更少的字符。请参阅https://stackoverflow.com/a/1119769 以获取基数为 62 的示例 (a-zA-Z0-9)。

或以 16 为底的快速而肮脏的,(0-9A-F,十六进制)。

hex(13892359163211)[2:] # -> 'ca291220d4b'

【讨论】:

  • 这样做的问题是两个相似的十进制数字在十六进制中也看起来相似
  • 啊,我在问题中忽略了这一点。使用 RSA 更新。请考虑更新您的问题,以使此要求更加突出。 short and easily differentiatable by eye 并没有完全传达出我看起来完全不同的信息。
  • @JamesKPolk 完全正确。我的错。
【解决方案2】:

为输入找到crc32 并以十六进制显示结果如何?

>>> n = 13892359163211
>>> 
>>> import binascii
>>> hex(binascii.crc32(str(n).encode()))[2:]
'240a831a'

【讨论】:

  • a CRC32 仅对于
【解决方案3】:

将数字 ID 转换为二进制形式 (3) 并使用编码器 (4, 5)。

In [1]: import struct, base64

In [2]: i = 13892359163211
Out[2]: 13892359163211

In [3]: struct.pack('L', i)
Out[3]: b'K\r"\x91\xa2\x0c\x00\x00'

In [4]: base64.b85encode(struct.pack('L', i)).decode('ascii')
Out[4]: 'OAR8Cq6`24'

In [5]: base64.b64encode(struct.pack('L', i)).decode('ascii')[:-1]
Out[5]: 'Sw0ikaIMAAA'

使用哪种编码器取决于您要允许哪些字符。

【讨论】:

  • 这也解决不了问题!!!长度需要固定,不同的ID需要有本质区别!
  • 长度是固定的,因为转换为 8 字节二进制。唯一能想到的对相近的数字产生“明显不同”结果的算法是散列函数......
【解决方案4】:

这个问题说起来比解决起来容易。一种解决方案是从保留格式的加密中借鉴一些想法,但由于安全性不是目标而进行了简化。使用 Feistel 密码框架,可以编写一个非常短且可逆的“混合”函数,然后是一个短编码函数,以实现您想要的东西。

import hashlib
import string

mask = (1 << 22) - 1
alphabet = string.ascii_uppercase + string.digits


def func(x: int):
    return int.from_bytes(hashlib.sha256(x.to_bytes(3, 'big')).digest(), 'big') & mask


def mix(id_in: int):
    L, R = id_in >> 22, id_in & mask
    L ^= func(R)
    R ^= func(L)
    return (L << 22) | R


def unmix(mixed: int):
    L, R = mixed >> 22, mixed & mask
    R ^= func(L)
    L ^= func(R)
    return (L << 22) | R


def base_n_encode(value: int):
    digits = []
    for i in range(9):
        value, rem = divmod(value, len(alphabet))
        digits.insert(0, rem)
    return ''.join(alphabet[digit] for digit in digits)


def base_n_decode(encoded: str):
    digits = [alphabet.index(ch) for ch in encoded]
    result = 0
    for digit in digits:
        result = result * len(alphabet) + digit
    return result


def encode(id_in: int):
    return base_n_encode(mix(id_in))


def decode(encoded: str):
    return unmix(base_n_decode(encoded))


if __name__ == '__main__':
    e1 = encode(13892359163211)
    e2 = encode(13992351216421)
    print('13892359163211 -> ' + e1)
    print('13992351216421 -> ' + e2)
    print(e1 + ' -> ' + str(decode(e1)))
    print(e2 + ' -> ' + str(decode(e2)))

输出是:

13892359163211 -> BC33VXN8A
13992351216421 -> D1UOW6SLL
BC33VXN8A -> 13892359163211
D1UOW6SLL -> 13992351216421

注意使用 sha256。这很慢,而且绝对是矫枉过正,但它具有内置于 python 的优点,因此是单行的。除非您要转换数百万个 ID,否则速度应该不是问题,但如果是,您可以用更快的速度替换 func,也许是 Murmur3。

代码是用硬编码的常量编写的,以便更容易看到发生了什么,但它可以推广到使用任意长度(以位为单位)的 ID 和任意字母。

github 上提供了此示例的更通用版本。

【讨论】:

    【解决方案5】:

    您可以使用CrypIIidea 将整数转换为base64。这将是最短的

    • 13892359163211 是4LWL 和
    • 13992351216421 是64yl

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-03
      • 2016-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多