编码与加密

编码与加密在爬虫中经常涉及,常见的编码有base64, unicode, urlencode,常见的加密有MD5, SHA1, HMAC, DES, AES, RSA。

下面逐一介绍:

一,编码

1.1 常规编码

常规编码约定了字符集中字符与一定长度二进制的映射关系,字符集是指各国家的文字、标点符号、图形符号和数字等字符的集合,计算机要准确地处理不同字符集,就需对字符进行编码。这种编码包括ASCII,utf-8,GBK,unicode。

python 复制代码
'lx'.encode() # 不指定编码格式时,会自动调用计算机的默认编码格式进行编码
'中国'.encode('utf-8').decode('GBK') # 编码与解码的格式不同,会导致报错

ASCII

ASCII是基于拉丁字母表的一套计算机编码系统,主要用于显示现代英语和其他西欧语言。ASCII编码实际上约定了字符和二进制的映射关系,如小写字母"a"对应的8位二进制数为01100001。因此,我们也可以将它看作二进制与拉丁字符的映射表。

ASCII的RFC文档编号为20(详见https://tools.ietf.org/html/rfc20),其中约定了ASCII的使用范围、标准码、字符表示和代码识别等内容。

ASCII码默认使用7位二进制数来表示所有的大写字母、小写字母、数字(0~9)、标点符号和特殊的控制符。

unicode

unicode被称为统一码、万国码,以\u、&#开头,python实现如下:

python 复制代码
# str转为 Unicode 编码:
chinese_str = "四川省"
print(chinese_str.encode("unicode_escape")) # b'\\u56db\\u5ddd\\u7701'

# Unicode 编码转为str:
a = b'\\u56db\\u5ddd\\u7701'
print(a.decode('unicode_escape')) # 四川省

1.2 Base64

Base64在基于常规编码的基础上,将字符转为字节,然后将字节切分为6位的长度,并约定了6位字节与64个字符的对应关系。

Base64的出现是为了解决不可打印的字符(如非英文的字符)在网络传输过程中造成的乱码现象。Base64的RFC文档编号为4648,文档地址为https://tools.ietf.org/html/rfc4648。

将字符进行Base64编码时,首先要将字符转换成对应的ASCII码,然后得出8位二进制数,接着连接3个8位输入,形成字节数为24的输入组,再将24位输入组拆分成4组6位的二进制数,然后将6位二进制数转换为十进制数,最后找到十进制数在Base64编码表中对应的字符,并将这些字符组合成新的字符串,这个字符串就是编码结果。编码过程中用到的Base64编码表如图所示。

要注意的是,在编码过程中,如果字符位数少于24位,那么就需要进行特殊处理,也就是在编码结果的末尾用"="符号填充。

我们可以通过一个例子来加深对Base64编码过程的理解。首先,我们将字符async转换成ASCII码,并找到对应的8位二进制数。字符、ASCII码和8位二进制数的对应值如图:

接着将3组8位二进制数连接成24位的输入组,再将24位输入组拆分成4组6位的二进制数。要注意的是,如果输入组的元素不足24位,那么就用0进行填充。24位输入组转换成6位二进制数的过程如图:

得到6位二进制数之后,我们还需要计算出对应的十进制。二进制转十进制其实是按权相加,将二进制数写成加权系数展开式,并按十进制加法规则求和。字符"a"对应的6位二进制数为011000,将其转换成十进制时,计算过程如图:

按照这个计算方法,计算其他的6位二进制数,最后得到字符"async"对应的十进制值:

24 23 13 57 27 38 12 65

补位字符"="没有对应的值,本书约定其值为65。在得到所有的十进制值之后,就可以将其与RFC4648中的Base64编码表进行映射,从而得出编码后的字符串。映射过程如图:

最终得出字符"async"的Base64编码结果为"YXN5bmM=",完整的编码过程如图:

Base64编码时所用的对照表是固定的,也就是说它的编码过程是可逆的。这意味着我们只需要将编码的流程倒置,就能够得解码的方法。Base64编码表中的 "+" 和 "/" 会影响文件编码和URI编码,我们在实际使用时,需要考虑到应用场景中是否包含文件编码或URI 。

如果在URI场景下使用Base64,就会引起错误,RFC4648文档中给出了一个解决办法:使用"-"和"_"替代"+"和"/"。

base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%base64加密后的密文长度视原字符而定

base64加密后的密文大概率最后1位或2位是=,这就是它的特点;此外其他编码方法如果用16进制表示,字母要么是大写要么是小写,而bash64会有大小写同时出现的情况和+,/,且字母不仅仅是a-f

浏览器原生提供了base64的编码、解码方法:btoa atob

window.btoa('lx')
window.atob('bHg=')

Js实现如下:

html 复制代码
<html>
    <script type="text/javascript">
		// 创建Base64对象
		var Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9+/=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/rn/g,"n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
		 
		// 定义字符串
		var string = 'i am bobo!';
		 
		// 加密
		var encodedString = Base64.encode(string);
		alert(encodedString); 
		 
		// 解密
		var decodedString = Base64.decode(encodedString);
		alert(decodedString); 
	</script>
		
</html>

nodejs实现如下:

javascript 复制代码
var str1 = 'lx';
var str2 = 'bHg=';
var strToBase64 = new Buffer(str1).toString('base64');
var base64ToStr = new Buffer(str2, 'base64').toString();

python实现如下:

python 复制代码
import base64

res = base64.b64encode('lx'.encode('utf-8'))
print(res) # b'bHg='

data = base64.b64decode('bHg='.encode())
origin = data.decode('utf-8')
print(origin) # 'lx'

爬虫工程师只需要按照Base64解码规则进行倒推,就能得到原字符。

爬虫工程师很轻松就拿到了原字符,这显然不是开发者想要见到的结果。其实,开发者还可以通过自定义编码规则的方式保护数据。只需要稍微改动一下Base64编码过程中用到的对照表,或者改动输入组的划分规则,就可以创造一个新的编码规则。

Base64编码和解码时都是将原本8位的二进制数转换成6位的二进制数。如果我们改动位数,将其设置为5位或者4位,那么就可以实现新的编码规则。

此时如果爬虫工程师使用Base64对该编码结果进行解码,那么他将无法得到正确的原字符。这不仅达到了保护数据的目的,还能够迷惑爬虫工程师,使其将时间花费在"Base64解码不成功"的问题上。

1.3 urlencode

urlencode 称为url编码、百分号编码;就是将字符串以URL编码,一种编码方式,主要为了解决url中中文乱码问题。

python实现如下:

  • 传入参数类型:字典
  • 功能:将存入的字典参数编码为URL查询字符串,即转换成以key1=value1&key2=value2的形式

例子1:url标准符号,数字字母

python 复制代码
from urllib.parse import urlencode

base_url = "https://m.weibo.cn/api/container/getIndex?"
params1 = {"value": "english", "page": 1}
url1 = base_url + urlencode(params1)
print(urlencode(params1))  # value=english&page=1
print(url1) # https://m.weibo.cn/api/container/getIndex?value=english&page=1

例子2:汉字, /, &, =, URL编码转化为%xx的形式

python 复制代码
from urllib.parse import urlencode

params2 = {
    'name': "王二",
    'extra': "/",
    'special': '&',
    'equal': '='}
base_url = "https://m.weibo.cn/api/container/getIndex?"
url2 = base_url + urlencode(params2)
print(urlencode(params2)) # name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D
print(url2) # https://m.weibo.cn/api/container/getIndex?name=name=%E7%8E%8B%E4%BA%8C&extra=%2F&special=%26&equal=%3D

例子3:以上两例子默认utf8编码,如果用gb2312编码,则需指定

python 复制代码
from urllib.parse import urlencode

params2 = {
    'name': "王二",
    'extra': "/",
    'special': '&',
    'equal': '='}
base_url = "https://m.weibo.cn/api/container/getIndex?"
url2 = base_url + urlencode(params2, encoding='gb2312')
print(urlencode(params2, encoding='gb2312')) 
print(url2)
# name=%CD%F5%B6%FE&extra=%2F&special=%26&equal=%3D
# https://m.weibo.cn/api/container/getIndex?name=%CD%F5%B6%FE&extra=%2F&special=%26&equal=%3D

二,加密

常见的加密算法基本分为这几类:

  • 线性散列算法(签名算法)MD5 SHA
  • 对称加密算法 AES DES
  • 非对称加密算法 RSA

2.1 线性散列算法(签名算法)

2.1.1 MD5

Message-Digest Algorithm,消息摘要算法,一种被广泛使用的密码散列函数,它能够将任意长度的消息转换成128bit的消息摘要,这个特性被称为"压缩"。

相比Base64编码,MD5的运算过程要复杂很多。由于MD5在运算过程中使用了补位、追加和移位等操作,所以他人无法从输出结果倒推出输入字符,这个特性被称为**"不可逆"**,所以解密一般是通过暴力穷举,以及网站的接口实现解密。

MD5的RFC文档编号为1321,文档地址为https://tools.ietf.org/html/rfc1321。RFC1321约定了一些术语和符号,并描述了MD5算法的计算步骤和方法。

MD5的典型应用场景就是一致性验证,如文件一致性和信息一致性。在注册账号时的密码一般都是用的MD5加密。

md5加密后产生一个固定长度(大概率为32位)的数据,在线加密解密网址:https://md5jiami.bmcx.com/

 字符串:123456   可加密成16位或32位
 16位小写:49ba59abbe56e057
 32位小写:e10adc39 49ba59abbe56e057 f20f883e  其实就是16位前后加了一些东西

MD系列的加密方式有很多,增加破解成本的方法举例:

  • 使用一段无意义且随机的私匙进行MD5加密会生成一个加密串,称为串1,将要加密的的数据跟串1拼接,再进行一次MD5,这时会生成串2,将串2再次进行MD5加密,这时生成的串3就是加密后的数据

Js实现如下:

javascript 复制代码
# Js可以通过crypto-js加密库进行算法实现
const CryptoJs = require('crypto-js');
let password = 'lx123';
let encPwd = CryptoJs.MD5(password).toString();

python实现如下:

python 复制代码
import hashlib

obj = hashlib.md5()  # 加盐:obj = hashlib.md5("salt".encode('utf-8'))
obj.update('lx'.encode('utf-8'))

v1 = obj.hexdigest() # fb0e22c79ac75679e9881e6ba183b354     十六进制
v2 = obj.digest() # b'\xfb\x0e"\xc7\x9a\xc7Vy\xe9\x88\x1ek\xa1\x83\xb3T'   二进制字节

2.1.2 SHA1

Secure Hash Algorithm,安全哈希算法,比MD5的安全性更强,对长度小于2^64位的消息,SHA1会产生一个160bit的消息摘要。一般在未高度混淆的Js代码中,SHA1加密的关键词就是sha1。

SHA在线加密解密网站:http://www.wetools.com/sha/

从代码上看,SHA1与MD5的实现很相似:

Js实现如下:

javascript 复制代码
const CryptoJs = require('crypto-js');
let password = 'lx123';
let encPwd = CryptoJs.SHA1(password).toString();

python实现如下:

python 复制代码
import hashlib

m = hashlib.sha1()
m.update("lx".encode("utf-8"))
m.hexdigest()

2.1.3 HMAC

Hash Message Authentication Code,散列消息鉴别码。实现原理是用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。

以HMAC中的SHA256加密为例:

Js实现如下:

javascript 复制代码
const CryptoJs = require('crypto-js');
let key = 'key';
let text = 'lx';
let hash = CryptoJs.HmacSHA256(text, key);
let hashInHex = CryptoJs.enc.Hex.stringify(hash);

python实现如下:

python 复制代码
import hmac
import hashlib

key = 'key'.encode()
text = 'lx'.encode()

mac = hmac.new(key, text, hashlib.sha256) # 或 hashlib.sha512
mac.digest()
mac.hexdigest()

2.2 对称加密算法

2.2.1 DES

Data Encryption Standard,数据加密标准,是使用密钥加密的算法。是一种对称加密方式,其加密、解密使用同样的密钥,密钥长度为56位。

破解方法:暴力破解。DES使用 56 位的密钥,则可能的密钥数量是 2 的 56 次方个,只要计算足够强大是可以被破解的。

DES算法的入口参数:Key、Data、Mode,padding

  • Key 为7个字节共56位,是DES算法的工作密钥;
  • Data 是要被加密或被解密的数据;
  • Mode 为DES的工作方式;
  • padding 为填充模式,如果加密后密文长度达不到指定整数倍(8个字节、16个字节),填充对应字符,padding的赋值固定为 CryptoJS.pad.Pkcs7 即可。

Js逆向时,DES加密的搜索关键词有DES,mode,padding等。

Js实现如下:(引用不同的包实现方式不同,这里举两例说明)

crypto-js.js:

javascript 复制代码
var aseKey = "12345678"     //定制秘钥,长度必须为:8/16/32B
var message = "i am bobo,who are you ?";
cfg = {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
asekey = CryptoJS.enc.Utf8.parse(aseKey)
//加密 DES/AES切换只需要修改 CryptoJS.DES <=> CryptoJS.AES
var encrypt = CryptoJS.DES.encrypt(message,asekey,cfg).toString();
// 0Gh9NGnwOpgmB525QS0JhVJlsn5Ev9cHbABgypzhGnM

//解密
var decrypt = CryptoJS.DES.decrypt(encrypt,asekey,cfg).toString(CryptoJS.enc.Utf8);
// i am bobo,who are you ?

aes.js:

javascript 复制代码
<html>
    <script src="static/plugins/aes/aes.js"></script>
    <!-- 加密时需引入 pad-zeropadding.js -->
    <script src="static/plugins/aes/pad-zeropadding-min.js"></script>
    <script type="text/javascript">
        // 加密
        function encrypt(data,key,iv) { //key,iv:16B的字符串
            var key  = CryptoJS.enc.Latin1.parse(key);
            var iv   = CryptoJS.enc.Latin1.parse(iv);
            return CryptoJS.AES.encrypt(data, key,{
                iv : iv,
                mode : CryptoJS.mode.CBC,
                padding : CryptoJS.pad.ZeroPadding
            }).toString();
        }
        // 解密
        function decrypt(data,key,iv){ //key,iv:16B的字符串
            var key  = CryptoJS.enc.Latin1.parse(key);
            var iv   = CryptoJS.enc.Latin1.parse(iv);
            var decrypted=CryptoJS.AES.decrypt(data,key,{
                iv : iv,
                mode : CryptoJS.mode.CBC,
                padding : CryptoJS.pad.ZeroPadding
            });
            return decrypted.toString(CryptoJS.enc.Utf8);
        }
    </script>
</html>

python实现:

pip install pycryptodome

当key与iv是普通字符串时:

python 复制代码
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def aes_encrypt(data_string):
    key_string = "fd6b639dbcff0c2a1b03b389ec763c4b"
    key = key_string.encode('utf-8')
    
    iv_string = "77b07a672d57d64c"
    iv = iv_string.encode('utf-8')
    
    data = data_string.encode("utf-8")
    
    aes = AES.new(
        key=key,
        mode=AES.MODE_CBC,
        iv=iv
    )
    raw = pad(data, 16)
    return aes.encrypt(raw)

data = "|878975262|d000035rirv|1631615607|mg3c3b04ba|1.3.5|ktjwlm89_to920weqpg|433070"

result = aes_encrypt(data)
print(result)

当key与iv是十六进制字符串时:

python 复制代码
import binascii

v1 = "4E2918885FD98109869D14E0231A0BF4"

""" 自己实现
bs = bytearray() # []
for i in range(0, len(v1), 2):
    item_hex = v1[i:i + 2]
    item_int = int(item_hex, base=16)
    bs.append(item_int)
v3 = bytes(bs)
print(v3) # b'N)\x18\x88_\xd9\x81\t\x86\x9d\x14\xe0#\x1a\x0b\xf4'
"""

# 调包实现
v3 = binascii.a2b_hex(v1)
print(v3) # b'N)\x18\x88_\xd9\x81\t\x86\x9d\x14\xe0#\x1a\x0b\xf4'
python 复制代码
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import binascii

def aes_encrypt(data_string):
    key_string = "4E2918885FD98109869D14E0231A0BF4"
    key = binascii.a2b_hex(key_string)

    iv_string = "16B17E519DDD0CE5B79D7A63A4DD801C"
    iv = binascii.a2b_hex(iv_string)

    aes = AES.new(
        key=key,
        mode=AES.MODE_CBC,
        iv=iv
    )
    raw = pad(data_string.encode('utf-8'), 16)
    return aes.encrypt(raw)


data = "|878975262|d000035rirv|1631615607|mg3c3b04ba|1.3.5|ktjwlm89_to920weqpg"

result = aes_encrypt(data)
print(result)

2.2.2 AES

Advanced Encryption Standard,高级加密标准,这个标准用来替代原先的DES

AES和DES的区别:

  • 加密后密文长度的不同:DES加密后密文长度是8的整数倍,AES加密后密文长度是16的整数倍

  • 应用场景不同:企业级开发使用DES足够安全,如果要求高使用AES

AES的填充模式常用的有三种,分别是NoPadding,ZeroPadding,Pkcs7,默认为Pkcs7。

Js逆向时,AES加密的搜索关键词有AES,mode,padding等。

DES和AES切换只需要修改 CryptoJS.AES <=> CryptoJS.DES,故实现代码略。

2.3 非对称加密算法

2.3.1 RSA

RSA,全称Rivest-Shamir-Adleman,RSA加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。

非对称加密算法需要两个密钥:公开密钥(publickey:简称公钥),私有密钥(privatekey:简称私钥),公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以这种算法叫作非对称加密算法

注意:使用时都是使用公匙加密使用私匙解密。公匙可以公开,私匙自己保留。

Js代码中的RSA常见标志 setPublickey

Js实现加密可以使用jsencrypt加密库,另外需要生成好公钥和私钥,可以到在线网站去生成:http://web.chacuo.net/netrsakeypair

html 复制代码
<html>
	<script src="https://cdn.bootcss.com/jsencrypt/3.0.0-beta.1/jsencrypt.js"></script>
    <script type="text/javascript">
        //公钥
        var PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALyBJ6kZ/VFJYTV3vOC07jqWIqgyvHulv6us/8wzlSBqQ2+eOTX7s5zKfXY40yZWDoCaIGk+tP/sc0D6dQzjaxECAwEAAQ==-----END PUBLIC KEY-----';
        //私钥
		var PRIVATE_KEY = '-----BEGIN PRIVATE KEY-----MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvIEnqRn9UUlhNXe84LTuOpYiqDK8e6W/q6z/zDOVIGpDb545NfuznMp9djjTJlYOgJogaT60/+xzQPp1DONrEQIDAQABAkEAu7DFsqQEDDnKJpiwYfUE9ySiIWNTNLJWZDN/Bu2dYIV4DO2A5aHZfMe48rga5BkoWq2LALlY3tqsOFTe3M6yoQIhAOSfSAU3H6jIOnlEiZabUrVGqiFLCb5Ut3Jz9NN+5p59AiEA0xQDMrxWBBJ9BYq6RRY4pXwa/MthX/8Hy+3GnvNw/yUCIG/3Ee578KVYakq5pih8KSVeVjO37C2qj60d3Ok3XPqBAiEAqGPvxTsAuBDz0kcBIPqASGzArumljkrLsoHHkakOfU0CIDuhxKQwHlXFDO79ppYAPcVO3bph672qGD84YUaHF+pQ-----END PRIVATE KEY-----';
        
		//使用公钥加密
        var encrypt = new JSEncrypt();//实例化加密对象
        encrypt.setPublicKey(PUBLIC_KEY);//设置公钥
        var encrypted = encrypt.encrypt('hello bobo!');//对指定数据进行加密
		alert(encrypted)
        //使用私钥解密
        var decrypt = new JSEncrypt();
        decrypt.setPrivateKey(PRIVATE_KEY);//设置私钥
        var uncrypted = decrypt.decrypt(encrypted);//解密
		alert(uncrypted);
    </script>
</html>

python实现代码:实现代码较多,以后需要时自行搜索。

相关推荐
阿伟来咯~4 分钟前
记录学习react的一些内容
javascript·学习·react.js
吕彬-前端9 分钟前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱12 分钟前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai21 分钟前
uniapp
前端·javascript·vue.js·uni-app
也无晴也无风雨22 分钟前
在JS中, 0 == [0] 吗
开发语言·javascript
狂奔solar31 分钟前
yelp数据集上识别潜在的热门商家
开发语言·python
Tassel_YUE32 分钟前
网络自动化04:python实现ACL匹配信息(主机与主机信息)
网络·python·自动化
聪明的墨菲特i39 分钟前
Python爬虫学习
爬虫·python·学习
王哲晓2 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
努力的家伙是不讨厌的2 小时前
解析json导出csv或者直接入库
开发语言·python·json