如何在Python中将长整型编码为Base64?

9
在Java中,我可以将BigInteger编码为:
java.math.BigInteger bi = new java.math.BigInteger("65537L");
String encoded = Base64.encodeBytes(bi.toByteArray(), Base64.ENCODE|Base64.DONT_GUNZIP);

// result: 65537L encodes as "AQAB" in Base64

byte[] decoded = Base64.decode(encoded, Base64.DECODE|Base64.DONT_GUNZIP);
java.math.BigInteger back = new java.math.BigInteger(decoded);

在C#中:

System.Numerics.BigInteger bi = new System.Numerics.BigInteger("65537L");
string encoded = Convert.ToBase64(bi);
byte[] decoded = Convert.FromBase64String(encoded);
System.Numerics.BigInteger back = new System.Numerics.BigInteger(decoded);

我该如何在Python中将长整数编码为Base64编码的字符串?到目前为止,我尝试过的方法与其他语言(如Java和C#)的实现产生了不同的结果,特别是它会产生更长的Base64编码字符串。

import struct
encoded = struct.pack('I', (1<<16)+1).encode('base64')[:-1]
# produces a longer string, 'AQABAA==' instead of the expected 'AQAB'

使用这段Python代码生成Base64编码字符串时,在Java中解码的整数结果(例如)会产生16777472而不是预期的65537。首先,我错过了什么?
其次,我必须手动确定在struct.pack中要使用的长度格式;如果我尝试编码一个长数字(大于(1<<64)-1),则'Q'格式规范太短以包含表示。这是否意味着我必须手动进行表示,还是struct.pack函数有一个未记录的格式说明符?(我不一定要使用struct,但乍一看它似乎可以做到我所需的。)

我猜这与填充有关,Python的输出是填充的,而C#和Java则没有填充。 - Saddam Abu Ghaida
@SaddamAbuGhaida 是的,struct.pack 的二进制表示具有尾随的\x00字符,这会产生额外的填充。这是否意味着我必须手动修剪额外的填充? - jbatista
1
是的,你需要对它进行去除操作。 - Saddam Abu Ghaida
2
你的 << 方向错误。 - John La Rooy
1
试着查看十六进制的数字:hex(16777472)0x1000100,而 hex(65537)0x10001。这是否有助于你弄清楚发生了什么? - abarnert
@gnibbler 谢谢,我已经修复了。顺便说一下,你肯定知道在达到一定的积分后,你可以编辑其他人的帖子。如果你主动修复它,我不会介意,但我很感激你指出了这个问题。 - jbatista
5个回答

7

请查看这个页面,了解如何在Python中将整数转换为Base64。

import base64
import struct

def encode(n):
    data = struct.pack('<Q', n).rstrip('\x00')
    if len(data)==0:
        data = '\x00'
    s = base64.urlsafe_b64encode(data).rstrip('=')
    return s

def decode(s):
    data = base64.urlsafe_b64decode(s + '==')
    n = struct.unpack('<Q', data + '\x00'* (8-len(data)) )
    return n[0]

3
这对于大于1<<63的数字不起作用,而OP特别要求这个。 - abarnert
3
这段代码本身不起作用。但是,如果将“'x00'”更改为“'\ x00'”,它就会起作用。我认为博客软件可能已经处理掉了反斜杠。这些应该是字符0的常量。你也可以在那里放置“chr(0)”。 - steveha
@msc 哎呀!我不知道有 rstrip 这个函数。谢谢提醒。 - jbatista

6

struct模块

…执行Python值和C结构之间的转换,这些结构表示为Python字符串。

由于C没有无限长度的整数,因此没有将它们打包的功能。

但是编写自己的打包功能非常容易。例如:

def pack_bigint(i):
    b = bytearray()
    while i:
        b.append(i & 0xFF)
        i >>= 8
    return b

或者:

def pack_bigint(i):
    bl = (i.bit_length() + 7) // 8
    fmt = '<{}B'.format(bl)
    # ...

接下来是一些相关的内容。

当然,你会需要一个unpack函数,就像评论中jbatista的函数:

def unpack_bigint(b):
    b = bytearray(b) # in case you're passing in a bytes/str
    return sum((1 << (bi*8)) * bb for (bi, bb) in enumerate(b))

谢谢!通过你的第一个示例,我可以做出这样的事情:bi = bytes(pack_bigint(i)); b64bi = base64.standard_b64encode(bi); - jbatista
@jbatista:base64.standard_b64encode 是否可以直接用于 bytearray,这样你就不需要进行 bytes 转换了吗?还是说只有在3.x版本中才行? - abarnert
我已经在Python 2.6.6中尝试过。base64.standard_b64encode不能与bytearray一起使用(它会产生一个TypeError),但如果我先用bytes()将返回的bytearray包围起来,它就可以工作了。 - jbatista
1
为了补充你的答案:def unpack_bigint(b): n=0L; for bi,bb in enumerate(b): n+=(1<<(bi*8))*ord(bb); return n; - jbatista
1
@jbatista:是的,我可能应该给一个pack和一个unpack,而不是1-1/2个pack...我会把你的加到答案里。但是你也可以再次使用bytearray,这样你就不需要在每个字符上使用ord。(如果你有非常大的大整数,这可能是浪费的——但是OP已经将pack中的bytearray转换为bytes了。) - abarnert
显示剩余2条评论

1
这有点晚了,但我想我会加入这个行列:
def inttob64(n):                                                              
    """                                                                       
    Given an integer returns the base64 encoded version of it (no trailing ==)
    """
    parts = []                                                                
    while n:                                                                  
        parts.insert(0,n & limit)                                             
        n >>= 32                                                              
    data = struct.pack('>' + 'L'*len(parts),*parts)                           
    s = base64.urlsafe_b64encode(data).rstrip('=')                            
    return s                                                                  

def b64toint(s):                                                              
    """                                                                       
    Given a string with a base64 encoded value, return the integer representation
    of it                                                                     
    """                                                                       
    data = base64.urlsafe_b64decode(s + '==')                                 
    n = 0                                                                     
    while data:                                                               
        n <<= 32                                                              
        (toor,) = struct.unpack('>L',data[:4])                                
        n |= toor & 0xffffffff                                                
        data = data[4:]                                                       
    return n

这些函数将任意大小的长整数转换为/从大端序的base64表示形式。

1
我假设 inttob64 中的“limit”为 0xffffffff? - Tatu Lahtela

1

int.to_bytes 在 Python 3.2 之后添加

import base64

def int_to_b64(n: int) -> str:
    bytes_length = (n.bit_length() + 7) // 8
    return base64.urlsafe_b64encode(n.to_bytes(bytes_length, 'big')).decode().rstrip('=')

def b64_to_int(b64: str) -> int:
    data = base64.urlsafe_b64decode(b64 + '==')
    return int.from_bytes(data, 'big')

0

这里有一些可能会有所帮助的内容。与其使用struct.pack(),我正在构建一个字节字符串进行编码,然后调用BASE64对其进行编码。我没有编写解码,但显然解码可以恢复相同的字节字符串,并且循环可以恢复原始值。我不知道您是否需要固定大小的整数(例如总是128位),也不知道您是否需要大端字节序,因此我将解码器留给您。

此外,encode64()decode64()来自@msc的答案,但经过修改可正常工作。

import base64
import struct

def encode64(n):
  data = struct.pack('<Q', n).rstrip('\x00')
  if len(data)==0:
    data = '\x00'
  s = base64.urlsafe_b64encode(data).rstrip('=')
  return s

def decode64(s):
  data = base64.urlsafe_b64decode(s + '==')
  n = struct.unpack('<Q', data + '\x00'* (8-len(data)) )
  return n[0]

def encode(n, big_endian=False):
    lst = []
    while True:
        n, lsb = divmod(n, 0x100)
        lst.append(chr(lsb))
        if not n:
            break
    if big_endian:
        # I have not tested Big Endian mode, and it may need to have
        # some initial zero bytes prepended; like, if the integer is
        # supposed to be a 128-bit integer, and you encode a 1, you
        # would need this to have 15 leading zero bytes.
        initial_zero_bytes = '\x00' * 2
        data = initial_zero_bytes + ''.join(reversed(lst))
    else:
        data = ''.join(lst)
    s = base64.urlsafe_b64encode(data).rstrip('=')
    return s

print encode(1234567890098765432112345678900987654321)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接