Конвертировать Python long / int в байтовый массив фиксированного размера - PullRequest
39 голосов
/ 04 января 2012

Я пытаюсь реализовать обмен ключами RC4 и DH в python.Проблема в том, что я понятия не имею о том, как преобразовать python long / int из обмена ключами в байтовый массив, который мне нужен для реализации RC4.Есть ли простой способ преобразовать байты в массив байтов требуемой длины?

Обновление : забыл упомянуть, что числа, с которыми я имею дело, представляют собой 768-битные целые числа без знака.

Ответы [ 10 ]

42 голосов
/ 21 января 2015

В Python 3.2 и более поздних версиях вы можете использовать int.to_bytes и int.from_bytes: https://docs.python.org/3/library/stdtypes.html#int.to_bytes

18 голосов
/ 25 января 2013

Я не делал никаких тестов, но этот рецепт «работает для меня».

Короткая версия: используйте '%x' % val, затем unhexlify результат.Дьявол кроется в деталях, так как unhexlify требует четного числа шестнадцатеричных цифр, что %x не гарантирует.См. Подробности в документации и комментарии к нему.

from binascii import unhexlify

def long_to_bytes (val, endianness='big'):
    """
    Use :ref:`string formatting` and :func:`~binascii.unhexlify` to
    convert ``val``, a :func:`long`, to a byte :func:`str`.

    :param long val: The value to pack

    :param str endianness: The endianness of the result. ``'big'`` for
      big-endian, ``'little'`` for little-endian.

    If you want byte- and word-ordering to differ, you're on your own.

    Using :ref:`string formatting` lets us use Python's C innards.
    """

    # one (1) hex digit per four (4) bits
    width = val.bit_length()

    # unhexlify wants an even multiple of eight (8) bits, but we don't
    # want more digits than we need (hence the ternary-ish 'or')
    width += 8 - ((width % 8) or 8)

    # format width specifier: four (4) bits per hex digit
    fmt = '%%0%dx' % (width // 4)

    # prepend zero (0) to the width, to zero-pad the output
    s = unhexlify(fmt % val)

    if endianness == 'little':
        # see http://stackoverflow.com/a/931095/309233
        s = s[::-1]

    return s

... и мои тест на нос модульные тесты; -)

class TestHelpers (object):
    def test_long_to_bytes_big_endian_small_even (self):
        s = long_to_bytes(0x42)
        assert s == '\x42'

        s = long_to_bytes(0xFF)
        assert s == '\xff'

    def test_long_to_bytes_big_endian_small_odd (self):
        s = long_to_bytes(0x1FF)
        assert s == '\x01\xff'

        s = long_to_bytes(0x201FF)
        assert s == '\x02\x01\xff'

    def test_long_to_bytes_big_endian_large_even (self):
        s = long_to_bytes(0xab23456c8901234567)
        assert s == '\xab\x23\x45\x6c\x89\x01\x23\x45\x67'

    def test_long_to_bytes_big_endian_large_odd (self):
        s = long_to_bytes(0x12345678901234567)
        assert s == '\x01\x23\x45\x67\x89\x01\x23\x45\x67'

    def test_long_to_bytes_little_endian_small_even (self):
        s = long_to_bytes(0x42, 'little')
        assert s == '\x42'

        s = long_to_bytes(0xFF, 'little')
        assert s == '\xff'

    def test_long_to_bytes_little_endian_small_odd (self):
        s = long_to_bytes(0x1FF, 'little')
        assert s == '\xff\x01'

        s = long_to_bytes(0x201FF, 'little')
        assert s == '\xff\x01\x02'

    def test_long_to_bytes_little_endian_large_even (self):
        s = long_to_bytes(0xab23456c8901234567, 'little')
        assert s == '\x67\x45\x23\x01\x89\x6c\x45\x23\xab'

    def test_long_to_bytes_little_endian_large_odd (self):
        s = long_to_bytes(0x12345678901234567, 'little')
        assert s == '\x67\x45\x23\x01\x89\x67\x45\x23\x01'
14 голосов
/ 30 июля 2013

Однострочный:

bytearray.fromhex('{:0192x}'.format(big_int))

Значение 192 равно 768/4, потому что OP хотел 768-битные числа, а в шестнадцатеричной цифре 4 бит.Если вам нужно больше bytearray, используйте строку формата с большим числом.Пример:

>>> big_int = 911085911092802609795174074963333909087482261102921406113936886764014693975052768158290106460018649707059449553895568111944093294751504971131180816868149233377773327312327573120920667381269572962606994373889233844814776702037586419
>>> bytearray.fromhex('{:0192x}'.format(big_int))
bytearray(b'\x96;h^\xdbJ\x8f3obL\x9c\xc2\xb0-\x9e\xa4Sj-\xf6i\xc1\x9e\x97\x94\x85M\x1d\x93\x10\\\x81\xc2\x89\xcd\xe0a\xc0D\x81v\xdf\xed\xa9\xc1\x83p\xdbU\xf1\xd0\xfeR)\xce\x07\xdepM\x88\xcc\x7fv\\\x1c\x8di\x87N\x00\x8d\xa8\xbd[<\xdf\xaf\x13z:H\xed\xc2)\xa4\x1e\x0f\xa7\x92\xa7\xc6\x16\x86\xf1\xf3')
>>> lepi_int = 0x963b685edb4a8f336f624c9cc2b02d9ea4536a2df669c19e9794854d1d93105c81c289cde061c0448176dfeda9c18370db55f1d0fe5229ce07de704d88cc7f765c1c8d69874e008da8bd5b3cdfaf137a3a48edc229a41e0fa792a7c61686f1f
>>> bytearray.fromhex('{:0192x}'.format(lepi_int))
bytearray(b'\tc\xb6\x85\xed\xb4\xa8\xf36\xf6$\xc9\xcc+\x02\xd9\xeaE6\xa2\xdff\x9c\x19\xe9yHT\xd1\xd91\x05\xc8\x1c(\x9c\xde\x06\x1c\x04H\x17m\xfe\xda\x9c\x187\r\xb5_\x1d\x0f\xe5"\x9c\xe0}\xe7\x04\xd8\x8c\xc7\xf7e\xc1\xc8\xd6\x98t\xe0\x08\xda\x8b\xd5\xb3\xcd\xfa\xf17\xa3\xa4\x8e\xdc"\x9aA\xe0\xfay*|aho\x1f')

[Мой ответ раньше использовал hex().Я исправил это с format(), чтобы обрабатывать целые числа с выражениями байтов нечетного размера.Это исправляет предыдущие жалобы на ValueError.]

13 голосов
/ 14 августа 2015

Все усложнили этот ответ:

some_int = <256 bit integer>
some_bytes = some_int.to_bytes(32, sys.byteorder)
my_bytearray = bytearray(some_bytes)

Вам просто нужно знать количество байтов, которые вы пытаетесь преобразовать. В моих случаях использования обычно я использую это большое число только для криптографии, и в этот момент я должен беспокоиться о модуле и что-нет, так что я не думаю, что это большая проблема, чтобы узнать максимальное число байтов для возврата.

Поскольку вы делаете это как 768-битную математику, то вместо 32 в качестве аргумента это будет 96.

7 голосов
/ 04 января 2012

long / int для байтового массива выглядит как точное назначение struct.pack. Для длинных целых чисел, которые превышают 4 (8) байтов, вы можете получить что-то вроде следующего:

>>> limit = 256*256*256*256 - 1
>>> i = 1234567890987654321
>>> parts = []
>>> while i:
        parts.append(i & limit)
        i >>= 32

>>> struct.pack('>' + 'L'*len(parts), *parts )
'\xb1l\x1c\xb1\x11"\x10\xf4'

>>> struct.unpack('>LL', '\xb1l\x1c\xb1\x11"\x10\xf4')
(2976652465L, 287445236)
>>> (287445236L << 32) + 2976652465L
1234567890987654321L
6 голосов
/ 04 января 2012

Вы можете попробовать использовать struct :

import struct
struct.pack('L',longvalue)
5 голосов
/ 31 января 2013

Little-endian, измените результат или диапазон, если хотите Big-endian:

def int_to_bytes(val, num_bytes):
    return [(val & (0xff << pos*8)) >> pos*8 for pos in range(num_bytes)]

тупоконечник:

def int_to_bytes(val, num_bytes):
    return [(val & (0xff << pos*8)) >> pos*8 for pos in reversed(range(num_bytes))]
3 голосов
/ 05 января 2012

По сути, вам нужно преобразовать int / long в его базовое представление 256 - то есть число, чьи "цифры" находятся в диапазоне 0-255.Вот довольно эффективный способ сделать что-то подобное:

def base256_encode(n, minwidth=0): # int/long to byte array
    if n > 0:
        arr = []
        while n:
            n, rem = divmod(n, 256)
            arr.append(rem)
        b = bytearray(reversed(arr))
    elif n == 0:
        b = bytearray(b'\x00')
    else:
        raise ValueError

    if minwidth > 0 and len(b) < minwidth: # zero padding needed?
        b = (minwidth-len(b)) * '\x00' + b
    return b

Многие не нуждаются в вызове reversed() в зависимости от желаемого порядка байтов (для этого потребуется заполнение также по-другому),Также обратите внимание, что как написано, он не обрабатывает отрицательные числа.

Возможно, вы также захотите взглянуть на аналогичную, но высоко оптимизированную функцию long_to_bytes() в модуле number.py, который является частью открытого источника Python Cryptography Toolkit .Фактически он преобразует число в строку, а не в байтовый массив, но это незначительная проблема.

2 голосов
/ 20 октября 2015

Python 2.7 не реализует метод int.to-ly slow_bytes ().

Я пробовал 3 метода:

  1. шестнадцатеричный распаковать / упаковать: очень медленно
  2. смещение байтов 8 бит за раз: значительно быстрее.
  3. с использованием модуля "C" и упаковки в младшие (7 ia64 или 3 i32) байты. Это было примерно в два раза быстрее, чем 2 /. Это самый быстрый вариант, но все еще слишком медленный.

Все эти методы очень неэффективны по двум причинам:

  • Python 2.7 не поддерживает эту полезную операцию.
  • c не поддерживает расширенную арифметику с использованием флагов переноса / заимствования / переполнения, доступных на большинстве платформ.
0 голосов
/ 14 ноября 2017
i = 0x12345678
s = struct.pack('<I',i)
b = struct.unpack('BBBB',s)
...