b.JS加密
JS加密
参考博客:
Python还原CryptoJs_AES_CBC模式_js逆向学习
《Python3网络爬虫开发实战第二版》-第11章
常用调试流程
以下是可以参考的调试流程(面向新手):
-
如果网页有跳转,必须勾选 preserve log 防止丢包
-
看一下有没有框架 右键查看框架源代码(弹出式登陆界面)
-
登陆尽量使用错误密码 防止跳转
-
查看关键登陆包 分析哪些参数是加密的
-
使用别的浏览器分析哪些参数是固定的值
-
初步猜测加密方法
-
分析
- 搜索
- 直接搜索参数。搜索时可以加上引号,冒号、空格等
- 密码框地方右键 检查 查看 id name type class
- 在network中分析发起调用
- 搜索
-
找到加密的地方(重点)
-
调试
- 可用浏览器debug;拿不准时全打上断点
- 可用node.js 运行JS文件,命令:node JS文件
- Hook
-
找出所有的加密代码
-
从最后一步开始写起,缺啥找啥
- 可在控制面板打印
- 可逐步debug分析作用域中的变量
-
如果找的是函数的话 search 要带上 function xxx
-
如果看到加密的地方有个类,并且之后是用 prototype 把方法加在原生对象上的话,要把
所有加在原生对象上的方法都找出来 -
函数找多了没关系,只要不报错不会影响结果,但是不能找少了
-
常用的加密有哪些?
- 信息摘要算法/签名算法:MD5、HMAC、SHA
- 对称加密(加密解密密钥相同):DES、DES3、AES
- 非对称加密(分公钥私钥):RSA
加密解密基本参数
在一些对称和非对称加密算法中,经常会用到以下三个参数:初始向量 iv、加密模式 mode、填充方式 padding,先介绍一下这三个参数的含义和作用:
1、初始向量 iv
在密码学中,初始向量(initialization vector,缩写为 iv),又称初始变数(starting variable,缩写为 sv),与密钥结合使用,作为加密数据的手段,它是一个固定长度的值,iv 的长度取决于加密方法,通常与使用的加密密钥或密码块的长度相当,一般在使用过程中会要求它是随机数或拟随机数,使用随机数产生的初始向量才能达到语义安全,让攻击者难以对原文一致且使用同一把密钥生成的密文进行破解。
参考资料:维基百科:https://en.wikipedia.org/wiki/Initialization_vector
2、加密模式 mode
目前流行的加密和数字认证算法,都是采用块加密方式,就是将需要加密的明文分成固定大小的数据块,然后对其执行密码算法,得到密文。数据块的大小通常采用跟密钥一样的长度。加密模式在加密算法的基础上发展出来,同时也可以独立于加密算法而存在,加密模式定义了怎样通过重复利用加密算法将大于一个数据块大小的明文转化为密文,描述了加密每一数据块的过程。目前利用较多的加密模式有以下几种:
ECB:Electronic Code Book(电子码本模式),是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
CBC:Cipher Block Chaining(密码块链接模式),是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度。
PCBC:Propagating Cipher Block Chaining(填充密码块链接模式),也称为明文密码块链接模式(Plaintext Cipher Block Chaining),是一种可以使密文中的微小更改在解密时导致明文大部分错误的模式,并在加密的时候也具有同样的特性。
CFB:Cipher Feedback(密码反馈模式),可以将块密码变为自同步的流密码,类似于 CBC,CFB 的解密过程几乎就是颠倒的 CBC 的加密过程。
OFB:Output Feedback(输出反馈模式),可以将块密码变成同步的流密码,它产生密钥流的块,然后将其与明文块进行异或,得到密文。与其它流密码一样,密文中一个位的翻转会使明文中同样位置的位也产生翻转。
CTR:Counter mode(计数器模式),也被称为 ICM 模式(Integer Counter Mode,整数计数模式)和 SIC 模式(Segmented Integer Counter),在 CTR 模式中,有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。
参考资料:维基百科:https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation
3、填充方式 padding
块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的。因此部分模式最后一块数据在加密前需要进行填充。有数种填充方法,其中最简单的一种是在明文的最后填充空字符以使其长度为块长度的整数倍。常见填充方式有以下几种:
PKCS7:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为需要填充的字节长度值。
PKCS5:PKCS5 作为 PKCS7 的子集算法,概念上没有什么区别,只是在 blockSize 上固定为 8 bytes,即块大小固定为 8 字节。
ZeroPadding:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为 0 。
ISO10126:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充随机数值。
ANSIX923:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充数字零。
参考资料:
维基百科:https://en.wikipedia.org/wiki/Padding_(cryptography)
PKCS7/PKCS5 填充算法:https://segmentfault.com/a/1190000019793040
base64
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。
-
Nodejs
const CryptoJS = require('crypto-js') // 加密 let pwd = '密码' let b64_pwd = Buffer.from(pwd).toString('base64') console.log(b64_pwd) // 5a+G56CB // 解密 console.log(Buffer.from(b64_pwd, 'base64').toString()) // 密码
-
python
import base64 # 加密 pwd = '密码' b64_pwd = base64.b64encode(pwd.encode('utf-8')) print(b64_pwd) # 解密 print(base64.b64decode(b64_pwd).decode('utf-8'))
案例参考:
MD5/SHA1
MD5信息摘要算法:(英语:MD5 Message-Digest Algorithm),一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。
MD5算法的原理可简要的叙述为:MD5码以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。
SHA-1:(英语:Secure Hash Algorithm 1,中文名:安全散列算法1)是一种密码散列函数。SHA-1可以生成一个被称为消息摘要的160位(20字节)散列值,散列值通常的呈现形式为40个十六进制数。
-
Nodejs
const CryptoJS = require('crypto-js') let pwd = '密码' // md5加密 let md5_enc_pwd = CryptoJS.MD5(pwd).toString() // sha1加密 let sha1_enc_pwd = CryptoJS.SHA1(pwd).toString() console.log(md5_enc_pwd) // a8105204604a0b11e916f3879aae3b0b console.log(sha1_enc_pwd) // c839a8ff17885af0b098662ccc3ac5e3111b3b3b
-
python
from hashlib import md5, sha1 # 加密 pwd = '密码' print(md5(pwd.encode('utf-8')).hexdigest()) print(sha1(pwd.encode('utf-8')).hexdigest())
HMAC
HMAC是密钥相关的哈希运算消息认证码(Hash-based Message Authentication Code)的缩写。
消息认证码:它是一个需要密钥的算法,可以对可变长度的消息进行认证,把输出的结果作为认证符。
散列函数:它是将任意长度的消息映射成为定长的散列值的函数,以该散列值消息摘要)作为认证符。
消息加密:它将整个消息的密文作为认证符。
-
Nodejs
const CryptoJS = require('crypto-js') let key = 'key' // 密钥 let pwd = '密码' // hmac中的sha256加密 let hash = CryptoJS.HmacSHA256(pwd, key) let hmac_sha256_pwd = CryptoJS.enc.Hex.stringify(hash) console.log(hmac_sha256_pwd) // 2c3a5556c71f76f1270ca87db60e1e91c69d812d748767468459a46912feed9c
-
python
import hashlib import hmac # 加密 key = 'key' pwd = '密码' enc_pwd = hmac.new(key.encode('utf-8'), pwd.encode('utf-8'), hashlib.sha256).hexdigest() print(enc_pwd)
DES
Des对称加密,是一种比较传统的加密方式,其加密运算、解密运算使用的是同样的密钥,信息的发送者和信息的接收者在进行信息的传输与处理时,必须共同持有该密码(称为对称密码),是一种对称加密算法。
-
Nodejs
const CryptoJS = require('crypto-js') let key = '12345678' // 密钥 let pwd = '密码' let new_key = CryptoJS.enc.Utf8.parse(key) let new_pwd = CryptoJS.enc.Utf8.parse(pwd) let config = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } // 加密 let enc_pwd = CryptoJS.DES.encrypt(new_pwd, new_key, config).toString() console.log(enc_pwd) // 80lOPdkA6f4= // 解密 let dec_pwd = CryptoJS.DES.decrypt(enc_pwd, new_key, config).toString(CryptoJS.enc.Utf8) console.log(dec_pwd)
-
python
import base64 from Crypto.Cipher import DES from Crypto.Util.Padding import pad def encrypt(key, text, mode): ''' DES加密 :param key: 密钥 :param text: 待加密文本 :param mode: 模式 :return: ''' key = key.encode('utf-8') text = text.encode('utf-8') pad_text = pad(text, DES.block_size, style='pkcs7') cipher = DES.new(key, mode) enc_data = cipher.encrypt(pad_text) return base64.b64encode(enc_data).decode('utf-8') def decrypt(key, text, mode): ''' DES解密 :param key: 密钥 :param text: 待解密文本 :param mode: 模式 :return: ''' new_key = key.encode('utf-8') new_text = base64.b64decode(text) # base64解码 cipher = DES.new(new_key, mode) dec_data = cipher.decrypt(new_text)return dec_data[:-dec_data[-1]].decode('utf-8') # 去除末尾填充的字符 if __name__ == "__main__": key = '12345678' pwd = '密码' enc_pwd = encrypt(key, pwd, DES.MODE_ECB) print(enc_pwd) dec_data = decrypt(key, enc_pwd, DES.MODE_ECB) print(dec_data)
ECB模式没有使用初始化向量(IV)
AES
AES的实现原理,可以参考文章:
https://blog.csdn.net/gulang03/article/details/81175854
实现AES的4种模式:
- ECB模式(电子密码本模式:Electronic codebook):ECB是最简单的块密码加密模式,加密前根据加密块大小(如AES为128位)分成若干块,之后将每块使用相同的密钥单独加密,解密同理
- CBC模式(密码分组链接:Cipher-block chaining):CBC模式对于每个待加密的密码块在加密前会先与前一个密码块的密文异或,然后再用加密器加密。第一个明文块与一个叫初始化向量的数据块异或
- CFB模式(密文反馈:Cipher feedback):与ECB和CBC模式只能够加密块数据不同,CFB能够将块密文(Block Cipher)转换为流密文(Stream Cipher)
- OFB模式(输出反馈:Output feedback):OFB是先用块加密器生成密钥流(Keystream),然后再将密钥流与明文流异或得到密文流,解密是先用块加密器生成密钥流,再将密钥流与密文流异或得到明文,由于异或操作的对称性所以加密和解密的流程是完全一样的
在对数据进行加密的时候,某些加密算法需要明文满足某些长度的要求,比如DES和AES等分组加密需要明文满足是分组的倍数,但是大多数情况下,明文恰好满足需求的概率是非常低的,不满足的情况下需要进行Padding。可以参考文章:【密码学】Padding模式
因此对于AES的Padding来说,PKCS5/PKCS7这两种方式是一样的。
-
Nodejs
const CryptoJS = require('crypto-js') let pwd = "密码"; let key = "1234567890abcdef" let new_key = CryptoJS.enc.Utf8.parse(key) let new_pwd = CryptoJS.enc.Utf8.parse(pwd) let cfg = { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } // AES加密 let enc_pwd = CryptoJS.AES.encrypt(new_pwd, new_key, cfg).toString() console.log(enc_pwd) // cdT+fh971Dgn3ji5v3+0AQ== // AES解密 let dec_pwd = CryptoJS.AES.decrypt(enc_pwd, new_key, cfg).toString(CryptoJS.enc.Utf8) // 指定解码方式 console.log(dec_pwd) // 密码
-
python
import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad def encrypt(key, text, mode): ''' AES加密 :param key: 密钥 :param text: 待加密文本 :param mode: 模式 :return: ''' key = key.encode('utf-8') text = text.encode('utf-8') pad_text = pad(text, AES.block_size, style='pkcs7') cipher = AES.new(key, mode) enc_data = cipher.encrypt(pad_text) return base64.b64encode(enc_data).decode('utf-8') def decrypt(key, text, mode): ''' AES解密 :param key: 密钥 :param text: 待解密文本 :param mode: 模式 :return: ''' new_key = key.encode('utf-8') new_text = base64.b64decode(text) # base64解码 cipher = AES.new(new_key, mode) dec_data = cipher.decrypt(new_text) print(dec_data) return dec_data[:-dec_data[-1]].decode('utf-8') # 去除末尾填充的字符 if __name__ == "__main__": key = '1234567890abcdef' pwd = '密码' enc_pwd = encrypt(key, pwd, AES.MODE_ECB) print(enc_pwd) dec_data = decrypt(key, enc_pwd, AES.MODE_ECB) print(dec_data)
案例参考:
《Python3网络爬虫开发实战第二版》11.5、11.6章
RSA
-
Nodejs
window = global; const JSEncrypt = require('jsencrypt'); // npm install jsencrypt@3.2.1 publickey = ` -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCdRivxDSsUknPXJ4iGLwmezwgc 1mue6d+Xyf67NWeHc6vC5vq2BfSmGgOz42dQ1JOwzWM+1TG6gocJbfSSsW1dFy3G sMLUblq1iIQ9/NZLjGRgF7+MUxCxTp+okPyhhUCeeg7u44B9F3OdDXIc3peAs4hV QI241AHnQqKJJVrEIwIDAQAB -----END PUBLIC KEY----- `; // 加密 let jse = new JSEncrypt(); jse.setPublicKey(publickey); var enc_pwd = jse.encrypt('密码'); console.log(enc_pwd) // dYxPVvIG/O2rHq0M6IG1H2yB0euqgL3pJ8jS+oFyGg7ZNHsWtLH/T6sYFRmVeMQwPJ4+c0ReEr/8wdzPcDg8NA== // 解密 privatekey = ` -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQCdRivxDSsUknPXJ4iGLwmezwgc1mue6d+Xyf67NWeHc6vC5vq2 BfSmGgOz42dQ1JOwzWM+1TG6gocJbfSSsW1dFy3GsMLUblq1iIQ9/NZLjGRgF7+M UxCxTp+okPyhhUCeeg7u44B9F3OdDXIc3peAs4hVQI241AHnQqKJJVrEIwIDAQAB AoGAPW7dGDYUF1+Tlz3ugreZ8uoc2aLZ/AOP3ss80OSt8Yd51tKBqRtPcphjzN8t irHWlO/Nbgw59ggpdkH4kFp1BJRyqTIxNibZaBK4vrvP6nnta6Us3zsdmYvql6v8 zpa7mIiXkchftj2M2bZEsJib3Xor9idzg805H2pyYWSsd2ECQQDAG/7tIHJ5hsAa dyqjbHDpFoVa1t6JpNVlttj+NLIuQKi8Atd5xXF5SPGFwVjeYfyvFbPp0rAoFje1 ldfUNB5DAkEA0ZRWIOHOMV+h2NMx5PCRnPClmx450I2bqdOo14CnG6gmipw15Oh5 oeLGqM1XsNKobDclU8YbR5B/YRxaw8eUoQJAEnYeR3doyNj0ORbemBnht+ScKCCh /iRDBaVOsQ8rWFqKXJcBUghxYTBrVWlBOw27lK/HPF8s4o1QCTk/JntjtQJAQKk/ mY2RjHIxATDH6BbBFma48Y71z36hVFhVc4fiBwpuOb3Qcvu261eIa3RPZeLYy+qH vb0VlZLjehbBej4NgQJAF9RKBmh7VtqrzhBU53KzVzpwSUdpiuNGnb563xNt4FtS Ybwh5wrMDHf6y8GNUgI2ICKZA+0LCWDs/PxekA8URg== -----END RSA PRIVATE KEY----- ` jse.setPrivateKey(privatekey); var dec_pwd = jse.decrypt(enc_pwd); console.log(dec_pwd) // 加密
每次加密的结果都会不同,密钥对可通过在线工具生成
-
python
import base64 from Crypto.Cipher import PKCS1_v1_5 from Crypto import Random from Crypto.PublicKey import RSA # ------------------------生成密钥对------------------------ def create_rsa_pair(is_save=False): ''' 创建rsa公钥私钥对 :param is_save: default:False :return: public_key, private_key ''' f = RSA.generate(1024) private_key = f.exportKey("PEM") # 生成私钥 public_key = f.publickey().exportKey() # 生成公钥 if is_save: with open("crypto_private_key.pem", "wb") as f: f.write(private_key) with open("crypto_public_key.pem", "wb") as f: f.write(public_key) return public_key, private_key def read_public_key(file_path="crypto_public_key.pem") -> bytes: with open(file_path, "rb") as x: b = x.read() return b def read_private_key(file_path="crypto_private_key.pem") -> bytes: with open(file_path, "rb") as x: b = x.read() return b # ------------------------加密------------------------ def encryption(text: str, public_key: bytes): # 字符串指定编码(转为bytes) text = text.encode('utf-8') # 构建公钥对象 cipher_public = PKCS1_v1_5.new(RSA.importKey(public_key)) # 加密(bytes) text_encrypted = cipher_public.encrypt(text) # base64编码,并转为字符串 text_encrypted_base64 = base64.b64encode(text_encrypted).decode() return text_encrypted_base64 # ------------------------解密------------------------ def decryption(text_encrypted_base64: str, private_key: bytes): # 字符串指定编码(转为bytes) text_encrypted_base64 = text_encrypted_base64.encode('utf-8') # base64解码 text_encrypted = base64.b64decode(text_encrypted_base64) # 构建私钥对象 cipher_private = PKCS1_v1_5.new(RSA.importKey(private_key)) # 解密(bytes) text_decrypted = cipher_private.decrypt(text_encrypted, Random.new().read) # 解码为字符串 text_decrypted = text_decrypted.decode() return text_decrypted if __name__ == '__main__': # 生成密钥对 public_key, private_key = create_rsa_pair(is_save=False) # create_rsa_pair(is_save=True) # public_key = read_public_key() # private_key = read_private_key() # 加密 pwd = '密码' enc_pwd = encryption(pwd, public_key) print('密文:', enc_pwd) # 解密 dec_pwd = decryption(enc_pwd, private_key) print('明文:', dec_pwd)
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· 支付宝 IoT 设备入门宝典(下)设备经营篇
· 万字调研——AI生成内容检测
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库