2012-01-04 102 views
30

我試圖在python中實現RC4和DH密鑰交換。問題是我不知道如何將密鑰交換中的python long/int轉換爲RC4實現所需的字節數組。有沒有一種簡單的方法將long轉換爲所需長度的字節數組?將python long/int轉換爲固定大小的字節數組

更新:忘記提及我正在處理的數字是768位無符號整數。

+0

不知道這是否會幫助,但檢查'struct'模塊:http://docs.python.org/library/struct.html – 2012-01-04 17:34:13

+1

有多大你的號碼? – interjay 2012-01-04 17:41:44

回答

15

我還沒有做任何基準測試,但這個配方「適合我」。

簡短版本:使用'%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 

...我nosetest單元測試;-)

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(0xab23456c89) 
     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(0x123456789) 
     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(0xab23456c89, '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(0x123456789, 'little') 
     assert s == '\x67\x45\x23\x01\x89\x67\x45\x23\x01' 
+0

當值爲0時遇到問題(Python 3.5)'''binascii.Error:奇數長度字符串''',快速修復:如果fmt%val =='0',用'''s = unhexlify('00')替換'''s = unhexlify(fmt%val)'else unhexlify(fmt% val)''' – Kevin 2016-11-30 14:05:23

5

您可以嘗試使用struct

import struct 
struct.pack('L',longvalue) 
+1

可悲的是,錯誤:'L'格式代碼的整數超出範圍。它的長度爲768位,比4字節的unsigned int大得多。 – cdecker 2012-01-04 18:14:54

+1

Downvoted是因爲Python long int是任意長整數。把它想象成32位(或其他)整數的數組。 C long是大小定義的數據類型。有了這個迴應,你就會混淆兩者。 – Havok 2016-01-15 22:04:28

7

長/ 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 
+3

但是它對大數字(> 8字節)沒有幫助,通常用於加密應用程序。 – interjay 2012-01-04 17:48:13

+0

它被寫爲不是通用的,但更像是固定大小的解決方案,以表示所有可能的IP或類似的常見問題... – bigkahunaburger 2016-09-08 21:38:50

3

基本上你需要做的是轉換INT /長入其基體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()呼叫取決於字節序所需的(這樣做需要填充做不同以及)。另外請注意,正如它所寫的,它不處理負數。

您可能還想看看number.py模塊中類似但高度優化的long_to_bytes()函數,該函數是開源Python Cryptography Toolkit的一部分。它實際上將數字轉換爲一個字符串,而不是字節數組,但這是一個小問題。

3

小端,如果你想大端逆轉的結果或範圍。

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

一行代碼:

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的投訴。]

+0

它不起作用,如果你不產生一個龍雖然。我認爲smt像 bytearray.fromhex(十六進制(2 ** 61-1).strip('0x')。strip('L'))更安全 – 2014-07-07 09:30:29

+0

@MarioAlemi評論中的代碼是錯誤的。 strip('0x')'也會去掉尾部的零,這會導致錯誤的結果(有時候也會產生'ValueError')! – Lepi 2014-12-05 18:07:24

+0

@Jess Austin:你的解決方案是完全錯誤的,因爲它僅在x由偶數個十六進制數組成時才起作用。 實施例: 'X = 0x963b685edb4a8f336f624c9cc2b02d9ea4536a2df669c19e9794854d1d93105c81c289cde061c0448176dfeda9c18370db55f1d0fe5229ce07de704d88cc7f765c1c8d69874e008da8bd5b3cdfaf137a3a48edc229a41e0fa792a7c61686f1fL' – Lepi 2014-12-05 18:26:06

6

大家都過於複雜的這樣的回答:

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

你只需要知道你正試圖轉換的字節數。在我的用例中,通常我只使用這個大數字作爲加密函數,那時我不得不擔心模數和不是什麼,所以我不認爲這是需要知道最大數量的大問題的字節返回。

既然你正在做它爲768位數學,然後代替32作爲自變量,它是96

+0

在Python 3此解決方案的工作非常以及2048位整數。它的Python 2.7只適用於int(Python 2.7中的2048位整數很長)。 – desowin 2016-05-21 13:50:56

+1

在Python 2.7'some_bytes = some_int.to_bytes(32,sys.byteorder)'產生錯誤'AttributeError的: 'INT' 對象沒有屬性「to_bytes'' – olibre 2017-08-09 13:07:23

2

的Python 2.7沒有實現int.to-非常slow_bytes()方法。

我試圖3種方法:

  1. 六角解包/包:非常慢
  2. 字節在一個時間移位8位:顯著更快。
  3. 使用「C」模塊並裝入較低(7 ia64或3 i32)字節。這大約是2/2的兩倍。這是最快的選擇,但仍然太慢。

所有這些方法的原因有兩個非常低效:

  • Python 2.7版不支持此有用的操作。
  • c不支持使用大多數平臺上可用的進位/借位/溢出標誌的擴展精度算術。
0
i = 0x12345678 
s = struct.pack('<I',i) 
b = struct.unpack('BBBB',s)