如何对两个十六进制字符串进行异或操作,使得每个字节都单独进行异或?

10

我最近在这里发布了几个类似的问题,但似乎我没有问对,如果我的异或问题让你感到疲惫,请原谅:D。

直截了当地说 - 我有两个十六进制字符串,我想将这些字符串进行异或运算,使得每个字节都被单独异或(即每一对数字都被单独异或)。我想在Python中实现这个功能,并且希望能够处理不同长度的字符串。我会手动做一个例子来说明我的观点(我使用代码环境是因为它允许我在想放置的位置插入空格):

Input:
s1 = "48656c6c6f"
s2 = "61736b"

Encoding in binary:
48 65 6c 6c 6f = 01001000 01100101 01101100 01101100 01101111
61 73 6b       = 01100001 01110011 01101011

XORing the strings:
01001000 01100101 01101100 01101100 01101111
                  01100001 01110011 01101011
                  00001101 00011111 00000100

Converting the result to hex:
00001101 00011111 00000100 = 0d 1f 04

Output:
0d1f04

总之,我希望能够输入两个十六进制字符串(通常是用十六进制编码的ASCII字母),这些字符串长度可相等也可不等,并获取它们的异或,使每个字节都单独进行异或。


1
为什么不循环使用s2键并将其应用于s1所有字符?为什么只应用于最后3个字符? - Martijn Pieters
3个回答

13

使用binascii.unhexlify()将您的十六进制字符串转换为二进制数据,然后使用XOR运算,再使用binascii.hexlify()将其转换回十六进制:

>>> from binascii import unhexlify, hexlify
>>> s1 = "48656c6c6f"
>>> s2 = "61736b"
>>> hexlify(''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(unhexlify(s1[-len(s2):]), unhexlify(s2))))
'0d1f04'

实际上,按字节对解码数据应用异或运算(使用ord()chr()将整数转换为字符和从字符转换为整数)。

请注意,与您的示例一样,我截断了s1,使其与s2具有相同的长度(忽略来自s1开头的字符)。您可以通过循环字节来使用更短的密钥s2编码所有s1

>>> from itertools import cycle
>>> hexlify(''.join(chr(ord(c1) ^ ord(c2)) for c1, c2 in zip(unhexlify(s1), cycle(unhexlify(s2)))))
'2916070d1c'

您不必使用unhexlify(),但它比一次两个字符地循环s1s2并使用int(twocharacters, 16)将其转换为整数值进行异或运算要容易得多。

以上Python 3版本稍微轻便一些;使用bytes()代替str.join(),您可以直接迭代整数并省略chr()ord()调用:

>>> from binascii import unhexlify, hexlify
>>> s1 = "48656c6c6f"
>>> s2 = "61736b"
>>> hexlify(bytes(c1 ^ c2 for c1, c2 in zip(unhexlify(s1[-len(s2):]), unhexlify(s2)))) 
b'0d1f04'
>>> from itertools import cycle
>>> hexlify(bytes(c1 ^ c2 for c1, c2 in zip(unhexlify(s1), cycle(unhexlify(s2)))))
b'2916070d1c'

1
谢谢你的回答!我不理解的是这部分代码:for c1, c2 in zip(unhexlify(s1), cycle(unhexlify(s2)))))问题是,我是 Python 的新手,很容易感到困惑。我认为 zip 函数与数组有关,但我不知道在 Python 中如何使用它们。此外,我认为 for 循环只有一个计数器,但在这里你有两个 - c1 和 c2,这也让我感到困惑。 - user2535982
1
zip() 接受多个输入序列,并将它们的元素配对。因此,它会给你一个由 [(s1[0], s2[0]), (s1[1], s2[1]), ...] 组成的序列,每个元组都是在相同索引处从每个输入序列中取出的元素。 - Martijn Pieters
1
@NorsulRonsul: 因为在这种情况下,zip()函数有两个输入列表,每个输出元素都是一个具有两个值的元组。for循环将它们解包成两个值(就像在常规赋值中可以进行元组解包,foo, bar = ('spam', 'eggs')foo='spam'bar='eggs'赋值)。 - Martijn Pieters
1
我现在明白了,但是我现在有另一个问题:D。我不理解你在第二部分做了什么。编码s1的所有内容是什么意思?循环字节又是什么意思? - user2535982
1
@NorsulRonsul:我们不仅使用密钥中的3个字符('Hello'中的'llo')对其进行编码,而是通过重复使用密钥来对整个Hello进行编码。当到达末尾时,从开头重新开始;将Ha匹配,将es匹配,将lk匹配,然后循环到开头并将la匹配,将os匹配。itertools.cycle()方法让我们只需一个函数就能实现对s2的循环。 - Martijn Pieters
答案已接受!我是新手,所以不知道这一切是如何运作的 :D - user2535982

6
我找到了一个非常简单的解决方案:
def xor_str(a,b):
    result = int(a, 16) ^ int(b, 16) # convert to integers and xor them
    return '{:x}'.format(result)     # convert back to hexadecimal

它将对字符串执行异或操作,直到其中一个字符串结束。

1
当人们不使用内置格式时,我总是感到很困扰。return format(result, 'x') - Veky

1

我不确定你正在寻找什么,但希望这对你有用。

>>> def getstr(encoded):
     return "".join([chr(int(i+k, 16))for (i,k) in zip(encoded[0::2], encoded[1::2])])

>>> getstr(s1)
'Hello'

>>> getstr(s2)
'ask'

从两个普通字符串开始,你可以通过以下方式找到你的结果:

>>> "".join(reversed(["%02X" % (ord(c1) ^ ord(c2)) for c1, c2 in zip(reversed(getstr(s1)),       reversed(getstr(s2)))]))
'0D1F04'

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