Python位操作和数据校验与加密
二进制
按位计算都是在二进制上进行的。
十进制的位是个十、百、千、万、十万、百万...987这个数字9在百位,8在十位,7在个位,其实就是987=9x100+8x80+7x1。所以位就是数字的顺序位置。
二进制的位是1,2,4,8,16,32...比如1011就是1x8+0x4+1x2+1x1=11。
Python里面用bin(n)
可以把整数n变为0b开头的二进制字符串,比如
>>> bin(11)
'0b1011'
>>> bin(9)
'0b1001'
异或运算
Python中异或运算的符号是6数字上面的^。
对于两个二进制,比如1011和1001,把它们异或之后得到的是0010,因为两个二进制同位上相同,那么结果二进制这个位上就是0,不同就是1,参考下面的代码:
>>> 11^15
4
>>> print(bin(11));print(bin(15));print('0b0100')
0b1011
0b1111
0b0100
bin(4)其实是0b100,等同于0b0100。因为我们都知道十进制0030就是30,前面的0只是占位置的。
注意1011^1111=0100这个事实,只看前两位,简化一下的例子有10^11=01,只看后两位11^11=00。
更简单的例子是1^1=0,1^0=1,0^1=1,0^0=0,其实就是每一位上相同得0,相异得1。
再看代码中最后三行的数字,你会发现任意两个异或的结果都是第三个,11^15=4,11^4=15,15^4=11。两个数字a,b异或后得到第三个数字c,那么这三个数字互为其余两个数字的异或结果。
另外,任何数字和自己异或结果都是0,因为每一位都相同,每一位结果就都是0。
数据校验
假如你有一段文字,发送给我,我收到后如何确保中间传输的时候没有出错或者丢失或者被其他人中间做了修改?
如果让你再重新发送一遍,我把两遍收到的对比,可不可以?不行!首先同样的错误很可能会再次发生,两遍都错的可能性并不小;其次即使发现两次收到的不同,但哪一次才是正确的呢?
换个思路,我们事先都约定好一个数字C比如100,你把要发送的数据A先和C做异或得到B,把A和B都发给我,我收到之后,再把收到的数据A0和B0做异或,得到结果C0,C0再和C做异或,最终应该是0,这样就可以确定数据是正确的了,如果不是0就表示数据出了问题。
为什么?因为B=A^C,我用B0^A0=C0,再用C0^C=0自身异或为0完成验证。
当然如果数据A很多,那么你可以用C和A中的每个数字做异或,得到最后一个简单的数字B,把这个数字B放在A的最前面和A一起打包成D发过来,我只要把D的第一个数字当做B,其余当做A就可以了。
参照下面的代码:
data = '你好,Python!' #要发送的数据
A = [ord(t) for t in data] #数据变成待传送的列表[20320, 22909, 65292,...]
C = 100 #用C初始,下面循环和A每一项做异或
for t in A:
C = C ^ t
B = C #B是C和A每一项异或的结果,这时C已经变成B,不再是100
D = [B] + A #拼合传送数据
A0 = D[1:] #A0是D减去第一项剩余的其余部分
B0 = D[0] #用B0是D的第一项,下面循环和A0每一项做异或
for t in A0:
B0 = B0 ^ t
C0 = B0 #C0是B0和A0每一项异或的结果
C0 ^ 100 #因为C已经不再是100,所以这里不能使用C
ord('a')
获得字母a对应的计算机ascii编码数字,所有的字符包括中文日文,每一个都对应一个ascii编码数字。[int(t) for t in '123']
语法得到的是[1,2,3]
如果你修改D=[B]+A+[33,1223,55]
那么最后结果就不再是0,表示数据传输时候出了错误。
我们都知道电脑会不断地发送信息给远程服务器,远程服务器也不断的发送信息给电脑,自动取款机也是一台电脑,如果你把它的网线拔下来插在你的笔记本上,然后编写一个程序来假冒取款机发送信息给银行远程服务器,那么你就可以直接修改你的存款余额。
而有了校验机制,你编写的程序因为不知道取款机使用的秘钥(就是上面说的C=100),那么你发送的数据就会被银行服务器认为是错误信息。
实际情况要比这复杂很多。
信息加密
仍然利用异或算法,我们可以把数据A和秘钥C异或生成X,A^C=X,如果你不知道C,即使截获到X也没有办法读取出A来,所以异或本身就是一种加密方法。
如果你获得了X,也知道C,那么当然可以用C^X=A得到A。这就是解密。
和上面的B不同,B只是一个数字,而X则是对A的每一个数字加密后得到的新数字列表。
可以从下面的代码中理解:
def enctrypt(text, key):
text_asc_arr = [ord(t) for t in text] #将’你好 Python!‘转为ascii码列表[20320, 22909, 32,...]
key_asc_arr = [ord(t) for t in key] #将key转为ascii码列表[24,32,1543]
key_len = len(key_asc_arr)#秘钥单词长度
for n in range(len(text_asc_arr)):
k = n % key_len #秘钥单词循环往复使用
text_asc_arr[n] = text_asc_arr[n] ^ key_asc_arr[k] #异或操作成为[451,223,116,...]
return ' '.join([bin(t)[2:] for t in text_asc_arr]) #返回字符串拼接格式‘1010101 101011 1100 01...'
def decrypt(text, key):
asc_str_arr = text.split(' ') #将字符串拆成列表‘1010101 1010...'-->['101010','1010',...]
text_asc_arr = [int(s, 2) for s in asc_str_arr] #转成ascii列表[451,223,116,...]
key_asc_arr = [ord(t) for t in key]#转为ascii码列表[24,32,1543]
key_len = len(key_asc_arr)#秘钥单词长度
for n in range(len(text_asc_arr)):
k = n % key_len#秘钥单词循环往复使用
text_asc_arr[n] = text_asc_arr[n] ^ key_asc_arr[k]#异或操作成为[20320, 22909, 32,...]
return ''.join([chr(int(t)) for t in text_asc_arr])#返回字符串拼接格式‘你好 Python!'
en = enctrypt('你好 Python!', '我很好')
de = decrypt(e, 'wobuhao')
de2 = decrypt(e, '我很好')
print(en)
print(de)
print(de2)
输出的结果是
10110101110001 11011110101 101100101011101 110001001000001 101111111110001 101100100001001 110001001111001 101111111100111 101100100010011 1001110100010000
ⴆښ夿戴徙奨或徐奼鵲
你好 Python!
其中de = decrypt(e, 'wobuhao')
使用了错误的秘钥'wobuhao'导致了解密出来都是乱码。
异或加密只是众多加密方式中的一种。之所以常用异或进行校验或简单加密,是因为异或是基于二进制计算的,速度非常的快。