web-dev-qa-db-fra.com

opérations d'octets (XOR) dans python

    #!/usr/bin/env python3

import binascii


var=binascii.a2b_qp("hello")
key=binascii.a2b_qp("supersecretkey")[:len(var)]

print(binascii.b2a_qp(var))
print(binascii.b2a_qp(key))


#here i want to do an XOR operation on the bytes in var and key and place them in 'encryption': encryption=var XOR key

print(binascii.b2a_qp(encrypted))

Si quelqu'un pouvait m'éclairer sur la façon dont je pouvais accomplir cela, je serais très heureux. Très nouveau pour toutes les conversions de type de données, alors oui ... la lecture du wiki python n'est pas aussi claire que je le voudrais :(.

11
Jcov

Il semble que ce que vous devez faire est XOR chacun des caractères du message avec le caractère correspondant dans la clé. Cependant, pour ce faire, vous avez besoin d'un peu d'interconversion en utilisant ord et chr, car vous ne pouvez que des nombres xor, pas des chaînes:

>>> encrypted = [ chr(ord(a) ^ ord(b)) for (a,b) in Zip(var, key) ] 
>>> encrypted
['\x1b', '\x10', '\x1c', '\t', '\x1d']

>>> decrypted = [ chr(ord(a) ^ ord(b)) for (a,b) in Zip(encrypted, key) ]
>>> decrypted
['h', 'e', 'l', 'l', 'o']

>>> "".join(decrypted)
'hello'

Notez que binascii.a2b_qp("hello") convertit simplement une chaîne en une autre chaîne (mais éventuellement avec un codage différent).

Votre approche et mon code ci-dessus ne fonctionneront que si la clé est au moins aussi longue que le message. Cependant, vous pouvez facilement répéter la clé si nécessaire en utilisant itertools.cycle:

>>> from itertools import cycle
>>> var="hello"
>>> key="xy"

>>> encrypted = [ chr(ord(a) ^ ord(b)) for (a,b) in Zip(var, cycle(key)) ]
>>> encrypted
['\x10', '\x1c', '\x14', '\x15', '\x17']

>>> decrypted = [ chr(ord(a) ^ ord(b)) for (a,b) in Zip(encrypted, cycle(key)) ]
>>> "".join(decrypted)
'hello'

Pour résoudre le problème des caractères unicode/multi-octets (soulevé dans les commentaires ci-dessous), on peut convertir la chaîne (et la clé) en octets, les compresser ensemble, puis effectuer le XOR, quelque chose comme:

>>> var=u"hello\u2764"
>>> var
'hello❤'

>>> encrypted = [ a ^ b for (a,b) in Zip(bytes(var, 'utf-8'),cycle(bytes(key, 'utf-8'))) ]
>>> encrypted
[27, 16, 28, 9, 29, 145, 248, 199]

>>> decrypted = [ a ^ b for (a,b) in Zip(bytes(encrypted), cycle(bytes(key, 'utf-8'))) ]
>>> decrypted
[104, 101, 108, 108, 111, 226, 157, 164]

>>> bytes(decrypted)
b'hello\xe2\x9d\xa4'

>>> bytes(decrypted).decode()
'hello❤'
17
DNA

Comparaison de deux solutions python3

Le premier est basé sur Zip :

def encrypt1(var, key):
    return bytes(a ^ b for a, b in Zip(var, key))

Le second utilise int.from_bytes et int.to_bytes :

def encrypt2(var, key):
    key = key[:len(var)]
    int_var = int.from_bytes(var, sys.byteorder)
    int_key = int.from_bytes(key, sys.byteorder)
    int_enc = int_var ^ int_key
    return int_enc.to_bytes(len(var), sys.byteorder)

Tests simples:

assert encrypt1(b'hello', b'supersecretkey') == b'\x1b\x10\x1c\t\x1d'
assert encrypt2(b'hello', b'supersecretkey') == b'\x1b\x10\x1c\t\x1d'

Tests de performances avec var et key faisant 1000 octets de long:

$ python3 -m timeit \
  -s "import test_xor;a=b'abcdefghij'*100;b=b'0123456789'*100" \
  "test_xor.encrypt1(a, b)"
10000 loops, best of 3: 100 usec per loop

$ python3 -m timeit \
  -s "import test_xor;a=b'abcdefghij'*100;b=b'0123456789'*100" \
  "test_xor.encrypt2(a, b)"
100000 loops, best of 3: 5.1 usec per loop

L'approche entière semble être beaucoup plus rapide.

24
Vincent