Konwertuj python long/int na tablicę bajtów o stałym rozmiarze

Próbuję zaimplementować wymianę kluczy RC4 i DH w Pythonie. Problem polega na tym, że nie mam pojęcia jak przekonwertować Python long/int z wymiany kluczy na tablicę bajtów potrzebną do implementacji RC4. Czy istnieje prosty sposób na przekonwertowanie tablicy long na wymaganą tablicę bajtów długości?

Update: zapomniałem wspomnieć, że liczby, z którymi mam do czynienia, to 768-bitowe niepodpisane liczby całkowite.

Author: skaffman, 2012-01-04

10 answers

Nie zrobiłem żadnych benchmarków, ale ten przepis "działa na mnie".

Skrócona wersja: użyj '%x' % val, a następnie unhexlify wyniku. Diabeł tkwi w szczegółach, ponieważ unhexlify wymaga parzystej liczby cyfr szesnastkowych, której %x nie gwarantuje. Zobacz docstring i liberalne komentarze inline po szczegóły.

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

...i moje nosetest testy jednostkowe; -)

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'
 20
Author: Tripp Lilley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-01-25 17:18:19

W Pythonie 3.2 i nowszych można używać int.to_bytes i int.from_bytes: https://docs.python.org/3/library/stdtypes.html#int.to_bytes

 53
Author: Jack O'Connor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-20 23:39:21

Każdy ma tę odpowiedź:

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

Musisz tylko znać liczbę bajtów, które próbujesz przekonwertować. W moich przypadkach użycia zwykle używam tylko tej dużej liczby dla krypto, a w tym momencie muszę się martwić o moduł i co-nie, więc nie sądzę, że jest to duży problem, aby znać maksymalną liczbę bajtów do powrotu.

Ponieważ robisz to jako 768-bitową matematykę, to zamiast 32 jako argumentu będzie to 96.

 21
Author: sparticvs,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-14 15:22:04

One-liner:

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

192 to 768 / 4, ponieważ OP chciał 768-bitowych liczb i są 4 bity w cyfrze szesnastkowej. Jeśli potrzebujesz większego bytearray, Użyj formatu z większą liczbą. Przykład:

>>> 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')

[moja odpowiedź używała hex() wcześniej. Poprawiłem to za pomocą format(), Aby Obsługiwać ints z wyrażeniami bajtowymi o nieparzystych rozmiarach. Rozwiązuje to wcześniejsze skargi dotyczące ValueError.]

 14
Author: Jess Austin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-31 23:23:15

Możesz spróbować użyć struct :

import struct
struct.pack('L',longvalue)
 7
Author: Eduardo Ivanec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 17:31:59

long / int do tablicy bajtów wygląda jak dokładny cel struct.pack. W przypadku długich liczb całkowitych, które przekraczają 4 (8) bajty, można wymyślić coś podobnego do następnego:

>>> 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
 7
Author: Roman Bodnarchuk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 18:16:38

Little-endian, Odwróć wynik lub zakres, jeśli chcesz Big-endian:

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

Big-endian:

def int_to_bytes(val, num_bytes):
    return [(val & (0xff << pos*8)) >> pos*8 for pos in reversed(range(num_bytes))]
 7
Author: scornwell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-06 02:09:10

Zasadniczo to, co musisz zrobić, to przekształcić int / long w jego reprezentację bazową 256 - tzn. liczbę, której "cyfry" mieszczą się w zakresie od 0-255. Oto dość skuteczny sposób na zrobienie czegoś takiego:

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

Ty wielu nie potrzebujesz reversed() wywołanie w zależności od endian-ness pożądane (robiąc to wymagałoby wypełnienia być wykonane inaczej, jak również). Zauważ również, że tak jak napisano, nie obsługuje liczb ujemnych.

Możesz też rzucić okiem na podobne, ale wysoce Zoptymalizowana funkcja long_to_bytes() w module number.py, który jest częścią open source Python Cryptography Toolkit . W rzeczywistości Konwertuje liczbę na łańcuch znaków, a nie tablicę bajtów, ale jest to drobny problem.

 3
Author: martineau,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-21 09:21:17

Python 2.7 nie implementuje int.to-metoda very slow_bytes ().

Wypróbowałem 3 metody:

  1. hex rozpakuj / pack: bardzo wolno
  2. przesunięcie bajtów 8 bitów na raz: znacznie szybciej.
  3. używanie modułu "C" i pakowanie do niższych (7 IA64 lub 3 I32) bajtów. Było to około dwa razy szybciej niż 2/. Jest to najszybsza opcja, ale wciąż zbyt wolna.

Wszystkie te metody są bardzo nieefektywne z dwóch powodów:

  • Python 2.7 nie obsługuje ta użyteczna operacja.
  • c nie obsługuje rozszerzonej arytmetyki precyzyjnej przy użyciu FLAG carry/borrow / overflow dostępnych na większości platform.
 2
Author: GP Eckersley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-20 01:18:16
i = 0x12345678
s = struct.pack('<I',i)
b = struct.unpack('BBBB',s)
 0
Author: user1025874,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-14 12:11:15