加密解密:base64、Unicode、escape、URL/HEX、md5、sha、hmac、rsa、PBKDF2、aes、des、sm、js混淆系列、cyberchef、ctf工具

From:https://mp.weixin.qq.com/s/Yezf-ueRi7PxggZtah8K5g

CTF中编码与加解密总结:https://www.cnblogs.com/gwind/p/7997922.html

关键字:加密 解密 编码 解码

1、"加密、解密" 工具

爬虫工程师在做加密参数逆向的时候,经常会遇到各种各样的加密算法、编码、混淆。每个算法都有其对应的特征,对于一些较小的网站,往往直接引用这些官方算法,没有进行魔改等其他操作,这种情况下,如果我们能熟悉常见算法的特征,通过密文就能猜测出使用的哪种算法、编码、混淆,将会大大提高工作效率!在 CTF 中通常也会有密码类的题目,掌握一些常见密文特征也是 CTFer 们必备的技能!

将介绍以下编码和加密算法的特征:

  • 编码:Base 系列、Unicode、Escape、URL、Hex;
  • 加密算法:MD5、SHA 系列、HMAC 系列、RSA、AES、DES、3DES、RC4、Rabbit、SM 系列;
  • 国密 SM 系列:SM 代表商密,即商业密码,是中国国家密码管理局发布的一系列国产加密算法。包括 SM1、SM2、SM3 、SM4、SM7、SM9、ZUC(祖冲之加密算法)等。SM 代表商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。SM1 和 SM7 的算法不公开
  • 混淆:Obfuscator、JJEncode、AAEncode、JSFuck、Jother、Brainfuck、Ook!、Trivial brainfuck substitution;
  • 其他:恺撒密码、栅栏密码、猪圈密码、摩斯密码、培根密码、维吉尼亚密码、与佛论禅、当铺密码。

在线 CTF 工具

CTF-Tools:https://github.com/qianxiao996/CTF-Tools

ToolsFx:https://github.com/Leon406/ToolsFx

在线 CTF 工具:http://www.hiencode.com/

ctf.bugku:https://ctf.bugku.com/tools.html

在线 cyberchef 工具

CyberChef 是一款由英国政府通信总部(GCHQ)开发的一款强大的开源工具,用于数据转换和分析。它能让你像在做烘焙一样处理数据,但不是用面粉和鸡蛋,而是用编码、解码、加密、解密、压缩、解压等操作!可以在线使用,也可以直接下载 zip 到本地,解压到任意目录,双击 .html 文件即可打开工具,是一个html页面。

如果添加多个 "算法框",切换执行顺序就是拖拽算法框。执行的算法永远是最下面的那个。

所有功能

CyberChef 是一款简单、直观的WEB应用程序,支持在浏览器中进行各种“网络”操作。这些操作包括如下:

  • 编码和解码:提供广泛的编码和解码操作,包括Base64、URL编码、HTML实体编码等。
  • 解密和加密:CyberChef可用于解密加密的文本或文件,也可以执行数据加密操作,支持多种加密算法。如 AES、DES 和 Blowfish 等。
  • 文本和文件处理:执行文本和文件的搜索、替换、提取等操作,还可以进行数据格式转换。
  • 计算散列和、校验和:支持多种哈希算法,用户可以计算文件或文本的哈希值,用于数据完整性验证。
  • 网络流量分析:处理和分析网络流量,包括HTTP请求、响应等,用于网络流量分析。
  • 创建二进制和hexdump,
  • 数据压缩和解压缩,
  • IPv6和X.509解析,
  • 更改字符编码
  • 常用的工具。

CyberChef支持多达数百种操作,从基本的编码解码到复杂的数据分析,应有尽有。而且,它的操作是可组合的,你可以像搭积木一样将各种操作连接起来,构建你自己的数据处理流程。想象一下,它就像是个万能的调味料,你可以随心所欲地混合、搭配,创造出你需要的数据处理流程!

示例:AES 加解密

to base64、from base64

你可以将想要使用的操作拖到食谱区域,并指定参数和选项。比如有一段经过Base64编码的文本,想将它解码成普通文本。

只需要在输入框中粘贴Base64编码文本,拖拽一个Base64解码操作到操作区域即可解码。

解密操作的算法就把加密的算法逆过来, to base64 变为 from base64。

还可以进行叠加操作,如,可以直接对上面的结果再进行Hex编码,找到并拖拽 “To Hex”模块到中间的工作区即可。

RSA 加解密

使用自带工具随意生成一对公私钥。把公钥复制进来,选择加密方式进行加密。

解密操作和AES类似。

时间戳 转 完整日期

中文乱码问题

代码整理模块(code tidy)

有时解密出来的数据是乱码,大多是因为使用了中文,复制下来也依旧乱码,这里给出一个解决方案:code tidy模块中有一个Syntax highlighter。这个模块是一个美化高亮操作,并且不会影响原有的缩进。

想要更加整齐的美化,如高亮、缩进之类的可根据加密数据的类型自行选择其它的beautify模块。

美化后依然乱码问题解决,有一些可能直接美化还是乱码的,例如下图的情况。

此时把AES解密算法的输出改成HEX,output旁边会出现一个魔法棒,鼠标移动上去会显示出应该出现的中文字符。

点击一下魔法棒会自动添加一个 from hex 的算法,此时再加上我们的美化模块即可出现正确的中文字符。

密码学 常用解码网站

xssee:http://web2hack.org/xssee
xssee:http://evilcos.me/lab/xssee
程默的博客(DES,3DES,AES,RC,Blowfish,Twofish,Serpent,Gost,Rijndael,Cast,Xtea,RSA):http://tool.chacuo.net/cryptdes
在线编码解码(多种并排):http://bianma.911cha.com
在线加密解密(多种):http://encode.chahuo.com
Unicode转中文:http://www.bejson.com/convert/unicode_chinese
栅栏密码 && 凯撒密码 && 摩斯电码:http://heartsnote.com/tools/cipher.htm
Caesar cipher(凯撒密码):http://planetcalc.com/1434/
Quoted-Printable && ROT13:http://www.mxcz.net/tools/QuotedPrintable.aspx
ROT5/13/18/47编码转换:http://www.qqxiuzi.cn/bianma/ROT5-13-18-47.php
Base32/16:http://pbaseconverter.com/
Base32:https://tools.deamwork.com/crypt/decrypt/base32decode.html
quipqiup古典密码自动化爆破(词频分析):http://quipqiup.com/index.php
词频分析/替换:http://cryptoclub.org/tools/cracksub_topframe.php
'+.<>[]' && '!.?'(Brainfuck/Ook!):https://www.splitbrain.org/services/ook
'+-.<>[]'(Brainfuck):https://www.nayuki.io/page/brainfuck-interpreter-javascript
'+-.<>[]'(Brainfuck):http://esoteric.sange.fi/brainfuck/impl/interp/i.html
'()[]!+'JavaScript编码(JSfuck):http://discogscounter.getfreehosting.co.uk/js-noalnum.php
用 6 个字符'()[]!+'来编写 JavaScript 程序(JSfuck同上):http://www.jsfuck.com/
http://patriciopalladino.com/files/hieroglyphy/
摩斯密码翻译器:http://www.jb51.net/tools/morse.htm
Morse Code摩尔斯电码:http://rumkin.com/tools/cipher/morse.php
字符串编码,解码,转换(长度,反转,进制转换):http://www.5ixuexiwang.com/str/
Cisco Type 7 Reverser:http://packetlife.net/toolbox/type7
Cisco:http://www.ifm.net.nz/cookbooks/passwordcracker.html
cmd5 && NTLM && mysql...:http://www.cmd5.com
spammimic(字符2一段话):http://www.spammimic.com/
js代码在线加密解密:https://tool.lu/js/
JScript/VBscript脚本解密(#@~^....^#~@):http://www.dheart.net/decode/index.php
VBScript.Encode解密(tip:Asp encode):http://adophper.com/encode.html
JScript.Encode脚本加密与解密:http://www.haokuwang.com/jsendecode.htm
'+/v+'UTF-7加密:http://web2hack.org/xssee
各种无知密码解密:http://www.tools88.com
uuencode解码 && xxencode解码(古老的邮件密码):http://web.chacuo.net/charsetuuencode
MIME标准(邮件编码的一种):http://dogmamix.com/MimeHeadersDecoder/
Binhex编码(邮件编码的一种,常见于MAC机):http://encoders-decoders.online-domain-tools.com/
%u8001%u9525非/u的hex,%u编码,只能编码汉字(xssee):http://web.chacuo.net/charsetescape
猪圈密码:http://www.simonsingh.net/The_Black_Chamber/pigpen.html
ppencode(把Perl代码转换成只有英文字母的字符串):http://namazu.org/~takesako/ppencode/demo.html
rrencode(把ruby代码全部转换成符号):挂了
aaencode(JS代码转换成常用的网络表情,也就是我们说的颜文字js加密):http://utf-8.jp/public/aaencode.html
'()[]!+' && '$=~[]+"_.\();'jother编码jjencode(JS代码转换成只有符号的字符串):http://web2hack.org/xssee
jother(是一种运用于javascript语言中利用少量字符构造精简的匿名函数方法对于字符串进行的编码方式。其中8个少量字符包括:! + ( ) [ ] { }。只用这些字符就能完成对任意字符串的编码):http://tmxk.org/jother/
jjencode/aaencode可用xssee && Chrome的Console模式来直接输出解密。
Manchester曼彻斯特解密:http://eleif.net/manchester.html
Vigenère维多利亚解密:https://www.guballa.de/vigenere-solver
Vigenère cipher:http://planetcalc.com/2468/
Hill cipher(希尔密码):http://planetcalc.com/3327/
Atbash cipher(埃特巴什码):http://planetcalc.com/4647/
snow(html隐写):http://fog.misty.com/perry/ccs/snow/snow/snow.html
Serpent加密解密:http://serpent.online-domain-tools.com/
十六进制Hex转文本字符串:http://www.bejson.com/convert/ox2str/
Hex2text:http://www.convertstring.com/EncodeDecode/HexDecode
Binary(二进制),ACSII,Hex(十六进制),Decimal(十进制):http://www.binaryhexconverter.com/
集合:http://www.qqxiuzi.cn/daohang.htm
集合(各种古典密码):http://rumkin.com/tools/cipher/
文本加密为汉字("盲文",音符,各种语言,花朵,箭头...):http://www.qqxiuzi.cn/bianma/wenbenjiami.php
在线工具集合:http://tomeko.net/online_tools/
二维码/条形码:https://online-barcode-reader.inliteresearch.com/
生成二维码:http://www.wwei.cn/
在线二维码解密:http://jiema.wwei.cn/
Image2Base64:http://www.vgot.net/test/image2base64.php
与佛论禅:http://www.keyfc.net/bbs/tools/tudoucode.aspx
在线分解GIF帧图:http://zh.bloggif.com/gif-extract
bejson(杂乱):http://www.bejson.com
atool(杂乱):http://www.atool.org
Punch Card:http://www.kloth.net/services/cardpunch.php
分解素数(ESA):http://www.factordb.com/index.php
文件在线Hash:http://www.atool.org/file_hash.php

2、编码系列

ASCII编码
Base64/32/16编码
shellcode编码
Quoted-printable编码
XXencode编码
UUencode编码
URL编码
Unicode编码
Escape/Unescape编码
HTML实体编码
敲击码(Tap code)
莫尔斯电码(Morse Code)

编码的故事:https://www.docin.com/p-957898329.html

一文读懂字符编码:https://cloud.tencent.com/developer/article/1450938

ASCII 编码

详见 ASCII码表 解释。ASCII(发音:,American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本延伸美国标准信息交换码则可以部分支持其他西欧语言,并等同于国际标准ISO/IEC 646。

ASCII 码使用8位二进制数组合来表示256 种可能的字符。

  • ASCII 编码前128个称为标准ASCII 码。标准ASCII码使用后7位二进制数,即首位二进制数固定为0(0xxxxxxx),以此来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。
    ASCII 控制字符的编号范围是 0-31 和 127(0x00-0x1F和0x7F),共 33 个字符。ASCII 表中的前 32 个字符是不可打印的控制代码,用于控制打印机等外围设备。
    ASCII 可显示字符编号范围是32-126(0x20-0x7E),共95个字符。用键盘敲下空白键所产生的空白字符也算1个可显示字符(显示为空白)。

  • ASCII 编码后128个称为扩展ASCII码。扩展ASCII 码允许将每个字符的第8位用于确定附加的128个特殊符号字符、外来语字母和图形符号。

源文本: The quick brown fox jumps over the lazy dog
ASCII编码对应十进制:84 104 101 32 113 117 105 99 107 32 98 114 111 119 110 32 102 111 120 32 106 117 109 112 115 32 111 118 101 114 32 116 104 101 32 108 97 122 121 32 100 111 103

对应也可以转换成二进制,八进制,十六进制等。

base64 系列编码

Base64 是最常见的编码,除此之外,其实还有 Base16、Base32、Base58、Base85、Base100 等,他们之间最明显的区别就是使用了不同数量的可打印字符对任意字节数据进行编码,比如 Base64 使用了 64 个可打印字符(A-Z、a-z、0-9、+、/),Base16 使用了 16 个可打印字符(A-F、0-9),这里主要讲怎么快速识别,其具体原理可自行百度,Base 系列主要特征如下:

  • Base16:结尾没有等号,数字要多于字母; 
  • Base32:字母要多于数字,明文数量超过 10 个,结尾可能会有很多等号;
  • Base58:结尾没有等号,字母要多于数字;
  • Base64:一般情况下结尾都会有 1 个或者 2 个等号,明文很少的时候可能没有;
  • Base85:等号一般出现在字符串中间,含有一些奇怪的字符;
  • Base100:密文由 Emoji 表情组成。

示例:

编码类型示例一示例二
明文01234567890administrators
Base16303132333435363738393061646D696E6973747261746F7273
Base32GAYTEMZUGU3DOOBZGA======MFSG22LONFZXI4TBORXXE4Y=
Base58cX8j8pvGzppMKVbBNF5dFLUTN5XwM1yLoF
Base64MDEyMzQ1Njc4OTA=YWRtaW5pc3RyYXRvcnM=
Base850JP==1c70M3&rY@:X4hDJ=06Eaa'.EcV
Base100🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐧👘👛👤👠👥👠👪👫👩👘👫👦👩👪

Base64 原理:将要编码的内容按3字节为一组进行分组,最后一组不够3位的则补0(显然最多补两个0)。每组中每字节最高2位改成0不使用,原先各位的内容保持原有顺序往后移;最后在上一步中补了几个0就加几个等号以供解码时按等号个数删除0(经此操作原先3节字就变成了只使用低6位的4字节)

用途:一是SMTP中要以BASE64形式传输二进制文件,二是常用于将二进制数据转成可打印的ASCII码字符进行存储(下文各加密算法的密钥一般使用十六进制字符串形式存储,但也有以base64形式存储)。

其他:本质上讲Base64只能算是一种编码不能算是一种加密算法,PyCryptodome库也不支持。但从Base64让人一下看不懂原本内容是什么的角度来讲,也算是一种加密,

import base64
test_str = "12345abcd"

test_str = "佛祖保佑,永无bug"

base64_byte = base64.b64encode(test_str.encode())
print(f"Base64 编码 ---> {base64_byte.decode('utf-8')}")
print(f"Base64 解码 ---> {base64.b64decode(base64_byte).decode('utf-8')}")

"""
Base64 编码 ---> 5L2b56WW5L+d5L2RLOawuOaXoGJ1Zw==
Base64 解码 ---> 佛祖保佑,永无bug
"""

unicode 编码

Unicode码扩展自ASCII码。在严格的ASCII中,每个字符用8个二进制数(8bit=1byte)
表示。而Unicode是由16个二进制数(2byte)组成的编码字符集。这使得Unicode码能够表示世界上所有语言中的字符、象形文字和其他符号。所以 Unicode 又称为统一码、万国码、单一码,可以满足跨语言、跨平台进行文本转换、处理的要求。

  • 其主要特征:以 \u&# 或 &#x 开头,后面是数字加字母组合

提示:\u 开头和 &#x 开头是一样的,都是 16 进制 Unicode 字符的不同写法,&# 则是 Unicode 字符 10 进制的写法,此外,&# 和 &#x 开头的,也称为 HTML 字符实体转换,字符实体是用一个编号写入 HTML 代码中来代替一个字符,在 HTML 中,某些字符是预留的,如果希望正确地显示预留字符,就必须在 HTML 源代码中使用字符实体。

编码类型示例一示例二
明文12345admin
Unicode\u0031\u0032\u0033\u0034\u0035\u0061\u0064\u006d\u0069\u006e

escape 编码

Escape 编码又叫 %u 编码,Escape 编码就是字符对应 UTF-16BE 表示方式前面加 %u,Escape 不会对 ASCII 字母和数字进行编码,也不会对下面这些 ASCII 标点符号进行编码: * @ - _ + . / ,其他所有的字符都会被转义序列替换。

  • 其主要特征:以 %u 开头,后面是数字加字母组合
编码类型示例一示例二
明文K 哥爬虫我爱 Python
EscapeK%u54E5%u722C%u866B%u6211%u7231Python

URL / Hex 编码

url 编码,是URL(统一资源定位符)编码方式,因为服务器只能解析URL中常用的数字,字母和部
分特殊字符,其它的字符必须通过URL编码进行编码才能被服务器正常解析。

  • url 编码方式:就是在某个字符ascii码的十六进制前面加上 %
  • url 编码特点:密文中有多个%号符

URL 和 Hex 编码的结果是一样的,不同的是当你用 URL 编码网址时是不会把 httphttps 关键字和 /?&= 等连接符进行编码的,而 Hex 编码则全部转化。

  • 其主要特征:以 % 开头,后面是数字加字母组合
编码类型示例
明文https://www.kuaidaili.com/
Unicodehttps://%77%77%77%2E%6B%75%61%69%64%61%69%6C%69%2E%63%6F%6D/
Hex%68%74%74%70%73%3a%2f%2f%77%77%77%2e%6b%75%61%69%64%61%69%6c%69%2e%63%6f%6d%2f

3、加密 算法

Python 加密模块

hashlib

  • hashlib 模块为不同的安全哈希/安全散列(Secure Hash Algorithm)和 信息摘要算法(Message Digest Algorithm)实现了一个公共的、通用的接口,也可以说是一个统一的入口。因为hashlib模块不仅仅是整合了md5和sha模块的功能,还提供了对更多中算法的函数实现,如:MD5,SHA1,SHA224,SHA256,SHA384和SHA512。

hashlib 库:https://docs.python.org/3/library/hashlib.html
廖雪峰 hashlib:https://www.liaoxuefeng.com/wiki/1016959663602400/1017686752491744

使用步骤

  • 1、获取一个哈希算法对应的哈希对象,有两种方法:(1):通过hashlib.new(哈希算法名称, 初始出入信息)来获取这个哈希对象,如:hashlib.new("MD5","username"),hashlib.new("SHA1", "username")等。(2):通过 hashlib.哈希算法名称() 来获取这个哈希对象,如:hashlib.md5(),hashlib.sha1()等。
  • 2、设置追加信息:调用哈希对象的 updata(输入的信息) 方法,设置追加信息。多次调用后等价于把所有追加的参数全部拼接起来作为一个参数,传递给update(),是累加,不是覆盖。
  • 3、获取输入信息对应的摘要:调用已经的到哈希对象的digest()或者hexdigest(),可得到传递给update()方法的字符串参数的摘要信息。
    (1)digest()与hexdigest()的区别:
    a、digest():摘要信息是一个二进制格式的字符串,其中可能包含非ASCII字符,包括NUL字节,该字符串长度可以通过哈希对象的digest_size属性获取。
    b、hexdigest():摘要信息是一个16进制格式的字符串,该字符串中只包含16进制的数字,且长度是digest()返回结果长度的2倍。
import hashlib


def func_1():
    m = hashlib.md5()
    m.update(b"username")
    m.update(b"password") #最终加密的字符串参数是:username + password
    print(m.digest_size)
    print(m.digest())
    print(m.hexdigest())


def func_2():
    # hashlib.new(name[, data])     创建hashlib(非首选), name=算法名, data:数据
    hash = hashlib.new('sha1', 'username'.encode())
    strs = hash.name  # hash名称, 可传给new()使用
    hash.update("password".encode())  # 字节缓冲区 hash.update(x) hash.update(y) == hash.update(x+y)
    hash = hash.copy()  # 拷贝hash对象副本
    print(strs, hash)

    # algorithms_guaranteed返回的是所有平台支持的hash算法的名称,结果是一个集合
    dics = hashlib.algorithms_guaranteed
    print(dics)

    # algorithms_available返回的是在Python解析器中可用的hash算法的名称, 传递给new()时, 可识别,结果也是一个集合
    dics1 = hashlib.algorithms_available
    print(dics1)

    """
    加盐加密方法介绍:
    hashlib.pbkdf2_hmac(hash_name, password, salt, iterations, dklen=None)
    hash_name:hash名称, password:数据, salt:盐, iterations:循环次数, dklen:密钥长度
    """
    hash_bytes = hashlib.pbkdf2_hmac('SHA1', b'password', b'80', 100, 64)
    print(hash_bytes)

    num = hash.digest_size  # hash结果的大小
    num1 = hash.block_size  # hash算法的内部块的大小
    print(num, num1)


func_1()
func_2()

hmac (引入 秘钥)

  • hmac 算法也是一种单项加密算法,并且它是基于上面各种哈希算法/散列算法,然后在运算过程中引入密钥来增强安全性。hmac模块实现了HAMC算法,提供了相应的函数和方法,且与 hashlib 提供的 api 基本一致。

hmac 库:https://docs.python.org/3/library/hmac.html
廖雪峰 hmac:https://www.liaoxuefeng.com/wiki/1016959663602400/1183198304823296

使用步骤:

  • hmac模块的使用步骤与hashlib模块基本一致,只是在获取hmac对象时,只能使用hmac.new()函数,因为hmac模块没有提供与具体哈希算法对应的函数来获取hmac对象。如:hashlib可以用hashlib.md5()来产生一个哈希对象,但是hmac不行。
import hmac


def func_1():
    hash = hmac.new(b"pwd", digestmod='md5')
    hash.update(b"uname")
    h_str = hash.hexdigest()
    print(h_str.upper())
    boolean = hmac.compare_digest(
        h_str,
        hmac.new("pwd".encode(), "uname".encode(), digestmod='md5').hexdigest()
    )
    print(boolean)


def func_2():
    # 创建key和内容,再进行加密
    # hmac.new(key, msg=None, digestmod=None)
    # 创建新的hmac对象, key:键, msg:update(msg), digestmod:hash名称(同hashlib.new())(默认md5)
    hc = hmac.new("userid".encode(), digestmod='md5')
    hc.update("username".encode())  # 字节缓冲区 hc.update(a) hc.update(b) == hc.update(a+b)
    hash_bytes = hc.digest()  # 字节hash
    print(hash_bytes)
    hash_str = hc.hexdigest().upper()  # 16进制hash字符串
    print(hash_str)
    hc = hc.copy()  # 拷贝hmac副本
    print(hc)
    num = hc.digest_size  # hash大小
    print(num)
    strs = hc.name  # hash名称
    print(strs)
    # hmac.compare_digest(a, b) // 比较两个hash密钥是否相同, 参数可为: str / bytes-like object, (注:建议使用此方法,不建议使用a==b)
    boolean = hmac.compare_digest(
        hmac.new("uid".encode(), "uname".encode(), digestmod='md5').digest(),
        hmac.new("uid".encode(), "uname".encode(), digestmod='md5').digest()
    )
    print(boolean)


func_1()
func_2()

pycryptodome & Crypto

Python 中有很多算法是通过第三方库 Cryptodome 或者 Crypto 来实现的,Cryptodome 几乎是 Crypto 的替代品,Crypto 已经停止更新好多年了,有很多未知错误,所以不建议安装 Crypto。

PyCryptodome 是 python 一个强大的加密算法库,支持几乎所有主流加密算法,包括 MD5、SHA、BLAKE2b、BLAKE2s、HMAC、PBKDF2、AES、DES、3DES(Triple DES)、ECC、RSA、RC4 等。可以实现常见的单向加密、对称加密、非对称加密和流加密算法。

  • 安装 PyCryptodome :pip install pycryptodome
    模块安装在 Crypto 包。注意:不要同时安装 pycrypto 和 pycryptodome ,会相互干扰。
  • 安装 PyCryptodomex :pip install pycryptodomex
    所有模块都安装在Cryptodome软件包下。pycrypto 和 pycryptodomex 可以共存。

这里 pycryptodome 和 pycryptodomex 都属于同一个帐户并指向 same Github repository

这两个模块代码相同,只是名称不同。

  • pycryptodome 与 pyCrypto 有一些关联,可以被视为从 PyCrypto 迁移到 PyCryptodome 时的替代品。包名是 是 Crypto。如果历史遗留问题则可以使用 pycryptodome

  • pycryptodomex 是 PyCryptodome 的独立版本,包名是 Cryptodome

支持的 所有 加密算法

import hmac
import hashlib


def func_print_all():
    # 标准库支持的加密算法
    available_algo = hashlib.algorithms_available
    list(map(lambda x=None: print(f'加密算法 ---> {x}'), available_algo))
    """
    sha3_224
    shake_256
    ripemd160
    sha512
    sha224
    blake2b
    sha3_512
    sm3
    md5-sha1
    blake2s
    sha512_256
    sha384
    shake_128
    md5
    sha3_384
    sha1
    sha256
    sha3_256
    sha512_224
    """


def func_md5():
    # 实例化方法一,直接使用算法名;这种形式不完全支持hashlib.algorithms_available中的算法
    # m = hashlib.md5()
    # 实例化方法二,使用new方法;所有支持的算法名看上边hashlib.algorithms_available
    m = hashlib.new("md5")  # 使用hashlib实现hash算法(以md5为例)
    # update内是要加密的内容
    # 调用多次update时,效果是+=,即连续多次update表示在原先内容上追加而不是替换
    m.update(b"username")
    m.update(b"password")  # 最终加密的字符串参数是:username + password
    # 以十六进制字符串形式输出
    md5_str = m.hexdigest()
    print(f'md5_str ---> {md5_str}')


def func_hmac():
    # 实例化原形是hmac.new(key, msg=None, digestmod=None)
    # key--加密用的盐值
    # msg--要加密码的内容,可为空而后用update方法设置
    # digestmod--加密算法,默认为md5,所有支持的算法名看上边hashlib.algorithms_available
    m = hmac.new(b"123", digestmod="sha1")  # 使用hmac实现hmac算法(以sha1为例)
    # update使用+=,即连续多次update表示在原先内容上追加而不是替换
    m.update(b"123456")
    # 以十六进制字符串形式输出
    hmac_str = m.hexdigest()
    print(f'hmac_str ---> {hmac_str}')


func_print_all()
func_md5()
func_hmac()

单向 散列

注意:是单向散列,不是加密。而且由于hash算法是不可逆的,所以不存在解密的逻辑。

单向 散列:只能对明文数据进行单向散列 ( 如果不严格来讲也叫 单向加密 ),而不能解密数据。

  • 单向加密算法,又称哈希函数、散列函数、杂凑函数、摘要算法,英文名One-way encryption algorithm。
  • 原理:单向加密如其名只能加密不能解密(彩虹表攻击不是正经的解密),不能解密的原因是本质上并不是用IV(Initial Vector)去加密M输出M的密文,而是用M去加密IV输出IV的密文。
  • 用途:消息认证摘要、内容或文档的数字指纹、口令存储。
  • 其他:单向加密又可以分为hash和hmac两大类,hmac和hash的算法是一样的,其实可以认为hmac就是hash加盐的形式。

不论是sha1, sha256, md5 都属于摘要算法,都是用来计算hash值,只是散列的程度不同而已。常用的算法实现:

  • MD5:128bits
  • SHA:SHA1(160bits), SHA224, SHA256, SHA384

特点:

  • - 不可逆:无法根据数据指纹/特征码还原原来的数据。
  • - 容易计算:从原数据计算出MD5值很容易。
  • - 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
  • - 定长输出:任意长度的数据,算出的MD5值长度都是固定的。

python 的标准库就很容易的实现单向加密算法。

  • hash 类算法使用 hashlib 库实现
  • hmac 类算法使用 hmac 库实现

md5

MD5 本质是一种消息摘要算法,一个数据的 MD5 值是唯一的,同一个数据不可能计算出多个不同的 MD5 值,但是,不同数据计算出来的 MD5 值是有可能一样的,知道一个 MD5 值,理论上是无法还原出它的原始数据的,MD5 是最容易辨别的,主要特征如下:

  • 密文一般为 16 位或者 32 位,其中 16 位是取的 32 位第 9~25 位的值;
  • 组成方式为字母(a-f)和数字(0-9)混合,字母可以全部是大写或者小写。

除了通过密文特征来判断以外,还可以搜索源代码,标准 MD5 的源码里是包含了一些特定的值的,没有这些特定值,就无法实现 MD5:

  • 0123456789ABCDEF、0123456789abcdef
  • 1732584193、-271733879、-1732584194、271733878

提示:某些特殊情况下,密文的长度也有可能不止 16 位或者 32 位,有可能是在官方算法上有一些魔改,通常也是在 16 位的基础上,左右填充了一些随机字符串。

编码类型示例一示例二
明文123456admin
MD5
(16 位小写)
49ba59abbe56e0577a57a5a743894a0e
MD5
(16 位大写)
49BA59ABBE56E0577A57A5A743894A0E
MD5
(32 位小写)
e10adc3949ba59abbe56e057f20f883e21232f297a57a5a743894a0e4a801fc3
MD5
(32 位大写)
E10ADC3949BA59ABBE56E057F20F883E21232F297A57A5A743894A0E4A801FC3

sha 系列

SHA 是比 MD5 更安全一点的摘要算法,SHA 通常指 SHA 家族算法,分别是 SHA-1、SHA-2、SHA-3。

  • SHA-2 是 SHA-224、SHA-256、SHA-384、SHA-512 的并称,
  • SHA-3 是 SHA3-224、SHA3-256、SHA3-384、SHA3-512、SHAKE128、SHAKE256 的并称。其名字的后缀的数字就代表了结果的大小(bit),注意,SHAKE 算法结果的大小并不是固定的,

算法特征如下:

  • SHA-1:字母(a-f)和数字(0-9)混合,固定位数40位;
  • SHA-224/SHA3-224:字母(a-f)和数字(0-9)混合,固定位数56位;
  • SHA-256/SHA3-256:字母(a-f)和数字(0-9)混合,固定位数64位;
  • SHA-384/SHA3-384:字母(a-f)和数字(0-9)混合,固定位数96位;
  • SHA-512/SHA3-512:字母(a-f)和数字(0-9)混合,固定位数128位。

示例:

编码类型示例
明文123456
SHA-17c4a8d09ca3762af61e59520943dc26494f8941b
SHA-2568d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
SHA3-256c888c9ce9e098d5864d3ded6ebcc140a12142263bace3a23a36f9905f12bd64a

对于需要加密安全性较高的场景,SHA-256是一个不错的选择。

import hashlib

# 需要进行哈希的数据
data = "Hello, SHA-256!"

# 使用 hashlib 库计算 SHA-256 哈希值
hash_object = hashlib.sha256(data.encode())
hash_hex = hash_object.hexdigest()

print(f"SHA-256 Hash: {hash_hex}")

在处理大量数据或文件时,可以使用update()方法分块处理数据,这样可以节省内存,例如:

import hashlib

# 创建一个 sha256 哈希对象
hash_object = hashlib.sha256()

# 假设 'large_data' 是一个大文本数据或来自大文件的数据块
# 你可以分批次对数据更新哈希对象
large_data = ['Hello, ', 'SHA-256!']  # 示例分块数据
for chunk in large_data:
    hash_object.update(chunk.encode())

# 最终获取整体数据的哈希值
hash_hex = hash_object.hexdigest()

print(f"SHA-256 Hash: {hash_hex}")

非常适合处理大文件或者在网络传输中计算数据的哈希值,可以有效地减少程序的内存使用。

hmac 系列

hmac 这种算法就是在 MD5、SHA 两种加密的基础上引入了秘钥,其密文也和 MD5、SHA 类似,密文的长度和使用的 MD5、SHA 算法对应密文的长度是一样的。特征如下:

  • HMAC-MD5:字母(a-f)和数字(0-9)混合,位数一般为32位;
  • HMAC-SHA-1:字母(a-f)和数字(0-9)混合,固定位数40位;
  • HMAC-SHA-224 / HMAC-SHA3-224:字母(a-f)和数字(0-9)混合,固定位数56位;
  • HMAC-SHA-256 / HMAC-SHA3-256:字母(a-f)和数字(0-9)混合,固定位数64位;
  • HMAC-SHA-384 / HMAC-SHA3-384:字母(a-f)和数字(0-9)混合,固定位数96位;
  • HMAC-SHA-512 / HMAC-SHA3-512:字母(a-f)和数字(0-9)混合,固定位数128位。

HMAC 和 SHA、MD5 的密文都很像,当无法确定是否为 HMAC 时,可以通过其名称搜索到加密方法,如果传入了密钥 key,说明就是 HMAC,当然你也可以直接当做是 SHA 或 MD5 来解,解密失败时就得考虑是否有密钥,是否为 HMAC 。

在 JS 中通常一个 HMAC 加密方法是这样写的:

function HmacSHA1Encrypt(word, key) {
    return CryptoJS.HmacSHA1(word, key).toString();
}

示例(密钥 123456abcde):

编码类型示例
明文123456
HMAC-MD5432bb95bb00005ddce4a1c757488ed95
HMAC-SHA-137a04076b7736c44460d330ee0d00014428b175e
HMAC-SHA-25650cb1345366df11140fb91b43caaf69627e3f5529705ddf6b0d0cae67986e585
HMAC-SHA3-256b808ed9f66436e89fba527a01d1d6044318fea8599d9f39bfb6bec4843964bf3

对称 加密 算法

"对称加密" 是指数据 "加密与解密" 使用相同的密钥。

  • 对称加密算法 (英文名Symmetric Encryption Algorithms),又称密钥加密算法、单密钥算法、共享密钥算法。即加密与解密使用的密钥相同。
  • 原理:对称加密算法最关键的就是SP变换,S变换通过代替操作实现混乱(即消除统计信息),P变换通过换位操作实现扩散(即雪崩效应);加解密是使用同一个密钥的逆操作过程。
  • 用途:对称加密可以还原内容,且代替和换位操作运算量不大,适用于大量内容的加解密。对称加密算法的缺点是加解密双方密钥分发困难。
  • 其他:对称加密算法有ECB、CBC、CFB、OFB、CTR等等多种模式,各种模式的加密是有些区别的,比如ECB不需要IV、CBC等则需要IV、EAX则需要nonce和tag等等,所以实现不同模式时写法会有差别需要具体研究,不能完全照搬下边的例子。对称加密算法实现代码(以AES算法CBC模式为例)
  • 由于算法一般都是公开的,因此机密性几乎完全依赖于密钥。
  • 通常使用密钥一般小于256bit。因为密钥越大,加密越强,但加密与解密的过程越慢。

DES

  • DES:Data Encryption Standard,秘钥长度为56位,2003年左右被破解--秘钥可以暴力破解。DES算法为密码体制中的对称密码体制,又被称为美国数据加密标准。DES是一个分组加密算法,典型的DES以64位为分组对数据加密,加密和解密用的是同一个算法。
  • DES算法的入口参数有三个:Key、Data、Mode。其中Key为8个字节共64位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。密钥长64位,密钥事实上是56位参与DES运算(第8、16、24、32、40、48、56、64位是校验位,使得每个密钥都有奇数个1),对64位二进制数据块进行加密,分组后的明文组和56位的密钥按位替代或交换的方法形成密文组。每次加密对64位的输入数据进行16轮编码,经过一系列替换和移位后转换成完全不同的64位输出数据。
# DES加密解密

from Crypto.Cipher import DES

# key: 8个字节
des = DES.new(b"alexissb", mode=DES.MODE_CBC, IV=b"01020304")
data = "我要吃饭".encode("utf-8")
# # 需要加密的数据必须是16的倍数
# # 填充规则: 缺少数据量的个数 * chr(缺少数据量个数)
pad_len = 8 - len(data) % 8
data += (pad_len * chr(pad_len)).encode("utf-8")

bs = des.encrypt(data)
print(bs)
# 解密
des = DES.new(key=b'alexissb', mode=DES.MODE_CBC, IV=b"01020304")
data = b'6HX\xfa\xb2R\xa8\r\xa3\xed\xbd\x00\xdb}\xb0\xb9'
result = des.decrypt(data)
print(result.decode("utf-8"))

3DES

  • DES的改进版本。3DES(或称为Triple DES)是三重数据加密算法(TDEA,Triple Data Encryption Algorithm)块密码的通称。它相当于是对每个数据块应用三次DES加密算法。由于计算机运算能力的增强,原版DES密码的密钥长度变得容易被暴力破解。3DES即是设计用来提供一种相对简单的方法,即通过增加DES的密钥长度来避免类似的攻击,而不是设计一种全新的块密码算法。3DES(即Triple DES)是DES向AES过渡的加密算法(1999年,NIST将3-DES指定为过渡的加密标准),加密算法,其具体实现如下:设Ek()和Dk()代表DES算法的加密和解密过程,K代表DES算法使用的密钥,M代表明文,C代表密文,这样:
            3DES加密过程为:C=Ek3(Dk2(Ek1(M)))
            3DES解密过程为:M=Dk1(EK2(Dk3(C)))

AES

  • 高级加密标准 ( Advanced Encryption Standard,缩写:AES),支持的秘钥长度包括 128bits,192bits,258bits,384bits,512bits。在密码学中又称 "Rijndael加密法",是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。AES为分组密码,分组密码也就是把明文分成一组一组的,每组长度相等,每次加密一组数据,直到加密完整个明文。在AES标准规范中,分组长度只能是128位,也就是说,每个分组为16个字节(每个字节8位)。密钥的长度可以使用128位、192位或256位。密钥的长度不同,推荐加密轮数也不同。一般常用的是128位,秘钥长度越长,数据加密与解密的时间就越久。

AES、DES、3DES、RC4、Rabbit 等加密算法的密文通常没有固定的长度,他们通常使用 crypto-js 来实现,比如 AES 加解密示例如下:

CryptoJS = require("crypto-js")

var key = CryptoJS.enc.Utf8.parse("0123456789abcdef");
var iv = CryptoJS.enc.Utf8.parse("0123456789abcdef");

function AESEncrypt(word) {
    var srcs = CryptoJS.enc.Utf8.parse(word);
    var encrypted = CryptoJS.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return encrypted.toString();
}

function AESDecrypt(word) {
    var srcs = word;
    var decrypt = CryptoJS.AES.decrypt(srcs, key, {
        iv: iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    });
    return decrypt.toString(CryptoJS.enc.Utf8);
}

console.log(AESEncrypt("K哥爬虫"))
console.log(AESDecrypt("nSk3wCd92s08sQ9N+VHNvA=="))

在 crypto-js 中,也有一些特定的关键字,我们可以通过搜索这些关键字来快速定位到 crypto-js:

CryptoJS、crypto-js、iv、mode、padding、createEncryptor、createDecryptor
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=、0xffffffff、0xffff

定位到 CryptoJS 后,观察加密方法,比如

  • AES 就是 CryptoJS.AES.encrypt
  • DES 就是CryptoJS.DES.encrypt
  • 3DES 就是CryptoJS.TripleDES.encrypt

注意他们的 iv、mode、padding,拿下来就可以本地复现。

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes


def func_encrypt_data():
    # 生成一个16字节(即128位)的随机AES密钥
    key = get_random_bytes(16)
    # 创建一个AES加密器,使用CBC模式
    cipher = AES.new(key, AES.MODE_CBC)
    # 需要加密的数据
    data = "要加密的秘密信息".encode('utf-8')
    # pad函数用于分组和填充,然后加密
    ct_bytes = cipher.encrypt(pad(data, AES.block_size))
    # 获取用于后续解密的初始化向量
    iv = cipher.iv
    print("初始化向量 (IV):", iv)
    print("加密后的数据:", ct_bytes)
    return key, iv, ct_bytes


def func_decrypt_data():
    key, iv, ct_bytes = func_encrypt_data()
    # 实例化加密套件
    cipher = AES.new(key, AES.MODE_CBC, iv)
    # 解密,如无意外data值为最先加密的b"123456"
    data = unpad(cipher.decrypt(ct_bytes), AES.block_size)
    print(f"解密后的数据 ---> {data.decode('utf-8')}")


func_decrypt_data()

源码中 AES.new() 方法:

def new(key, mode, *args, **kwargs):
    """Create a new AES cipher.

    Args:
      key(bytes/bytearray/memoryview):
        The secret key to use in the symmetric cipher.
        必须是 16 (*AES-128)*, 24 (*AES-192*) or 32 (*AES-256*) 字节长度.
        对于 ``MODE_SIV`` 模式, 必须翻倍,分别是 32, 48, 或者 64 字节长度.
		
      mode (a ``MODE_*`` constant):
        The chaining mode to use for encryption or decryption.
        If in doubt, use ``MODE_EAX``.

      iv (bytes/bytearray/memoryview):
	     如果不提供,则默认使用随机生成的.
        (初始化向量,只支持 ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``, ``MODE_OPENPGP`` mode).
        对于 ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` 必须是16字节长度.
        对于 ``MODE_OPENPGP``, 加密时必须是 16字节长度,解密时必须是18字节长度
     
       
      nonce (bytes/bytearray/memoryview):
        (Only applicable for ``MODE_CCM``, ``MODE_EAX``, ``MODE_GCM``,
        ``MODE_SIV``, ``MODE_OCB``, and ``MODE_CTR``).

        A value that must never be reused for any other encryption done
        with this key (except possibly for ``MODE_SIV``, see below).

        For ``MODE_EAX``, ``MODE_GCM`` and ``MODE_SIV`` there are no
        restrictions on its length (recommended: **16** bytes).

        For ``MODE_CCM``, its length must be in the range **[7..13]**.
        Bear in mind that with CCM there is a trade-off between nonce
        length and maximum message size. Recommendation: **11** bytes.

        For ``MODE_OCB``, its length must be in the range **[1..15]**
        (recommended: **15**).

        For ``MODE_CTR``, its length must be in the range **[0..15]**
        (recommended: **8**).

        For ``MODE_SIV``, the nonce is optional, if it is not specified,
        then no nonce is being used, which renders the encryption
        deterministic.

        If not provided, for modes other than ``MODE_SIV``, a random
        byte string of the recommended length is used (you must then
        read its value with the :attr:`nonce` attribute).

      segment_size (integer):
        (Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
        are segmented in. It must be a multiple of 8.
        If not specified, it will be assumed to be 8.

      mac_len (integer):
        (Only ``MODE_EAX``, ``MODE_GCM``, ``MODE_OCB``, ``MODE_CCM``)
        Length of the authentication tag, in bytes.

        It must be even and in the range **[4..16]**.
        The recommended value (and the default, if not specified) is **16**.

      msg_len (integer):
        (Only ``MODE_CCM``). Length of the message to (de)cipher.
        If not specified, ``encrypt`` must be called with the entire message.
        Similarly, ``decrypt`` can only be called once.

      assoc_len (integer):
        (Only ``MODE_CCM``). Length of the associated data.
        If not specified, all associated data is buffered internally,
        which may represent a problem for very large messages.

      initial_value (integer or bytes/bytearray/memoryview):
        (Only ``MODE_CTR``).
        The initial value for the counter. If not present, the cipher will
        start counting from 0. The value is incremented by one for each block.
        The counter number is encoded in big endian mode.

      counter (object):
        (Only ``MODE_CTR``).
        Instance of ``Crypto.Util.Counter``, which allows full customization
        of the counter block. This parameter is incompatible to both ``nonce``
        and ``initial_value``.

      use_aesni: (boolean):
        Use Intel AES-NI hardware extensions (default: use if available).

    Returns:
        an AES object, of the applicable mode.
    """

    kwargs["add_aes_modes"] = True
    return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
# AES加密
from Crypto.Cipher import AES

"""
长度
    16: *AES-128*
    24: *AES-192*
    32: *AES-256*
    
MODE 加密模式. 
    常见的ECB, CBC
    以下内容来自互联网~~
    ECB:是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。
    CBC:是一种循环模式,前一个分组的密文和当前分组的明文异或或操作后再加密,这样做的目的是增强破解难度。
    CFB/OFB:实际上是一种反馈模式,目的也是增强破解的难度。
    FCB和CBC的加密结果是不一样的,两者的模式不同,而且CBC会在第一个密码块运算时加入一个初始化向量。
"""
aes = AES.new(b"alexissbalexissb", mode=AES.MODE_CBC, IV=b"0102030405060708")
data = "我吃饭了"
data_bs = data.encode("utf-8")
# 需要加密的数据必须是16的倍数
# 填充规则: 缺少数据量的个数 * chr(缺少数据量个数)
pad_len = 16 - len(data_bs) % 16
data_bs += (pad_len * chr(pad_len)).encode("utf-8")

bs = aes.encrypt(data_bs)
print(bs)

AES解密

from Crypto.Cipher import AES


aes = AES.new(b"alexissbalexissb", mode=AES.MODE_CBC, IV=b"0102030405060708")
# 密文
bs = b'\xf6z\x0f;G\xdcB,\xccl\xf9\x17qS\x93\x0e'
result = aes.decrypt(bs)  # 解密
print(result.decode("utf-8"))

初始向量 iv、加密模式 mode、填充方式 padding

在一些对称和非对称加密算法中,经常会用到以下三个参数:

  • 初始向量 iv:在密码学中,初始向量(initialization vector,缩写为 iv),又称初始变数(starting variable,缩写为 sv),与密钥结合使用,作为加密数据的手段,它是一个固定长度的值,iv 的长度取决于加密方法,通常与使用的加密密钥或密码块的长度相当,一般在使用过程中会要求它是随机数或拟随机数,使用随机数产生的初始向量才能达到语义安全,让攻击者难以对原文一致且使用同一把密钥生成的密文进行破解。 参考资料:维基百科:https://en.wikipedia.org/wiki/Initialization_vector
  • 加密模式 mode:目前流行的加密和数字认证算法,都是采用块加密方式,就是将需要加密的明文分成固定大小的数据块,然后对其执行密码算法,得到密文。数据块的大小通常采用跟密钥一样的长度。加密模式在加密算法的基础上发展出来,同时也可以独立于加密算法而存在,加密模式定义了怎样通过重复利用加密算法将大于一个数据块大小的明文转化为密文,描述了加密每一数据块的过程。
  • 填充方式 padding:块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的。因此部分模式最后一块数据在加密前需要进行填充。有数种填充方法,其中最简单的一种是在明文的最后填充空字符以使其长度为块长度的整数倍。

目前利用较多的 加密模式 mode 有以下几种:

参考资料:维基百科:https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

  • 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 模式中,有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。

常见 填充方式 padding 有以下几种:

维基百科:https://en.wikipedia.org/wiki/Padding_(cryptography)
PKCS7/PKCS5 填充算法:https://segmentfault.com/a/1190000019793040

  • PKCS7:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为需要填充的字节长度值。
  • PKCS5:PKCS5 作为 PKCS7 的子集算法,概念上没有什么区别,只是在 blockSize 上固定为 8 bytes,即块大小固定为 8 字节。
  • ZeroPadding:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为 0 。
  • ISO10126:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充随机数值。
  • ANSIX923:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充数字零。

非对称 加密 算法

  • 非对称加密算法,又称公钥加密算法,英文名 Asymmetric Cryptographic Algorithm。指的是加密和解密使用不同的秘钥。一把作为公开的公钥,另一把作为私钥。使用公钥进行加密,私钥用于解密。反之亦然(被私钥加密的数据也可以被公钥解密) 。在实际使用时,私钥一般保存在发布者手中,是私有的不对外公开的,只将公钥对外公布,就能实现只有私钥的持有者才能将数据解密的方法。这种加密方式安全系数很高,因为它不用将解密的密钥进行传递,从而没有密钥在传递过程中被截获的风险,而破解密文几乎又是不可能的。但是算法的效率低,所以常用于很重要数据的加密。
  • 原理:非对称加密依赖与明文经过与公钥进行数学运算可得出密文,而密文经过与密钥进行数学运算又可得到明文。
  • 用途:非对称加密算法的优点是密钥分发简单,但缺点也是很明显的,其加解密过程依赖于数学运算运算量大所以加解密速度慢(另外同样的密钥强度其安全性弱于对称加密算法),其只适用于少量内容的加解密,最典型的就是 https 中用于完成对称密钥的交换。事实上,公钥加密算法很少用于数据加密,它通常只是用来做身份认证,因为它的密钥太长,加密速度太慢--公钥加密算法的速度甚至比对称加密算法的速度慢上3个数量级(1000倍)。

常用的公钥加密算法有:

  • RSA:可以实现数字签名 和 数据加密。RSA公钥加密算法是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。RSA就是他们三人姓氏开头字母拼在一起组成的。 RSA是目前最有影响力的公钥加密算法,它能够抵抗到目前为止已知的绝大多数密码攻击,RSA算法基于一个十分简单的数论事实:将两个大质数相乘十分容易,但是想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥。
  • DSA:只能实现数字签名,不能实现数据加密

特点:

  • - 加密与解密使用的不同的密钥。
  • - 实际上它所使用的密钥是一对儿,一个叫公钥,一个叫私钥。这对密钥不是独立的,公钥是从私钥中提炼出来,因此私钥是很长的,968位、1024位、2048位、4096位的都有。
  • - 通常公钥是公开的,所有人都可以得到;私钥是不能公开的,只有自己才有。
  • - 用公钥机密的内容只能用与之对应的私钥才能解密,反之亦然。

RSA

RSA 加密算法是一种非对称加密算法,通过公钥加密结果,必须私钥解密。同样私钥加密结果,公钥可以解密,应用非常广泛,在网站中通常使用 JSEncrypt 库来实现,其最大的特征就是有一个设置公钥的过程,我们可以通过以下方法来快速初步判断是否为 RSA 算法:

  • 搜索关键词 new JSEncrypt(),JSEncrypt 等,一般会使用 JSEncrypt 库,会有 new 一个实例对象的操作;
  • 搜索关键词 setPublicKey、setKey、setPrivateKey、getPublicKey 等,一般实现的代码里都含有设置密钥的过程。

RSA 的私钥、公钥、明文、密文长度也有一定对应关系,也可以从这方面初步判断:

私钥长度
(Base64)
公钥长度
(Base64)
明文长度密文长度
4281281~5388
8122161~117172
15883921~245344

非对称加密算法实现代码(以RSA为例)

生成密钥对代码如下:

from Crypto.PublicKey import RSA  # 生成密钥对

key = RSA.generate(2048)

# 提取私钥并存入文件
private_key = key.export_key()
file_out = open("private_key.pem", "wb")
file_out.write(private_key)

# 提取公钥存入文件
public_key = key.publickey().export_key()
file_out = open("public_key.pem", "wb")
file_out.write(public_key)

加密、解密

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP


def func_encrypt():
    # 要加密的内容
    data = b"123456"
    # 从文件中读取公钥
    public_key = RSA.import_key(open("public_key.pem").read())
    # 实例化加密套件
    cipher = PKCS1_OAEP.new(public_key)
    # 加密
    encrypted_data = cipher.encrypt(data)
    print(encrypted_data)
    return encrypted_data


def func_decrypt():
    # 从私钥文件中读取私钥
    private_key = RSA.import_key(open("private_key.pem", "rb").read())
    # 实例化加密套件
    cipher = PKCS1_OAEP.new(private_key)
    encrypted_data = func_encrypt()
    # 解密,如无意外data值为最先加密的b"123456"
    data = cipher.decrypt(encrypted_data)
    print(data)

func_decrypt()

示例:

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto import Random
import base64

# 随机
gen_random = Random.new


# 生成秘钥
rsa_key = RSA.generate(1024)
with open("rsa_public.pem", mode="wb") as f:
    f.write(rsa_key.publickey().exportKey())

with open("rsa_private.pem", mode="wb") as f:
    f.write(rsa_key.exportKey())


def func_encrypt():
    # 加密
    data = "我要吃饭了"
    with open("rsa_public.pem", mode="r") as f:
        pk = f.read()
        rsa_pk = RSA.importKey(pk)
        rsa = PKCS1_v1_5.new(rsa_pk)

        result = rsa.encrypt(data.encode("utf-8"))
        # 处理成b64方便传输
        b64_result = base64.b64encode(result).decode("utf-8")
        print(b64_result)


def func_decrypt():
    data = "e/spTGg3roda+iqLK4e2bckNMSgXSNosOVLtWN+ArgaIDgYONPIU9i0rIeTj0ywwXnTIPU734EIoKRFQsLmPpJK4Htte+QlcgRFbuj/hCW1uWiB3mCbyU3ZHKo/Y9UjYMuMfk+H6m8OWHtr+tWjiinMNURQpxbsTiT/1cfifWo4="
    # 解密
    with open("rsa_public.pem", mode="r") as f:
        rsa_pk = RSA.importKey(f.read())
        rsa = PKCS1_v1_5.new(rsa_pk)
        result = rsa.decrypt(base64.b64decode(data), gen_random)
        print(result.decode("utf-8"))

一直以为私钥加密公钥解密和公钥加密私钥解密没什么两样,但首先一是和一个朋友说用私钥加密发送回来时她疑或说私钥可以加密吗,然后回公司又和领导说私钥加密公钥解密的时候他直接说私钥不能加密只能做签名。

首先说私钥加密公钥解密在数学原理上是可行的,而且所谓的数字签名其本质就是我用你的公钥可以解开这加密的内容说明这些内容就是你的,即数字签名和签名认证本质就是私钥加密公钥解密。

但另一方面,因为公钥是公开的所以私钥加密并不能起加密通信的作用,所以一般没有直接的私钥加密公钥解密的操作----但我不太明白为什么PyCryptodome这些库在程序试图使用私钥加密时直接报错拒绝执行(TypeError: This is not a private key),虽然没什么用,私钥加密也没有什么危害吧?

from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA

# 以下是签名部分
# 要签名的内容
data = b'123456'
# 获取要签名的内容的HASH值。摘要算法是什么不重要,只要验证时使用一样的摘要算法即可
digest = SHA256.new(data)
# 读取私钥
private_key = RSA.import_key(open('private_key.pem').read())
# 对HASH值使用私钥进行签名。所谓签名,本质就是使用私钥对HASH值进行加密
signature = pkcs1_15.new(private_key).sign(digest)

# 以下是签名校验部分
# 签名部分要传给签名校验部分三个信息:签名内容原文、摘要算法、HASH值签名结果
# 获取被签名的内容的HASH值。使用与签名部分一样的摘要算法计算
digest = SHA256.new(data)
# 读取公钥
public_key = RSA.import_key(open('public_key.pem').read())

try:
    # 进行签名校验。本质上就是使用公钥解密signature,看解密出来的值是否与digest相等
    # 相等则校验通过,说明确实data确实原先的内容;不等则校验不通过,data或signature被篡改
    # 可能有人会想,如果我先修改data然后再用自己的私钥算出signature,是不是可以完成欺骗?
    # 答案是不能,因为此时使用原先的公钥去解signature,其结果不会等于digest
    pkcs1_15.new(public_key).verify(digest, signature)
    print(f"有效的签名")
except (ValueError, TypeError):
    print("无效的签名")

流 加密算法

流加密算法说明。别称:流加密算法,又称序列加密算法,英文名Stream cipher。

原理:流加密算法加密和解密也使用同一个密钥,所以从咬文嚼字来说他也属于对称加密算法。流加密算法与前边单向加密算法、对称加密算法、非对称加密算法的区别是前三者都是分组加密算法即一个分组使用同一个密钥,而流加密算法每一位都使用不同的密钥。

用途:流加密主要基于异或(xor)操作,运算相对简单,但安全性较低,没有很多的使用场景,最典型的是WEP上的使用但也正由于其安全性问题导致WEP的淘汰。

流加密算法实现代码(以RC4为例)

from Crypto.Cipher import ARC4
from Crypto.Hash import SHA
from Crypto.Random import get_random_bytes

# 要加密的内容
data = b"123456"
# 流加密密码长度是可变的,RC4为40到2048位
# 一般上使用一个字符串作为初始密钥,然后再用sha1等生成真正的密钥
# 我们这是直接点,随机生成16字节(即128位)作为密钥
key = get_random_bytes(16)
# 实例化加密套件
cipher = ARC4.new(key)
# 加密内容
encrypted_data = cipher.encrypt(data)

# 注意在即便加解密像这里一样在同一文件里,解密时一定要重新实例化不然解密不正确
cipher = ARC4.new(key)
# 解密,如无意外data为前边加密的b"123456"
data = cipher.decrypt(encrypted_data)

国产加密算法:SM 系列

[1] snowland-smx-python:https://gitee.com/snowlandltd/snowland-smx-python
[2] gmssl:https://github.com/duanhongyi/gmssl
[3] gmssl-python:https://github.com/gongxian-ding/gmssl-python
[4] sm-crypto:https://www.npmjs.com/package/sm-crypto
[5] 国家密码管理局:https://www.sca.gov.cn/
[6] 密码标准委员会:http://www.gmbz.org.cn/
[7] 开源国密算法工具箱:http://gmssl.org/
[8] 国密算法源代码下载:http://www.scctc.org.cn/templates/Download/index.aspx?nodeid=71
[9] GM/T 密码行业标准查询:http://www.gmbz.org.cn/main/bzlb.html

SM1 - SM9、ZUC

SM 代表商密,即商业密码,是用于商业不涉及国家秘密的密码技术,由中国国家密码管理局发布的一系列国产加密算法。包括 SM1、SM2、SM3 、SM4、SM7、SM9、ZUC(祖冲之加密算法)等。​​​​​​SM1 和 SM7 的算法不公开,其余算法都已成为 ISO/IEC 国际标准,SM 系列算法在我国一些 gov 网站上有应用。

算法名称算法类别应用领域特点
SM1对称(分组)加密算法芯片分组长度、密钥长度均为 128 比特
SM2非对称(基于椭圆曲线 ECC)加密算法数据加密ECC 椭圆曲线密码机制 256 位,相比 RSA 处理速度快,消耗更少
SM3散列(hash)函数算法完整性校验安全性及效率与 SHA-256 相当,压缩函数更复杂
SM4对称(分组)加密算法数据加密和局域网产品分组长度、密钥长度均为 128 比特,计算轮数多
SM7对称(分组)加密算法非接触式 IC 卡分组长度、密钥长度均为 128 比特
SM9标识加密算法(IBE)端对端离线安全通讯加密强度等同于 3072 位密钥的 RSA 加密算法
ZUC对称(序列)加密算法移动通信 4G 网络流密码

有关国产加密算法 K 哥前期文章有介绍:《爬虫逆向基础,认识 SM1-SM9、ZUC 国密算法》:https://mp.weixin.qq.com/s?__biz=Mzg5NzY2MzA5MQ==&mid=2247486696&idx=1&sn=0f617c5e21a6617c33c624129e830267&scene=21#wechat_redirect

在 SM 的 JavaScript 代码中一般会存在以下关键字,可以通过搜索关键字定位:

  • SM2、SM3、SM4
  • FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
  • FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
  • 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
  • abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
  • getPublicKeyFromPrivateKey、doEncrypt、doDecrypt、doSignature

国产加密算法中,SM2、SM3、SM4 三种加密算法是比较常见的,在爬取部分网站时,也可能会遇到这些算法,所以作为爬虫工程师是有必要了解一下这些算法的,如下图所示某网站就使用了 SM2 和 SM4 加密算法:

SM1:分组加密

SM1 为分组加密算法,对称加密,分组长度和密钥长度都为 128 位,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。算法安全保密强度及相关软硬件实现性能与 AES 相当,该算法不公开,仅以 IP 核的形式存在于芯片中,调用该算法时,需要通过加密芯片的接口进行调用,采用该算法已经研制了系列芯片、智能 IC 卡、智能密码钥匙、加密卡、加密机等安全产品,广泛应用于电子政务、电子商务及国民经济的各个应用领域(包括国家政务通、警务通等重要领域),一般了解的人比较少,爬虫工程师也不会遇到这种加密算法。

SM2:椭圆曲线公钥加密

SM2 为椭圆曲线(ECC)公钥加密算法,非对称加密,SM2 算法和 RSA 算法都是公钥加密算法,SM2 算法是一种更先进安全的算法,在我们国家商用密码体系中被用来替换 RSA 算法,在不少官方网站会见到此类加密算法。我国学者对椭圆曲线密码的研究从 20 世纪 80 年代开始,目前已取得不少成果,SM2 椭圆曲线公钥密码算法比 RSA 算法有以下优势:

SM2RSA
安全性256 位 SM2 强度已超过 RSA-2048一般
算法结构基本椭圆曲线(ECC)基于特殊的可逆模幂运算
计算复杂度完全指数级亚指数级
存储空间(密钥长度)192-256 bit2048-4096 bit
秘钥生成速度较 RSA 算法快百倍以上
解密加密速度较快一般

SM3:杂凑算法

SM3 为密码杂凑算法,采用密码散列(hash)函数标准,用于替代 MD5/SHA-1/SHA-2 等国际算法,是在 SHA-256 基础上改进实现的一种算法,消息分组长度为 512 位,摘要值长度为 256 位,其中使用了异或、模、模加、移位、与、或、非运算,由填充、迭代过程、消息扩展和压缩函数所构成。在商用密码体系中,SM3 主要用于数字签名及验证、消息认证码生成及验证、随机数生成等。据国家密码管理局表示,其安全性及效率要高于 MD5 算法和 SHA-1 算法,与 SHA-256 相当。

SM4:分组加密算法

SM4 为无线局域网标准的分组加密算法,对称加密,用于替代 DES/AES 等国际算法,SM4 算法与 AES 算法具有相同的密钥长度和分组长度,均为 128 位,故对消息进行加解密时,若消息长度过长,需要进行分组,要消息长度不足,则要进行填充。加密算法与密钥扩展算法都采用 32 轮非线性迭代结构,解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。

SM4DESAES
计算轮数3216(3DES 为 16*3)10/12/14
密码部件S 盒、非线性变换、线性变换、合成变换标准算术和逻辑运算、先替换后置换,不含线性变换S 盒、行移位变换、列混合变换、圈密钥加变换(AddRoundKey)

SM7:分组加密

SM7 为分组加密算法,对称加密,该算法不公开,应用包括身份识别类应用(非接触式 IC 卡、门禁卡、工作证、参赛证等),票务类应用(大型赛事门票、展会门票等),支付与通卡类应用(积分消费卡、校园一卡通、企业一卡通等)。爬虫工程师基本上不会遇到此类算法。

SM9:标识加密

SM9 为标识加密算法(Identity-Based Cryptography),非对称加密,标识加密将用户的标识(如微信号、邮件地址、手机号码、QQ 号等)作为公钥,省略了交换数字证书和公钥过程,使得安全系统变得易于部署和管理,适用于互联网应用的各种新兴应用的安全保障,如基于云技术的密码服务、电子邮件安全、智能终端保护、物联网安全、云存储安全等等。这些安全应用可采用手机号码或邮件地址作为公钥,实现数据加密、身份认证、通话加密、通道加密等。在商用密码体系中,SM9 主要用于用户的身份认证,据新华网公开报道,SM9 的加密强度等同于 3072 位密钥的 RSA 加密算法。

ZUC:祖冲之算法

ZUC 为流密码算法,对称加密,该机密性算法可适用于 3GPP LTE 通信中的加密和解密,该算法包括祖冲之算法(ZUC)、机密性算法(128-EEA3)和完整性算法(128-EIA3)三个部分。已经被国际组织 3GPP 推荐为 4G 无线通信的第三套国际加密和完整性标准的候选算法。

国密 Python 实现

在 Python 里面并没有比较官方的库来实现国密算法,这里仅列出了其中两个较为完善的第三方库,需要注意的是,SM1 和 SM7 算法不公开,目前大多库仅实现了 SM2、SM3、SM4 三种密算法。

  • snowland-smx-python
  • gmssl,使pip install gmssl 安装的 gmssl 不支持 SM9 算法。
  • gmssl-python,是 gmssl 的改进版,新增支持了 SM9 算法。gmssl-python 并未发布 pypi,若要使用 SM9 算法,可下载 gmssl-python 源码手动安装。

以 gmssl 的 SM2 算法为例,实现如下(其他算法和详细用法可参考其官方文档):

SM2 加密(encrypt)和解密(decrypt):

from gmssl import sm2


# 16 进制的公钥和私钥
private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

# 待加密数据和加密后数据为 bytes 类型
data = b"this is the data to be encrypted"
enc_data = sm2_crypt.encrypt(data)
dec_data = sm2_crypt.decrypt(enc_data)

print('enc_data: ', enc_data.hex())
print('dec_data: ', dec_data)

# enc_data: 3cb96dd2e0b6c24df8e22a5da3951d061a6ee6ce99f46a446426feca83e501073288b1553ca8d91fad79054e26696a27c982492466dafb5ed06a573fb09947f2aed8dfae243b095ab88115c584bb6f0814efe2f338a00de42b244c99698e81c7913c1d82b7609557677a36681dd10b646229350ad0261b51ca5ed6030d660947

# dec_data: b'this is the data to be encrypted'

SM2 签名(sign)和校验(verify):

from gmssl import sm2, func


# 16 进制的公钥和私钥
private_key = '00B9AB0B828FF68872F21A837FC303668428DEA11DCD1B24429D0C99E24EED83D5'
public_key = 'B9C9A6E04E9C91F7BA880429273747D7EF5DDEB0BB2FF6317EB00BEF331A83081A6994B8993F3F5D6EADDDB81872266C87C018FB4162F5AF347B483E24620207'
sm2_crypt = sm2.CryptSM2(public_key=public_key, private_key=private_key)

# 待签名数据为 bytes 类型
data = b"this is the data to be signed"
random_hex_str = func.random_hex(sm2_crypt.para_len)

# 16 进制
sign = sm2_crypt.sign(data, random_hex_str)
verify = sm2_crypt.verify(sign, data)

print('sign: ', sign)
print('verify: ', verify)

# sign: 45cfe5306b1a87cf5d0034ef6712babdd1d98547e75bcf89a17f3bcb617150a3f111ab05597601bab8c41e2b980754b74ebe9a169a59db37d549569910ae273a

# verify: True

国密 JavaScript 实现

在 JavaScript 中已有比较成熟的实现库,这里推荐 sm-crypto[4],目前支持 SM2、SM3 和 SM4,需要注意的是,SM2 非对称加密的结果由 C1、C2、C3 三部分组成,其中 C1 是生成随机数的计算出的椭圆曲线点,C2 是密文数据,C3 是 SM3 的摘要值,最开始的国密标准的结果是按 C1C2C3 顺序的,新标准的是按 C1C3C2 顺序存放的,sm-crypto 支持设置 cipherMode,也就是 C1C2C3 的排列顺序。

以 SM2 算法为例,实现如下(其他算法和详细用法可参考其官方文档):

SM2 加密(encrypt)和解密(decrypt):

const sm2 = require('sm-crypto').sm2

// 1 - C1C3C2,0 - C1C2C3,默认为1
const cipherMode = 1

// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey  // 公钥
let privateKey = keypair.privateKey // 私钥

let msgString = "this is the data to be encrypted"
let encryptData = sm2.doEncrypt(msgString, publicKey, cipherMode)  // 加密结果
let decryptData = sm2.doDecrypt(encryptData, privateKey, cipherMode) // 解密结果

console.log("encryptData: ", encryptData)
console.log("decryptData: ", decryptData)

// encryptData: ddf261103fae06d0efe20ea0fe0d82bcc170e8efd8eeae24e9559b3835993f0ed2acb8ba6782fc21941ee74ca453d77664a5cb7dbb91517e6a3b0c27db7ce587ae7af54f8df48d7fa822b7062e2af66c112aa57de94d12ba28e5ba96bf4439d299b41da4a5282d054696adc64156d248049d1eb1d0af28d76b542fe8a95d427e

// decryptData: this is the data to be encrypted

SM2 签名(sign)和校验(verify):

const sm2 = require('sm-crypto').sm2

// 获取密钥对
let keypair = sm2.generateKeyPairHex()
let publicKey = keypair.publicKey  // 公钥
let privateKey = keypair.privateKey // 私钥

// 纯签名 + 生成椭圆曲线点
let msgString = "this is the data to be signed"
let sigValueHex = sm2.doSignature(msgString, privateKey)          // 签名
let verifyResult = sm2.doVerifySignature(msgString, sigValueHex, publicKey) // 验签结果

console.log("sigValueHex: ", sigValueHex)
console.log("verifyResult: ", verifyResult)

// sigValueHex: 924cbb9f2b5adb554ef77129ff1e3a00b2da42017ad3ec2f806d824a77646987ba8c8c4fb94576c38bc11ae69cc98ebbb40b5d47715171ec7dcea913dfc6ccc1

// verifyResult: true

4、混淆系列

代码 混淆 加密:

        asp混淆加密
        php混淆加密
        css/js混淆加密
        VBScript.Encode混淆加密
        ppencode
        rrencode
        jjencode/aaencode
        JSfuck
        jother
        brainfuck编程语言

Obfuscator ( ob 混淆 )

JavaScript Obfuscator Tool:https://www.obfuscator.io/

Obfuscator 就是混淆的意思,简称 OB 混淆,实战可参考 K 哥以前的文章:https://mp.weixin.qq.com/s?__biz=Mzg5NzY2MzA5MQ==&mid=2247486904&idx=1&sn=0c29305d38dcd0fd420eba4aae29dad7&scene=21#wechat_redirect,OB 混淆具有以下特征:

  • 一般由一个大数组或者含有大数组的函数、一个自执行函数、解密函数和加密后的函数四部分组成;
  • 函数名和变量名通常以_0x或者0x开头,后接 1~6 位数字或字母组合;
  • 自执行函数,进行移位操作,有明显的 push、shift 关键字;

一段正常的代码如下:

function hi() {
  console.log("Hello World!");
}
hi();

经过 OB 混淆后的结果:

function _0x5d88() {
    var _0x2e65c9 = ['260126kwArqn', '1056790kjWFJA', '8084055cSZLCK', 'Hello\x20World!', '8648724OKVBQv', '1UOvzJp', '8583072WmxiZz', '117gjlluh', '1839xhWSpo', '1122970wdDhxz', '1948oallQE'];
    _0x5d88 = function () {
        return _0x2e65c9;
    };
    return _0x5d88();
}

(function (_0x54d2cd, _0x3b7cbc) {
    var _0x420fa5 = _0x31f3, _0x3c7d60 = _0x54d2cd();
    while (!![]) {
        try {
            var _0x5559da = parseInt(_0x420fa5(0xa0)) / 0x1 * (parseInt(_0x420fa5(0x9b)) / 0x2) + parseInt(_0x420fa5(0x98)) / 0x3 * (parseInt(_0x420fa5(0x9a)) / 0x4) + parseInt(_0x420fa5(0x9d)) / 0x5 + -parseInt(_0x420fa5(0x9f)) / 0x6 + -parseInt(_0x420fa5(0x9c)) / 0x7 + -parseInt(_0x420fa5(0xa1)) / 0x8 + parseInt(_0x420fa5(0xa2)) / 0x9 * (parseInt(_0x420fa5(0x99)) / 0xa);
            if (_0x5559da === _0x3b7cbc) break; else _0x3c7d60['push'](_0x3c7d60['shift']());
        } catch (_0x295d54) {
            _0x3c7d60['push'](_0x3c7d60['shift']());
        }
    }
}(_0x5d88, 0xcd116));

function hi() {
    var _0x4a461f = _0x31f3;
    console['log'](_0x4a461f(0x9e));
}

function _0x31f3(_0x209d25, _0x2c617d) {
    var _0x5d88da = _0x5d88();
    return _0x31f3 = function (_0x31f3e8, _0xc38c45) {
        _0x31f3e8 = _0x31f3e8 - 0x98;
        var _0x47d894 = _0x5d88da[_0x31f3e8];
        return _0x47d894;
    }, _0x31f3(_0x209d25, _0x2c617d);
}

hi();

JJEncode

JJEncode、AAEncode、JSFuck 都是同一个作者,实战可参考 K 哥以前的文章:【JS 逆向百例】网洛者反爬练习平台第二题:JJEncode 加密,JJEncode 具有以下特征:

  • 大量 $、_ 符号、大量重复的自定义变量
  • 仅由 18 个符号组成:[]()!+,\"$.:;_{}~=

正常的一段 JS 代码:

alert("Hello, JavaScript" )

经过 JJEncode 混淆(自定义变量名为 $)之后的代码:

$=~[];$={___:++$,$$$$:(![]+"")[$],__$:++$,$_$_:(![]+"")[$],_$_:++$,$_$$:({}+"")[$],$$_$:($[$]+"")[$],_$$:++$,$$$_:(!""+"")[$],$__:++$,$_$:++$,$$__:({}+"")[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+"")[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+"")[$.__$])+((!$)+"")[$._$$]+($.__=$.$_[$.$$_])+($.$=(!""+"")[$.__$])+($._=(!""+"")[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!""+"")[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+"\""+$.$_$_+(![]+"")[$._$_]+$.$$$_+"\\"+$.__$+$.$$_+$._$_+$.__+"(\\\"\\"+$.__$+$.__$+$.___+$.$$$_+(![]+"")[$._$_]+(![]+"")[$._$_]+$._$+",\\"+$.$__+$.___+"\\"+$.__$+$.__$+$._$_+$.$_$_+"\\"+$.__$+$.$$_+$.$$_+$.$_$_+"\\"+$.__$+$._$_+$._$$+$.$$__+"\\"+$.__$+$.$$_+$._$_+"\\"+$.__$+$.$_$+$.__$+"\\"+$.__$+$.$$_+$.___+$.__+"\\\"\\"+$.$__+$.___+")"+"\"")())();

AAEncode

aaencode:使用常见的网络表情符号对js代码进编码。
解密:可以直接在控制台进行解码,或者在以下的链接网站进行解码

JJEncode、AAEncode、JSFuck 都是同一个作者,实战可参考 K 哥以前的文章:【JS 逆向百例】网洛者反爬练习平台第三题:AAEncode 加密,AAEncode 具有以下特征:

  • 仅由日式表情符号组成

正常的一段 JS 代码:alert("Hello, JavaScript" )

经过 AAEncode 混淆之后的代码:

JSFuck

JJEncode、AAEncode、JSFuck 都是同一个作者,实战可参考K哥以前的文章:【JS 逆向百例】网洛者反爬练习平台第四题:JSFuck 加密,JSFuck 具有以下特征:

  • 仅由 6 个符号组成:[]()!+

正常的一段 JS 代码:alert(1)

经过 JSFuck 混淆之后的代码类似于:

解密方式1:点击 run this 可进行解密,
解密方式2:将加密密文放在浏览器的console控制台上进行解密

Jother

Jother是一种运用于javascript语言中利用少量字符构造精简的匿名函数方法对于字符串进行的编
码方式。其中8个少量字符包括: ! + ( ) [ ] { } 。Jother 混淆和 JSFuck 有点儿类似,唯一的区别就是密文比 JSFuck 多了 {},其解密方式和 JSFuck 是一样的,Jother 混淆具有以下特征:

  • 仅由 8 个符号组成:[]()!+{}

正常的一段代码:

function anonymous() {
    console.log('anonymous');
}
anonymous();

在线工具:https://vulsee.com/tools/jother/index.htm

经过 Jother 混淆之后的代码类似于:

解密方式:jother解码工具/利用浏览器开发者工具进行解密

Brainfuck

Brainfuck 实际上是一种极小化的计算机语言,又称为 BF 语言,该语言以其极简主义着称,仅包含八个简单的命令、一个数据指针和一个指令指针,这种语言在爬虫领域也可以是一种反爬手段,可以视为一种混淆方式,虽然不常见,这里给一个在线体验的网址:https://copy.sh/brainfuck/text.html ,感兴趣的可以深入研究一下,Brainfuck 具有以下特征:

  • 仅由 <>+-.[] 组成,大量的 +- 符号。

正常的一段代码:alert("Hello, Brainfuck")

在线工具:https://ctf.bugku.com/tool/brainfuck

经过 Brainfuck 混淆之后的代码类似于:

Ook!

Ook! 和 Brainfuck 的原理都是类似的,只不过符号有差异,同样的,这种语言在爬虫领域也可以是一种反爬手段,可以视为一种混淆方式,虽然不常见,在线体验的网址:https://www.splitbrain.org/services/ook ,Ook! 具有以下特征:

  • 完整 Ook!:仅由 3 种符号组成 Ook.Ook?Ook!
  • Short Ook!:仅由 3 种符号组成 .!?

正常的一段代码:alert("Hello, Ook!"),经过 Ook! 混淆之后的代码类似于:

Trivial brainfuck substitution

Trivial brainfuck substitution 不是一种单一的编程语言,而是一大类编程语言,成员超过 20 个,前面提到的 Brainfuck、Ook! 都是其中的一员,在爬虫领域中,说实话这种稀奇古怪的混淆其实并不常见,但是在一些 CTF 中有可能会出现,作为爬虫工程师也可以了解了解,具体可以参考:https://esolangs.org/wiki/Trivial_brainfuck_substitution

5、其他 加密、解密

换位加密:
        栅栏密码(Rail-fence Cipher)
        曲路密码(Curve Cipher)
        列移位密码(Columnar Transposition Cipher)

替换加密:
        埃特巴什码(Atbash Cipher)
        凯撒密码(Caesar Cipher)
		摩斯密码
        ROT5/13/18/47
        简单换位密码(Simple Substitution Cipher)
        希尔密码(Hill Cipher)
        猪圈密码(Pigpen Cipher)
        波利比奥斯方阵密码(Polybius Square Cipher)
        夏多密码(曲折加密)
        普莱菲尔密码(Playfair Cipher)
        维吉尼亚密码(Vigenère Cipher)
        自动密钥密码(Autokey Cipher)
        博福特密码(Beaufort Cipher)
        滚动密钥密码(Running Key Cipher)
        Porta密码(Porta Cipher)
        同音替换密码(Homophonic Substitution Cipher)
        仿射密码(Affine Cipher)
        培根密码(Baconian Cipher)
        ADFGX和ADFGVX密码(ADFG/VX Cipher)
        双密码(Bifid Cipher)
        三分密码(Trifid Cipher)
        四方密码(Four-Square Cipher)
        棋盘密码(Checkerboard Cipher)
        跨棋盘密码(Straddle Checkerboard Cipher)
        分组摩尔斯替换密码(Fractionated Morse Cipher)
        Bazeries密码(Bazeries Cipher)
        Digrafid密码(Digrafid Cipher)
        格朗普雷密码(Grandpré Cipher)
        比尔密码(Beale ciphers)
        键盘密码(Keyboard Cipher)

其他有趣的机械密码:
        恩尼格玛密码

恺撒密码

恺撒密码(Caesar cipher)又称为恺撒加密、恺撒变换、变换加密,它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文。例如,当偏移量是 3 的时候,所有的字母 A 将被替换成 D,B 变成 E,以此类推。这个加密方法是以罗马共和时期恺撒的名字命名的,当年恺撒曾用此方法与其将军们进行联系。

根据偏移量的不同,还存在若干特定的恺撒密码名称:偏移量为10:Avocat(A→K);偏移量为13:ROT13;偏移量为-5:Cassis (K 6);偏移量为-6:Cassette (K 7)

示例(偏移量 3):

•明文字母表:ABCDEFGHIJKLMNOPQRSTUVWXYZ•密文字母表:DEFGHIJKLMNOPQRSTUVWXYZABC

栅栏密码

栅栏密码就是把要加密的明文分成 N 个一组,然后把每组的第 1 个字连起来,形成一段无规律的话。栅栏密码本身有一个潜规则,就是组成栅栏的字母一般不会太多,一般不超过 30 个。

示例:明文为 THE LONGEST DAY MUST HAVE AN END。加密时,把将要传递的信息中的字母交替排成上下两行:

T E O G S D Y U T A E N N

H L N E T A M S H V A E D

将下面一行字母排在上面一行的后边组合成密文:

TEOGSDYUTAENN HLNETAMSHVAED

栅栏密码还有一种变种,称为栅栏密码 W 型,它会先把明文类似 W 形状进行排列,然后再按栏目顺序 1-N,取每一栏的所有字符值,组成加密后密文,比如字符串 123456789,采用栏目数为 3 时,明文将采用如下排列: 

1—5—9

-2-4-6-8-

–3—7–

取每一栏所有字符串,组成加密后密文:159246837

猪圈密码

猪圈密码也称为朱高密码、共济会暗号、共济会密码或共济会员密码,是一种以格子为基础的简单替代式密码。只能对字母加解密并且符号无法复制,粘贴后会直接显示明文,即使使用符号,也不会影响密码分析,亦可用在其它替代式的方法。曾经是美国内战时盟军使用的密码,目前仅在密码教学、各种竞赛中使用。

摩斯密码

摩斯密码(Morse code),又称为摩尔斯电码、摩斯电码,是一种时通时断的信号代码,这种信号代码通过不同的排列顺序来表达不同的英文字母、数字和标点符号等。

26个字母的摩斯密码表

字符摩斯码字符摩斯码字符摩斯码
A.━B━ ...C━ .━ .
D━ ..EF..━ .
G━ ━ .H....I..
J.━ ━ ━K━ .━L.━ ..
M━ ━N━ .O━ ━ ━
P.━ ━ .Q━ ━ .━R.━ .
S...TU..━
V...━W.━ ━X━ ..━
Y━ .━ ━Z━ ━ ..

10个数字的摩斯密码表

字符摩斯码字符摩斯码字符摩斯码
0━ ━ ━ ━ ━1.━ ━ ━ ━2..━ ━ ━
3...━ ━4....━5.....
6━ ....7━ ━ ...8━ ━ ━ ..
9━ ━ ━ ━ .

标点符号的摩斯密码表

字符摩斯码字符摩斯码字符摩斯码
..━ .━ .━:━ ━ ━ ...,━ ━ ..━ ━
;━ .━ .━ .?..━ ━ ..=━ ...━
'.━ ━ ━ ━ ./━ ..━ .!━ .━ .━ ━
━ ....━_..━ ━ .━".━ ..━ .
(━ .━ ━ .)━ .━ ━ .━$...━ ..━
&. ...@.━ ━ .━ .

培根密码

培根密码,又名倍康尼密码(Bacon's cipher)是由法兰西斯·培根发明的一种隐写术,它是一种本质上用二进制数设计的,没有用通常的0和1来表示,而是采用a和b,看到一串的a和b,并且五个一组,那么就是培根加密了。

第一种方式:

字符培根密码字符培根密码字符培根密码字符培根密码
AaaaaaHaabbbOabbbaVbabab
BaaaabIabaaaPabbbbWbabba
CaaabaJabaabQbaaaaXbabbb
DaaabbKababaRbaaabYbbaaa
EaabaaLababbSbaabaZbbaab
FaababMabbaaTbaabb
GaabbaNabbabUbabaa

第二种方式:

字符培根密码字符培根密码字符培根密码字符培根密码
aAAAAAhAABBBpABBBAxBABAB
bAAAABi-jABAAAqABBBByBABBA
cAAABAkABAABrBAAAAzBABBB
dAAABBlABABAsBAAAB
eAABAAmABABBtBAABA
fAABABnABBAAu-vBAABB
gAABBAoABBABwBABAA

示例:

•明文:kuaidaili•密文:ABABABABAAAAAAAABAAAAAABBAAAAAABAAAABABBABAAA

维吉尼亚密码

维吉尼亚密码是在凯撒密码基础上产生的一种加密方法,它将凯撒密码的全部25种位移排序为一张表,与原字母序列共同组成26行及26列的字母表。另外,维吉尼亚密码必须有一个密钥,这个密钥由字母组成,最少一个,最多可与明文字母数量相等。维吉尼亚密码表如下:

示例:

  • 明文:I've got it.
  • 密钥:ok
  • 密文:W'fs qcd wd.

首先,密钥长度需要与明文长度相同,如果少于明文长度,则重复拼接直到相同。示例的明文长度为8个字母(非字母均被忽略),密钥会被程序补全为 okokokok,然后根据维吉尼亚密码表进行加密:明文第一个字母是 I,密钥第一个字母是 o,在表格中找到 I 列与 o 行相交点,字母 W 就是密文第一个字母,同理,v 列与 k 行交点字母是 Fe 列与 o 行交点字母是 S,以此类推。注意:维吉尼亚密码只对字母进行加密,不区分大小写,若文本中出现非字母字符会原样保留,如果输入多行文本,每行是单独加密的。

与佛论禅

字符串转换后,是一些佛语,在线体验:https://keyfc.net/bbs/tools/tudoucode.aspx

示例:

  • 明文:K哥爬虫
  • 密文:佛曰:哆室梵阿俱顛哆礙孕奢大皤帝罰藝哆伽密謹爍舍呐栗皤夷密

当铺密码

当铺密码在 CTF 比赛题目中出现过。该加密算法是根据当前汉字有多少笔画出头,对应的明文就是数字几。

示例:

  • 明文:王夫 井工 夫口 由中人 井中 夫夫 由中大
  • 密文:67 84 70 123 82 77 125

6、爬虫常见 加密 / 解密

总结了在爬虫中常见的各种加密算法、编码算法的原理、在 JavaScript 中和 Python 中的基本实现方法,遇到 JS 加密的时候可以快速还原加密过程,有的网站在加密的过程中可能还经过了其他处理,但是大致的方法是一样的。

常见 加密:

  • 对称加密(加密解密密钥相同):DES、3DES、AES、RC4、Rabbit
  • 非对称加密(区分公钥和私钥):RSA、DSA、ECC
  • 消息摘要算法/签名算法:MD5、SHA、HMAC、PBKDF2

常见 编码

  • Base64

JavaScript 加密解密 模块

JS逆向:由 words 、sigBytes 引发的一系列思考与实践:https://blog.csdn.net/studypy1024/article/details/139964629

在做JS逆向时,你是否经常看到 words 和 sigBytes 这两个属性,比如:

{
    words: [2003644449, 1081552243, 594308145, 1718382376], 
    sigBytes: 16
}

如果发现了那么基本可以确定,网站是使用crypto-js库中某一个加密算法实现加密的。在 crypto-js 中,words 和 sigBytes 是 CryptoJS.lib.WordArray 对象的两个重要属性。

  • words 数组是 WordArray 对象的主体,它用于存储加密数据。在 crypto-js 中,一个 word 是由32位(或者说是4个字节)组成的,因此每个元素在 words 数组中实际上可以表示4个字节的数据。相关知识:在计算机和信息技术中,32位(4字节)是一个常用的数据大小单位,用来表示数据的存储或处理容量。具体来讲:
    1位(bit):是计算机数据的最基本单位,代表一个二进制数字,即0或1。
    1字节(Byte):通常由8位组成,是基本的数据存储单位。1字节可以表示0到255(或者在二进制中是00000000到11111111)之间的一个数值。
    32位:则相当于4字节(4 Bytes),因为1字节是8位,所以4字节就是4 * 8 = 32位。所以一个32位的整数可以存储从-2,147,483,648到2,147,483,647的整数值(如果是有符号整数),或者从0到4,294,967,295(如果是无符号整数)。这种表示范围是因为32位可以表示2^32(即4,294,967,296)个不同的值。

如何生成 WordArray 对象

  • 方法 1:通过 CryptoJS.enc.Utf8.parse 生成。如果已知 utf8 编码的明文,则可以使用 CryptoJS.enc.Utf8.parse 方法生成 WordArray 对象。
    const CryptoJS = require("crypto-js");
    
    // 生成 WordArray 对象
    const keyWordArray = CryptoJS.enc.Utf8.parse("wm0!@w-s#ll1flo(");
    console.log(keyWordArray);
    
  • 方法 2:通过 CryptoJS.lib.WordArray.create 生成。
    const CryptoJS = require("crypto-js");
    
    // 32位整数数组(words数组)
    const words = [2003644449, 1081552243, 594308145, 1718382376]; 
    const sigBytes = 16;
    
    // 使用 CryptoJS 转换字节数组到 WordArray
    const keyWordArray = CryptoJS.lib.WordArray.create(words, sigBytes);
    
    console.log(keyWordArray);
    

WordArray 转化为 UTF-8 字符串

const CryptoJS = require("crypto-js");
const wordArrayBytes = {
    words: [ 2003644449, 1081552243, 594308145, 1718382376 ],
    sigBytes: 16
};

// 将 WordArray 转换为 UTF-8 字符串
const key = CryptoJS.enc.Utf8.stringify(wordArrayBytes);
console.log(key);

// wm0!@w-s#ll1flo(

WordArray 转化为十六进制Hex

const CryptoJS = require("crypto-js");
const wordArrayBytes = {
    words: [ 2003644449, 1081552243, 594308145, 1718382376 ],
    sigBytes: 16
};

// 将 WordArray 转换为十六进制Hex
const key = CryptoJS.enc.Hex.stringify(wordArrayBytes);
console.log(key);

// 776d302140772d73236c6c31666c6f28

如何判断一个字符串是否为有效的十六进制字符串,可以检查以下几点:

  • 字符串长度:十六进制字符串的长度应该是偶数。
  • 字符范围:十六进制字符串只能包含 0-9 以及 a-f 和 A-F 这些字符。
// 判断一个字符串是否为十六进制字符串的函数
function isHexString(str) {
    // 检查长度是否为偶数
    if (str.length % 2 !== 0) {
        return false;
    }
    // 使用正则表达式检查字符串是否只包含合法的十六进制字符(0-9, a-f, A-f)
    const hexRegex = /^[0-9a-fA-F]+$/;
    return hexRegex.test(str);
}

// 示例测试
const testHexString1 = "776d302140772d73236c6c31666c6f28";
const testHexString2 = "1a2b3g4d5e"; // 包含非法字符 'g'
const testHexString3 = "123";        // 长度为奇数

console.log(isHexString(testHexString1)); // 输出:true
console.log(isHexString(testHexString2)); // 输出:false
console.log(isHexString(testHexString3)); // 输出:false

相互转换:WordArray 整数数组、Hex十六进制、ByteArray字节数组、UTF-8字符串

// 判断一个数组是否为字节数组(每个元素为0到255之间的整数)
function isByteArray(arr) {
    // 首先检查是否为数组
    if (!Array.isArray(arr)) {
        return false;
    }
    // 检查每个元素是否为0到255之间的整数
    for (let i = 0; i < arr.length; i++) {
        if (typeof arr[i] !== 'number' || arr[i] < 0 || arr[i] > 255 || !Number.isInteger(arr[i])) {
            return false;
        }
    }
    return true;
}

// 将字节数组转换为十六进制字符串的函数
function bytesToHex(bytes) {
    if (!isByteArray(bytes)) {
        throw new Error('输入必须是字节数组');
    }
    return bytes.map(byte => byte.toString(16).padStart(2, '0')).join('');
}

// 将十六进制字符串转换为字节数组的函数
function hexToBytes(hex) {
    let bytes = [];
    for (let c = 0; c < hex.length; c += 2) {
        bytes.push(parseInt(hex.substr(c, 2), 16));
    }
    return bytes;
}

// 将32位整数数组(words数组)转换为字节数组的函数
function wordsToBytes(words) {
    let bytes = [];
    for (let i = 0; i < words.length; i++) {
        let word = words[i];
        bytes.push((word >> 24) & 0xFF);
        bytes.push((word >> 16) & 0xFF);
        bytes.push((word >> 8) & 0xFF);
        bytes.push(word & 0xFF);
    }
    return bytes;
}

// 将字节数组转换为UTF-8字符串的函数
function bytesToStringUTF8(bytes) {
    if (!isByteArray(bytes)) {
        throw new Error('输入必须是字节数组');
    }
    return new TextDecoder('utf-8').decode(new Uint8Array(bytes));
}

let key = { words: [2003644449, 1081552243, 594308145, 1718382376], sigBytes: 16 };

// 将key的words数组转换为字节数组
let keyBytes = wordsToBytes(key.words);

// 将字节数组转换为十六进制字符串,测试使用
let keyHex = bytesToHex(keyBytes);

// 打印key和iv的十六进制字符串
console.log("十六进制Hex:", keyHex);

// 将十六进制字符串还原为字节数组,以确保转换过程的无损失和准确性,测试使用
let keyBytesFromHex = hexToBytes(keyHex);

// 将字节数组转换为UTF-8字符串,得到最终的字符串表示
let keyString = bytesToStringUTF8(keyBytes);

// 打印key和iv的UTF-8字符串表示
console.log("字符串UTF-8:", keyString);

WordArray,可以理解成CryptoJS中定义的 新的 数据类型,叫 "单词数组"。

// 初始化
var wordArray = CryptoJS.lib.WordArray.create();//创建一个空的 WordArray对象

// WordArray 对象 —>16进制字符串
var string = wordArray.toString();//默认CryptoJS.enc.Hex,即16进制字符串
var string = wordArray.toString(CryptoJS.enc.Utf8);//utf-8字符串

// 16进制字符串 —>WordArray对象
var wordArray = CryptoJS.enc.Hex.parse(hexString);

// WordArray对象—>utf8字符串
var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
//等价于 wordArray.toString(CryptoJS.enc.Utf8);

// utf8字符串—>WordArray对象
 var wordArray = CryptoJS.enc.Utf8.parse(utf8String);

// WordArray对象—>Base64字符串
var base64String = CryptoJS.enc.Base64.stringify(wordArray);

// Base64字符串—>WordArray对象
var wordArray = CryptoJS.enc.Base64.parse(base64String);

用 Uint8Array 生成字节数组

Uint8Array 的特点包括以下几点:

  • 无符号整数:Uint8Array 存储的是无符号的 8 位整数(0 到 255),因此每个元素的取值范围是从 0 到 255。这意味着每个元素可以表示一个字节的数据,适用于处理字节数据和二进制数据。
  • 定长数组:Uint8Array 是一种定长数组,它的长度在创建时就已经确定,并且不能动态改变。这有助于提高操作效率和性能,尤其在处理大量数据时非常有用。
// 用 Uint8Array 生成字节数组
const uint8Array = Uint8Array.from([
    119, 109, 48, 33, 64,
    119, 45, 115, 35, 108,
    108, 49, 102, 108, 111,
    40
]);

// 使用 TextDecoder 将 bytes 转换为字符串
const textDecoder = new TextDecoder();
const decodedString = textDecoder.decode(uint8Array);

console.log(decodedString);

// wm0!@w-s#ll1flo(

为什么 AES、DES等加密时,要先转化 WrodArray

// AES 加密
function encrypt(word) {
     // 转化为 WordArray
    const iv = CryptoJS.enc.Utf8.parse("0102030405060708");
     // 转化为 WordArray
    const key = CryptoJS.enc.Utf8.parse('wm0!@w-s#ll1flo(');
    const encrypted = CryptoJS.AES.encrypt(word, key, {
        iv,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7
    })
    return encrypted.toString();
}

转化为32位二进制数据再进行加密是出于以下几个主要原因:

  • 加密算法的要求:几乎所有的现代加密算法,包括对称加密(如AES)和非对称加密(如RSA),在底层操作时都是以二进制数据形式工作的。这是因为加密算法在设计时需要操作位(bit)和字节(byte),而不是高级语言中的文本或数字等类型。因此,将数据转换为二进制形式是执行这些算法的必要步骤。
  • 安全性增强:通过转换为二进制数据,可以确保在加密过程中原始数据的内容不会因其格式或结构而泄露信息。此外,某些加密方式(如块加密)要求数据以特定大小的块来处理。将数据预先转化为二进制形式,可以更容易地按块对数据进行操作和填充。
  • 性能优化:直接对二进制数据进行操作比对高级数据类型(如字符串或对象)进行操作要有效率得多。加密和解密操作通常需要大量的数据处理工作,特别是在数据量大或需求高性能的应用场景中。预先将数据转换为二进制格式,可以最大限度地减少处理时间和资源消耗。

使用 crypto-js 库进行 AES 加密和解密

const CryptoJS = require('crypto-js');

// 加密
function encrypt(text, key, iv) {
  const encrypted = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv),
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return encrypted.ciphertext.toString(CryptoJS.enc.Base64);
}

// 解密
function decrypt(encryptedText, key, iv) {
  const decrypted = CryptoJS.AES.decrypt(encryptedText, CryptoJS.enc.Utf8.parse(key), {
    iv: CryptoJS.enc.Utf8.parse(iv),
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7
  });
  return decrypted.toString(CryptoJS.enc.Utf8);
}

const key = '2458077842538476';
const iv = CryptoJS.lib.WordArray.random(16);
const originalText = 'Hello, world!';

const encrypted = encrypt(originalText, key, iv.toString(CryptoJS.enc.Utf8));
console.log('Encrypted:', encrypted);

const decrypted = decrypt(encrypted, key, iv.toString(CryptoJS.enc.Utf8));
console.log('Decrypted:', decrypted);

python 实现

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def encrypt(text, key, iv):
    cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    padded_data = pad(text.encode(), AES.block_size)
    encrypted_data = cipher.encrypt(padded_data)
    return base64.b64encode(encrypted_data).decode()

def decrypt(encrypted_text, key, iv):
    cipher = AES.new(key.encode(), AES.MODE_CBC, iv.encode())
    encrypted_bytes = base64.b64decode(encrypted_text)
    decrypted_data = cipher.decrypt(encrypted_bytes)
    unpadded_data = unpad(decrypted_data, AES.block_size)
    return unpadded_data.decode()

key = '2458077842538476'
iv = 'ertdfgslyuqwrpxh'
original_text = 'Hello, world!'

encrypted = encrypt(original_text, key, iv)
print('Encrypted:', encrypted)

decrypted = decrypt(encrypted, key, iv)
print('Decrypted:', decrypted)

Crypto-JS

Crypto-js 是 JavaScrip 前端加密库,支持 MD5、SHA256、base64、RIPEMD-160、HMAC、PBKDF2、AES、DES、3DES(Triple DES)、Rabbit、RC4 等,crypto-js 是一个对称加密的库不支持 RSA、ECC 等非对称加密的方法,Crypto-js 是应用比较广的加密模块,使用命令 npm install crypto-js 安装。

参考资料:

js前端加密库Crypto-js进行MD5/SHA256/BASE64/AES加解密的:https://www.cnblogs.com/hellofangfang/p/18304444

CryptoJS (crypto.js) 为 JavaScript 提供了各种各样的加密算法。

  • crypto-js/core
  • crypto-js/x64-core
  • crypto-js/lib-typedarrays
  • crypto-js/md5
  • crypto-js/sha1
  • crypto-js/sha256
  • crypto-js/sha224
  • crypto-js/sha512
  • crypto-js/sha384
  • crypto-js/sha3
  • crypto-js/ripemd160
  • crypto-js/hmac-md5
  • crypto-js/hmac-sha1
  • crypto-js/hmac-sha256
  • crypto-js/hmac-sha224
  • crypto-js/hmac-sha512
  • crypto-js/hmac-sha384
  • crypto-js/hmac-sha3
  • crypto-js/hmac-ripemd160
  • crypto-js/pbkdf2
  • crypto-js/aes
  • crypto-js/tripledes
  • crypto-js/rc4
  • crypto-js/rabbit
  • crypto-js/rabbit-legacy
  • crypto-js/evpkdf
  • crypto-js/format-openssl
  • crypto-js/format-hex
  • crypto-js/enc-latin1
  • crypto-js/enc-utf8
  • crypto-js/enc-hex
  • crypto-js/enc-utf16
  • crypto-js/enc-base64
  • crypto-js/mode-cfb
  • crypto-js/mode-ctr
  • crypto-js/mode-ctr-gladman
  • crypto-js/mode-ofb
  • crypto-js/mode-ecb
  • crypto-js/pad-pkcs7
  • crypto-js/pad-ansix923
  • crypto-js/pad-iso10126
  • crypto-js/pad-iso97971
  • crypto-js/pad-zeropadding
  • crypto-js/pad-nopadding

封装 axios 拦截器进行加密解密

/**
 *
 * http配置
 *
 */
// 引入axios以及element ui中的loading和message组件
import { aes } from "@/util/encrypt.js";
import { getKey, getAesKey } from "@/config/key.js";
import axios from "axios";
import store from "../store";
import router from "../router/router";
import { Loading, Message } from "element-ui";
import { getSessStore, setSessStore } from "@/util/store";


// 超时时间
if (store.online) axios.defaults.timeout = 20000;
else axios.defaults.timeout = 0;
//跨域请求,允许保存cookie
axios.defaults.withCredentials = true;

// 统一加解密
const Unify = {
  // 统一加密方法
  en(data, key) {
    // 1.aes加密
    let aesStr = aes.en(JSON.stringify(data), key);
    return aesStr;
  },
  // 统一解密
  de(aesStr, key) {
    // 1.aes解密
    let dataStr = aes.de(aesStr, key);
    // 3.转json对象
    let data = JSON.parse(dataStr);
    return data;
  },
};

let loadinginstace;
let cfg, msg;
msg = "服务器君开小差了,请稍后再试";
function ens(data) {
  // debugger
  let src = [...data];
  src = JSON.stringify(src);
  let dataJm = aes.en(src);
  return dataJm;
}
function des(data) {
  // debugger
  let src = [...data];
  let dataJm = aes.de(src);
  dataJm = JSON.parse(dataJm);
  return dataJm;
}
const cancelToken = axios.CancelToken
const source = cancelToken.source()
//HTTPrequest拦截
axios.interceptors.request.use(
  function (config) {
    console.log(config.data, "加密前入参---");
    config.cancelToken = source.token; // 全局添加cancelToken
    loadinginstace = Loading.service({
      fullscreen: true,
    });
    if (store.getters.token) {
      let info = getSessStore("token");
      // console.log("info", info);
      config.headers["Authorization"] = "Bearer " + info; // 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
    }
    const contenttype = config.headers.common["Accept"];
    let types = contenttype.includes("application/json");
    let key = getKey(); // 获取密钥
    config.headers["aes"] = key.code; // 将 aes 的 code 设置到请求头,传给后端解密
    config.headers["name"] = "send";
    if (types) {
      if (config.method == "post" || config.method == "put") {
        if (config.data) {
          config.headers["crypto"] = true;
          config.headers["content-type"] = "application/json";
          let data = {
            body: config.data,
          };
          let dataJm = Unify.en(data, key.key); // 加密 post 请求参数
          config.data = dataJm;
        }
      }
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

//HTTPresponse拦截
axios.interceptors.response.use(
  (response) => {
    loadinginstace.close();
    let res = response.data || {};
    if (response.headers["date"]) {
      store.commit("setServiceTime", response.headers.date);
    }
    if (res.crypto) {
      try {
        let key = getAesKey(response.headers.aes); // 拿到加密后的key
        if (!key) {
          message("获取密钥异常", "error");
          return Promise.reject(res);
        }
        // debugger
        res = Unify.de(res.body, key); // 解密数据
        response.data = res;
      } catch (err) {
        message("系统异常:" + err.message, "error");
        return Promise.reject(err);
      }
    }
    // debugger
    if (res.code === 1) {
      message(res.msg, "error");
      return Promise.reject(res);
    }
    console.log(response, "解密后response");
    return response;
  },
  (error) => {
    console.log("错误信息", error);
    loadinginstace.close();
    const res = error.response || {};
    if (res.status === 478 || res.status === 403 || res.status === 401) {
      let resMsg = res.data.msg ? res.data.msg : res.data.data
      if (res.status === 403) {
        message('服务授权失败,请联系管理添加权限!', "error");
      } else {
        message(resMsg, "error");
      }
      let flg = res.data.msg.includes('当前登录状态已失效')
      if (res.status === 478 && flg) {
        //token失效
        source.cancel('登录信息已过期'); // 取消其他正在进行的请求
        store.dispatch("FedLogOut").then(() => {
          router.push("/login"); ///test
        });
      }
    } else if (res.status === 400) {
      message(res.data.error_description, "error");
    } else if (res.status === 202) {
      //三方未绑定
      this.$router.push({
        path: "/",
      });
    } else if (res.status === 503 || res.status === 504) {
      //服务异常
      message(res.data, "error");
    } else if (
      (res.status === 401 && res.statusText == "Unauthorized") ||
      res.data.error == "invalid_token" ||
      res.data.error == "unauthorized"
    ) {
      //token失效
      store.dispatch("FedLogOut").then(() => {
        router.push("/login"); ///test
      });
    } else {
      message(res.data.message, "error");
    }
    return Promise.reject(error);
  }
);
export function message(text, type) {
  let t = text ? text : "服务或网络异常!"
  Message({
    message: t,
    type: type,
    duration: 30 * 1000,
    center: true,
    showClose: true
  });
}
export default axios;


Node-RSA

Node-RSA 对  RSA 算法提供了支持,使用命令 npm install node-rsa 安装。
参考资料:Node-RSA Github:https://github.com/rzcoder/node-rsa

JSEncrypt

参考资料:JSEncrypt 对  RSA 算法提供了更加全面的支持,

使用命令 npm install jsencrypt 安装。

Python 加密解密 模块

binascii 模块主要用于二进制和ASCII互相转换(Convert between binary and ASCII );

相关内置函数

chr():把一个整形转换成ASCII码表中对应的单个字符
ord():把ASCII码表中的字符转换成对应的整形
hex():把十进制转换成16进制字符
oct():把十进制转换成八进制字符
bin():把十进制整形转换成二进制字符

import binascii

test_byte = b'hello world'
# 利用b2a_hex()返回的字符串长度为原串的两倍,因为转换为十六进制,一个字节用两个字节表示了
b1 = binascii.b2a_hex(test_byte)
print(f'b1 ---> {b1}')
b2 = binascii.hexlify(test_byte)  # 和a2b_hex()功能是一样的,但是推荐用这个函数
print(f'b2 ---> {b2}')

test_str = 'hello world'
for i in test_str:
    print(hex(ord(i))[2:], end=' ')  # 手动转换为二进制十六进制
print()
# 与b2a_hex相反
print(binascii.a2b_hex(b1))
print(binascii.unhexlify(b2))

"""
b1 ---> b'68656c6c6f20776f726c64'
b2 ---> b'68656c6c6f20776f726c64'
68 65 6c 6c 6f 20 77 6f 72 6c 64 
b'hello world'
b'hello world'
"""
import binascii

print(f"1 ---> {'测试'.encode()}")
print(f"2 ---> {binascii.b2a_hex('测试'.encode())}")
print(f"3 ---> {binascii.a2b_hex(b'e6b58be8af95')}")
print(f"4 ---> {binascii.a2b_hex(b'e6b58be8af95').decode()}")

"""
# 注:两位十六进制常常用来显示一个二进制字节。
# 利用`binascii`模块可以将十六进制显示的字节转换成我们在加解密中更常用的显示方式:
"""
"""
1 ---> b'\xe6\xb5\x8b\xe8\xaf\x95'
2 ---> b'e6b58be8af95'
3 ---> b'\xe6\xb5\x8b\xe8\xaf\x95'
4 ---> 测试
"""

计算机使用的存储单位
    位(bit) 计算机使用的最小单位,用来存放二进制数
    字节(Byte) 计算机最常用的基本单位,一个字节有八位 (1Byte = 8 bit)
        KB(K字节)  1K = 1024 Byte
        MB(兆字节) 1M = 1024 K
        GB(吉字节) 1G = 1204 M
        TB(太字节) 1T = 1024 G

hashlib、hmac、pycryptodome

  • 标准库 hashlib 提供了常见的摘要算法,如 MD5,SHA、BLAKE2b、BLAKE2s 等。
  • 标准库 hmac 提供了对 HMAC 算法的支持。
  • Crypto 已经停止更新好多年,不建议安装。
  • Cryptodome 是 Crypto 的替代品,是 python 一个强大的加密算法库,支持几乎所有主流加密算法,包括 MD5、SHA、BLAKE2b、BLAKE2s、HMAC、PBKDF2、AES、DES、3DES(Triple DES)、ECC、RSA、RC4 等。可以实现常见的单向加密、对称加密、非对称加密和流加密算法。安装 PyCryptodome :pip install pycryptodome
    安装后,模块在 Crypto 包下。不要同时安装 pycrypto 和 pycryptodome ,会相互干扰。
  • PyCryptodomex 和 Cryptodome 相等,都属于同一个帐户并指向 same Github repository

    这两个模块代码相同,只是名称不同。安装 PyCryptodomex :pip install pycryptodomex
    安装后,模块在 Cryptodome 包下。pycrypto 和 pycryptodomex 可以共存。

Cryptodome 库:https://pycryptodome.readthedocs.io/en/latest/index.html

源码文件支持的 加密算法

Base64

简介:Base64 是一种用 64 个字符来表示任意二进制数据的方法。

参考资料:

JavaScript 实现

//引用crypto-js加密模块
var CryptoJS = require('crypto-js')

function base64Encode() {
    var srcs = CryptoJS.enc.Utf8.parse(text);
    var encodeData = CryptoJS.enc.Base64.stringify(srcs);
    return encodeData
}

function base64Decode() {
    var srcs = CryptoJS.enc.Base64.parse(encodeData);
    var decodeData = srcs.toString(CryptoJS.enc.Utf8);
    return decodeData
}

var text = "I love Python!"

var encodeData = base64Encode()
var decodeData = base64Decode()

console.log("Base64编码:", encodeData)
console.log("Base64解码:", decodeData)

//Base64编码:SSBsb3ZlIFB5dGhvbiE=
//Base64解码:I love Python!

Python 实现

import base64


def base64_encode(text):
    encode_data = base64.b64encode(text.encode())
    return encode_data


def base64_decode(encode_data):
    decode_data = base64.b64decode(encode_data)
    return decode_data


if __name__ == '__main__':
    temp = 'I love Python!'
    encode_data = base64_encode(text=temp)
    decode_data = base64_decode(encode_data)
    print('Base64 编码:', encode_data)
    print('Base64 解码:', decode_data)

# Base64 编码:b'SSBsb3ZlIFB5dGhvbiE='
# Base64 解码:b'I love Python!'

MD5

简介:全称 MD5 消息摘要算法(英文名称:MD5 Message-Digest Algorithm),又称哈希算法、散列算法,由美国密码学家罗纳德·李维斯特(Ronald Linn Rivest)设计,于 1992 年作为 RFC 1321 被公布,用以取代 MD4 算法。摘要算法是单向加密的,也就是说明文通过摘要算法加密之后,是不能解密的。摘要算法的第二个特点密文是固定长度的,它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。之所以叫摘要算法,它的算法就是提取明文重要的特征。所以,两个不同的明文,使用了摘要算法之后,有可能他们的密文是一样的,不过这个概率非常的低。

参考资料:

RFC 1321:https://datatracker.ietf.org/doc/rfc1321/
MD5 维基百科:https://en.wikipedia.org/wiki/MD5

JavaScript 实现

// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function MD5Test() {
    var text = "I love python!"
    return CryptoJS.MD5(text).toString()
}

console.log(MD5Test())  // 21169ee3acd4a24e1fcb4322cfd9a2b8

Python 实现

import hashlib


def md5_test1():
    md5 = hashlib.new('md5', 'I love python!'.encode('utf-8'))
    print(md5.hexdigest())


def md5_test2():
    md5 = hashlib.md5()
    md5.update('I love '.encode('utf-8'))
    md5.update('python!'.encode('utf-8'))
    print(md5.hexdigest())


if __name__ == '__main__':
    md5_test1()  # 21169ee3acd4a24e1fcb4322cfd9a2b8
    md5_test2()  # 21169ee3acd4a24e1fcb4322cfd9a2b8

PBKDF2

简介:英文名称:Password-Based Key Derivation Function 2,PBKDF2 是 RSA 实验室的公钥加密标准(PKCS)系列的一部分,2017 年发布的 RFC 8018 (PKCS #5 v2.1)推荐使用 PBKDF2 进行密码散列。PBKDF2 将伪随机函数(例如 HMAC),把明文和一个盐值(salt)作为输入参数,然后进行重复运算,并最终产生密钥,如果重复的次数足够大,破解的成本就会变得很高。

参考资料:

JavaScript 实现

// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')

function pbkdf2Encrypt() {
    var text = "I love Python!"
    var salt = "43215678"
    // key 长度 128,10 次重复运算
    var encryptedData = CryptoJS.PBKDF2(text, salt, {keySize: 128/32,iterations: 10});
    return encryptedData.toString()
}

console.log(pbkdf2Encrypt())  // 7fee6e8350cfe96314c76aaa6e853a50

Python 实现

import binascii
from Crypto.Hash import SHA256, SHA1
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Random import get_random_bytes


pw = 'I love Python!'
salt = b'43215678'
salt = get_random_bytes(16)  # 生成16字节的随机盐

result = PBKDF2(pw, salt, count=10, hmac_hash_module=SHA1)
result = binascii.hexlify(result)
print(result)
# b'7fee6e8350cfe96314c76aaa6e853a50'


# PBKDF2参数
key_length = 32  # 希望生成的密钥长度(以字节为单位)
iterations = 100000  # 迭代次数
# 使用PBKDF2派生密钥
key = PBKDF2(pw, salt, dkLen=key_length, count=iterations, hmac_hash_module=SHA256)
print("派生的密钥:", key.hex())
print("使用的盐:", salt.hex())

SHA

简介:全称安全哈希算法(英文名称:Secure Hash Algorithm),由美国国家安全局(NSA)所设计,主要适用于数字签名标准(Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA),SHA 通常指 SHA 家族的五个算法,分别是 SHA-1、SHA-224、SHA-256、SHA-384、SHA-512,后四者有时并称为 SHA-2,SHA 是比 MD5 更安全一点的摘要算法,MD5 的密文是 32 位,而 SHA-1 是 40 位,版本越强,密文越长,代价是速度越慢。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function SHA1Encrypt() {
    var text = "I  love  python!"
    return CryptoJS.SHA1(text).toString();
}

console.log(SHA1Encrypt())    //  23c02b203bd2e2ca19da911f1d270a06d86719fb

Python 实现

import hashlib


def sha1_test1():
    sha1 = hashlib.new('sha1', 'I   love   python!'.encode('utf-8'))
    print(sha1.hexdigest())


def sha1_test2():
    sha1 = hashlib.sha1()
    sha1.update('I   love   python!'.encode('utf-8'))
    print(sha1.hexdigest())


if __name__ == '__main__':
    sha1_test1()  # 23c02b203bd2e2ca19da911f1d270a06d86719fb
    sha1_test2()  # 23c02b203bd2e2ca19da911f1d270a06d86719fb

HMAC

简介:全称散列消息认证码、密钥相关的哈希运算消息认证码(英文名称:Hash-based Message Authentication Code 或者 Keyed-hash Message Authentication Code),于 1996 年提出,1997 年作为 RFC 2104 被公布,HMAC 加密算法是一种安全的基于加密 Hash 函数和共享密钥的消息认证协议,它要求通信双方共享密钥 key、约定算法、对报文进行 Hash 运算,形成固定长度的认证码。通信双方通过认证码的校验来确定报文的合法性。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function HMACEncrypt() {
    var text = "I  love  python!"
    var key = "secret"
    return CryptoJS.HmacMD5(text, key).toString();
    //  return  CryptoJS.HmacSHA1(text,  key).toString();
    //  return  CryptoJS.HmacSHA256(text,  key).toString();
}

console.log(HMACEncrypt())

Python 实现

import hmac


def hmac_test1():
    message = b'I   love   python!'
    key = b'secret'
    md5 = hmac.new(key, message, digestmod='MD5')
    print(md5.hexdigest())


def hmac_test2():
    key = 'secret'.encode('utf8')
    sha1 = hmac.new(key, digestmod='sha1')
    sha1.update('I   love   '.encode('utf8'))
    sha1.update('Python!'.encode('utf8'))
    print(sha1.hexdigest())


if __name__ == '__main__':
    hmac_test1()  # 9c503a1f852edcc3526ea56976c38edf
    hmac_test2()  # 2d8449a4292d4bbeed99ce9ea570880d6e19b61a

DES

简介:全称数据加密标准(英文名称:Data Encryption Standard),加密与解密使用同一密钥,属于对称加密算法,1977 年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),DES 是一个分组加密算法,使用 56 位的密钥(一般认为密钥是 64 位,但是密钥的每个第 8 位设置为奇偶校验位,所以实际上有效位只有 56 位),由于 56 位密钥长度相对较短,所以 DES 是不安全的,现在基本上已被更高级的加密标准 AES 取代。

  • mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。
  • padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function desEncrypt() {
    var key = CryptoJS.enc.Utf8.parse(desKey),
        iv = CryptoJS.enc.Utf8.parse(desIv),
        srcs = CryptoJS.enc.Utf8.parse(text),
        //  CBC  加密模式,Pkcs7  填充方式
        encrypted = CryptoJS.DES.encrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
    return encrypted.toString();
}

function desDecrypt() {
    var key = CryptoJS.enc.Utf8.parse(desKey),
        iv = CryptoJS.enc.Utf8.parse(desIv),
        srcs = encryptedData,
        //  CBC  加密模式,Pkcs7  填充方式
        decrypted = CryptoJS.DES.decrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
    return decrypted.toString(CryptoJS.enc.Utf8);
}

var text = "I  love  Python!"              //  待加密对象
var desKey = "6f726c64f2c2057"        //  密钥
var desIv = "0123456789ABCDEF"        //  初始向量

var encryptedData = desEncrypt()
var decryptedData = desDecrypt()

console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

//  加密字符串:    +ndbEkWNw2QAfIYQtwC14w==
//  解密字符串:    I  love  Python!

Python 实现

from Crypto.Cipher import DES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes


def encrypt_with_des(plaintext):
    # DES密钥必须是8字节长
    key = get_random_bytes(8)

    # 创建DES加密器对象,使用ECB模式
    cipher = DES.new(key, DES.MODE_ECB)

    # 对明文进行填充,然后加密
    # DES块大小是8字节
    padded_plaintext = pad(plaintext, DES.block_size)
    ciphertext = cipher.encrypt(padded_plaintext)

    return ciphertext, key


def decrypt_with_des(ciphertext, key):
    # 创建DES解密器对象,使用相同的密钥和ECB模式
    cipher = DES.new(key, DES.MODE_ECB)

    # 解密密文,然后去除填充
    decrypted_plaintext = cipher.decrypt(ciphertext)
    unpadded_plaintext = unpad(decrypted_plaintext, DES.block_size)

    return unpadded_plaintext


# 测试加密和解密
plaintext = b"Hello, DES Encryption!"  # 原始明文
ciphertext, key = encrypt_with_des(plaintext)
decrypted_plaintext = decrypt_with_des(ciphertext, key)

print(f"原始明文: {plaintext}")
print(f"密文: {ciphertext}")
print(f"解密后的明文: {decrypted_plaintext}")

3DES

简介:全称三重数据加密算法(英文名称:Triple Data Encryption Standard、 Triple Data Encryption Algorithm、TDES、TDEA),是对称加密算法中的一种。70 年代初由 IBM 研发,后 1977 年被美国国家标准局采纳为数据加密标准,它相当于是对每个数据块应用三次 DES 加密算法。由于计算机运算能力的增强,原版 DES 密码的密钥长度变得容易被暴力破解;3DES 即是设计用来提供一种相对简单的方法,即通过增加 DES 的密钥长度来避免破解,所以严格来说 3DES 不是设计一种全新的块密码算法。

  • mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。
  • padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function tripleDesEncrypt() {
    var key = CryptoJS.enc.Utf8.parse(desKey),
        iv = CryptoJS.enc.Utf8.parse(desIv),
        srcs = CryptoJS.enc.Utf8.parse(text),
        //  ECB  加密方式,Iso10126  填充方式
        encrypted = CryptoJS.TripleDES.encrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Iso10126
        });
    return encrypted.toString();
}

function tripleDesDecrypt() {
    var key = CryptoJS.enc.Utf8.parse(desKey),
        iv = CryptoJS.enc.Utf8.parse(desIv),
        srcs = encryptedData,
        //  ECB  加密方式,Iso10126  填充方式
        decrypted = CryptoJS.TripleDES.decrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.ECB,
            padding: CryptoJS.pad.Iso10126
        });
    return decrypted.toString(CryptoJS.enc.Utf8);
}

var text = "I  love  Python!"              //  待加密对象
var desKey = "6f726c64f2c2057c"        //  密钥
var desIv = "0123456789ABCDEF"        //  偏移量

var encryptedData = tripleDesEncrypt()
var decryptedData = tripleDesDecrypt()

console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

//  加密字符串:    3J0NX7x6GbewjjhoW2HKqg==
//  解密字符串:    I  love  Python!

Python 实现

from Cryptodome.Cipher import DES3
from Cryptodome import Random


#   需要补位,str不是16的倍数那就补足为16的倍数
def add_to_16(value):
    while len(value) % 16 != 0:
        value += '\0'
    return str.encode(value)


def des_encrypt(key, text, iv):
    #   加密模式   OFB
    cipher_encrypt = DES3.new(add_to_16(key), DES3.MODE_OFB, iv)
    encrypted_text = cipher_encrypt.encrypt(text.encode("utf-8"))
    return encrypted_text


def des_decrypt(key, text, iv):
    #   加密模式   OFB
    cipher_decrypt = DES3.new(add_to_16(key), DES3.MODE_OFB, iv)
    decrypted_text = cipher_decrypt.decrypt(text)
    return decrypted_text


if __name__ == '__main__':
    key = '12345678'  # 密钥,16   位
    text = 'I   love   Python!'  # 加密对象
    iv = Random.new().read(DES3.block_size)  # DES3.block_size   ==   8
    secret_str = des_encrypt(key, text, iv)
    print('加密字符串:', secret_str)
    clear_str = des_decrypt(key, secret_str, iv)
    print('解密字符串:', clear_str)

#   加密字符串:b'\xa5\x8a\xd4R\x99\x16j\xba?vg\xf2\xb6\xa9'
#   解密字符串:b'I love Python!'

AES

简介:全称高级加密标准(英文名称:Advanced Encryption Standard),在密码学中又称 Rijndael 加密法,由美国国家标准与技术研究院 (NIST)于 2001 年发布,并在 2002 年成为有效的标准,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的 DES,已经被多方分析且广为全世界所使用,它本身只有一个密钥,即用来实现加密,也用于解密。

  • mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。
  • padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function tripleAesEncrypt() {
    var key = CryptoJS.enc.Utf8.parse(aesKey),
        iv = CryptoJS.enc.Utf8.parse(aesIv),
        srcs = CryptoJS.enc.Utf8.parse(text),
        //  CBC  加密方式,Pkcs7  填充方式
        encrypted = CryptoJS.AES.encrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
    return encrypted.toString();
}

function tripleAesDecrypt() {
    var key = CryptoJS.enc.Utf8.parse(aesKey),
        iv = CryptoJS.enc.Utf8.parse(aesIv),
        srcs = encryptedData,
        //  CBC  加密方式,Pkcs7  填充方式
        decrypted = CryptoJS.AES.decrypt(srcs, key, {
            iv: iv,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.Pkcs7
        });
    return decrypted.toString(CryptoJS.enc.Utf8);
}

var text = "I  love  Python!"              //  待加密对象
var aesKey = "6f726c64f2c2057c"      //  密钥,16  倍数
var aesIv = "0123456789ABCDEF"        //  偏移量,16  倍数

var encryptedData = tripleAesEncrypt()
var decryptedData = tripleAesDecrypt()

console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

//  加密字符串:    dZL7TLJR786VGvuUvqYGoQ==
//  解密字符串:    I  love  Python!

Python 实现

import base64
from Cryptodome.Cipher import AES


#   需要补位,str不是16的倍数那就补足为16的倍数
def add_to_16(value):
    while len(value) % 16 != 0:
        value += '\0'
    return str.encode(value)


#   加密方法
def aes_encrypt(key, t, iv):
    aes = AES.new(add_to_16(key), AES.MODE_CBC, add_to_16(iv))  # 初始化加密器
    encrypt_aes = aes.encrypt(add_to_16(t))  # 先进行   aes   加密
    encrypted_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8')  # 执行加密并转码返回   bytes
    return encrypted_text


#   解密方法
def aes_decrypt(key, t, iv):
    aes = AES.new(add_to_16(key), AES.MODE_CBC, add_to_16(iv))  # 初始化加密器
    base64_decrypted = base64.decodebytes(t.encode(encoding='utf-8'))  # 优先逆向解密   base64   成   bytes
    decrypted_text = str(aes.decrypt(base64_decrypted), encoding='utf-8').replace('\0', '')  # 执行解密密并转码返回str
    return decrypted_text


if __name__ == '__main__':
    secret_key = '12345678'  # 密钥
    text = 'I   love   Python!'  # 加密对象
    iv = secret_key  # 初始向量
    encrypted_str = aes_encrypt(secret_key, text, iv)
    print('加密字符串:', encrypted_str)
    decrypted_str = aes_decrypt(secret_key, encrypted_str, iv)
    print('解密字符串:', decrypted_str)

#   加密字符串:lAVKvkQh+GtdNpoKf4/mHA==
#   解密字符串:I love Python!

RC4

简介:英文名称:Rivest Cipher 4,也称为 ARC4 或 ARCFOUR,是一种流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。RC4 是有线等效加密(WEP)中采用的加密算法,也曾经是 TLS 可采用的算法之一,该算法的速度可以达到 DES 加密的 10 倍左右,且具有很高级别的非线性,虽然它在软件方面的简单性和速度非常出色,但在 RC4 中发现了多个漏洞,它特别容易受到攻击,RC4 作为一种老旧的验证和加密算法易于受到黑客攻击,现在逐渐不推荐使用了。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function RC4Encrypt() {
    return CryptoJS.RC4.encrypt(text, key).toString();
}

function RC4Decrypt() {
    return CryptoJS.RC4.decrypt(encryptedData, key).toString(CryptoJS.enc.Utf8);
}

var text = "I  love  Python!"
var key = "6f726c64f2c2057c"

var encryptedData = RC4Encrypt()
var decryptedData = RC4Decrypt()

console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

//  加密字符串:    U2FsdGVkX18hMm9WWdoEQGPolnXzlg9ryArdGNwv
//  解密字符串:    I  love  Python!

Python 实现

import base64
from Cryptodome.Cipher import ARC4


def rc4_encrypt(key, t):
    enc = ARC4.new(key.encode('utf8'))
    res = enc.encrypt(t.encode('utf-8'))
    res = base64.b64encode(res)
    return res


def rc4_decrypt(key, t):
    data = base64.b64decode(t)
    enc = ARC4.new(key.encode('utf8'))
    res = enc.decrypt(data)
    return res


if __name__ == "__main__":
    secret_key = '12345678'  # 密钥
    text = 'I   love   Python!'  # 加密对象
    encrypted_str = rc4_encrypt(secret_key, text)
    print('加密字符串:', encrypted_str)
    decrypted_str = rc4_decrypt(secret_key, encrypted_str)
    print('解密字符串:', decrypted_str)

#   加密字符串:b'8tNVu3/U/veJR2KgyBw='
#   解密字符串:b'I love Python!'

Rabbit

简介:Rabbit 加密算法是一个高性能的流密码加密方式,2003 年首次被提出,它从 128 位密钥和 64 位初始向量(iv)创建一个密钥流。

参考资料:

JavaScript 实现

//  引用  crypto-js  加密模块
var CryptoJS = require('crypto-js')

function rabbitEncrypt() {
    return CryptoJS.Rabbit.encrypt(text, key).toString();
}

function rabbitDecrypt() {
    return CryptoJS.Rabbit.decrypt(encryptedData, key).toString(CryptoJS.enc.Utf8);
}

var text = "I  love  Python!"
var key = "6f726c64f2c2057"

var encryptedData = rabbitEncrypt()
var decryptedData = rabbitDecrypt()

console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

//  加密字符串:    U2FsdGVkX1+ZVCHRXlhmG5Xw87YPWMNIBlbukuh8
//  解密字符串:    I  love  Python!

Python 实现

目前没有找到有第三方库可以直接实现 Rabbit 算法,在 Python 中实现可以参考:https://asecuritysite.com/encryption/rabbit2

RSA

简介:英文名称:Rivest-Shamir-Adleman,是 1977 年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的,RSA 就是他们三人姓氏开头字母拼在一起组成的,RSA 加密算法是一种非对称加密算法。在公开密钥加密和电子商业中RSA被广泛使用。它被普遍认为是目前比较优秀的公钥方案之一。RSA是第一个能同时用于加密和数字签名的算法,它能够抵抗到目前为止已知的所有密码攻击。

参考资料:RSA 维基百科:https://en.wikipedia.org/wiki/RSA_(cryptosystem)

JavaScript 实现。安装 包:npm install node-rsa

//  引用  node-rsa  加密模块
var NodeRSA = require('node-rsa');

function rsaEncrypt() {
    pubKey = new NodeRSA(publicKey, 'pkcs8-public');
    var encryptedData = pubKey.encrypt(text, 'base64');
    return encryptedData
}

function rsaDecrypt() {
    priKey = new NodeRSA(privatekey, 'pkcs8-private');
    var decryptedData = priKey.decrypt(encryptedData, 'utf8');
    return decryptedData
}

var key = new NodeRSA({b: 512});                                        //生成512位秘钥
var publicKey = key.exportKey('pkcs8-public');        //导出公钥
var privatekey = key.exportKey('pkcs8-private');    //导出私钥
var text = "I  love  Python!"

var encryptedData = rsaEncrypt()
var decryptedData = rsaDecrypt()

console.log("公钥:\n", publicKey)
console.log("私钥:\n", privatekey)
console.log("加密字符串:  ", encryptedData)
console.log("解密字符串:  ", decryptedData)

/*
公钥:
  -----BEGIN  PUBLIC  KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAOV1BwTJSVce/QjJAro5fXG9WzOpal09
Qtv1yuXKE81vZSNTHxW6dICwPT/kjCfC3bA5Qs6wnYBANuwD6wlAS0UCAwEAAQ==
-----END  PUBLIC  KEY-----
私钥:
  -----BEGIN  PRIVATE  KEY-----
MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEA5XUHBMlJVx79CMkC
ujl9cb1bM6lqXT1C2/XK5coTzW9lI1MfFbp0gLA9P+SMJ8LdsDlCzrCdgEA27APr
CUBLRQIDAQABAkAiXwJbJC+5PioXG80tyhjRZdT4iyMkrl2Kh2oKO9f1iLaBXLya
D0HW82wFh+cUy8GcMl9jse8DE8wd1TdORmHhAiEA/rwmWjXHVgDqcH/fqk8Ufku0
fXvs56h5QDoh1so5vokCIQDmmL3JDW6Y7RuK2qwFbHBZtYPRFRVdn5X1oqU2FOSX
3QIhAOVTjVN5RtNuT6Cn/jvcpZ5tmTe+8TA8w6vGqeAsfn/BAiBvKKIUEQ2HWoU0
YkUaODPQiteIKomqIAvB5S2O7HNlYQIgWMuLUxGZbbcAmIX+YmRXuET97S7OWv+z
WHVfb/rbXtI=
-----END  PRIVATE  KEY-----
加密字符串:    hHXTF1K3w55Wd6OSjVYtqxceJ5VhlySNUahel9pwKD92Ef7wIT7DYPuJRKiqz5tuHtUqujbmbZBSL0qDE/EA+A==
解密字符串:    I  love  Python!
*/

在JavaScript中实现RSA加密通常需要使用一些库来帮助生成密钥、加密和解密数据。一个比较流行且功能强大的库是node-rsa,它可以用于Node.js环境。对于在浏览器中使用RSA,可以考虑使用Web Cryptography API,这是现代浏览器提供的一个原生API,支持多种安全操作,包括RSA加密。

生成密钥对:

const NodeRSA = require('node-rsa');
const key = new NodeRSA({b: 512}); // 生成一个512位的密钥

const publicKey = key.exportKey('public'); // 导出公钥
const privateKey = key.exportKey('private'); // 导出私钥

使用公钥加密数据

const text = 'Hello RSA!';
const encryptedString = key.encrypt(text, 'base64');
console.log('encrypted:', encryptedString);

使用私钥解密数据

const decryptedString = key.decrypt(encryptedString, 'utf8');
console.log('decrypted:', decryptedString);

上面示例展示了如何在Node.js环境下使用node-rsa库来实现RSA加密和解密。

Python 实现

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import binascii

# 生成密钥对
keyPair = RSA.generate(2048)
publicKey = keyPair.publickey()

pubKeyPEM = publicKey.exportKey()
privKeyPEM = keyPair.exportKey()

print(f"Public key: \n{pubKeyPEM.decode('ascii')}")
print(f"Private key: \n{privKeyPEM.decode('ascii')}")

# 使用公钥加密数据
msg = b'Hello RSA Encryption!'
encryptor = PKCS1_OAEP.new(publicKey)
encrypted = encryptor.encrypt(msg)
print("Encrypted:", binascii.hexlify(encrypted))

# 使用私钥解密数据
decryptor = PKCS1_OAEP.new(keyPair)
decrypted = decryptor.decrypt(encrypted)
print('Decrypted:', decrypted)

使用 rsa 模块:pip install rsa

import rsa


def rsa_encrypt(pu_key, t):
    #   公钥加密
    temp = rsa.encrypt(t.encode("utf-8"), pu_key)
    return temp


def rsa_decrypt(pr_key, t):
    #   私钥解密
    temp = rsa.decrypt(t, pr_key).decode("utf-8")
    return temp


if __name__ == "__main__":
    public_key, private_key = rsa.newkeys(512)  # 生成公钥、私钥
    print('公钥:', public_key)
    print('私钥:', private_key)
    text = 'I_love_Python!'  # 加密对象
    encrypted_str = rsa_encrypt(public_key, text)
    print('加密字符串:', encrypted_str)
    decrypted_str = rsa_decrypt(private_key, encrypted_str)
    print('解密字符串:', decrypted_str)

'''
公钥:PublicKey(7636479066127060956100056267701318377455704072072698049978592945665550579944731953431504993757594103617537700972424661030900303472123028864161050235168613, 65537)
私钥:PrivateKey(7636479066127060956100056267701318377455704072072698049978592945665550579944731953431504993757594103617537700972424661030900303472123028864161050235168613, 65537, 3850457767980968449796700480128630632818465005441846698224554128042451115530564586537997896922067523638756079019054611200173122138274839877369624069360253, 4713180694194659323798858305046043997526301456820208338158979730140812744181638767, 1620238976946735819854194349514460863335347861649166352709029254680140139)
加密字符串:b"\x1aaeps\xa0c}\xb6\xcf\xa3\xb0\xbb\xedA\x7f}\x03\xdc\xd5\x1c\x9b\xdb\xda\xf9q\x80[=\xf5\x91\r\xd0'f\xce\x1f\x01\xef\xa5\xdb3\x96\t0qIxF\xbd\x11\xd6\xb25\xc5\xe1pM\xb4M\xc2\xd4\x03\xa6"
解密字符串:I love Python!
'''

使用 Cryptodome 模块

import base64
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5

data = "cKK8B2rWwfwWeXhz"
public_key = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAM1xhOWaThSMpfxFsjV5YaWOFHt+6RvS+zH2Pa47VVr8PkZYnRaaKKy2MYBuEh7mZfM/R1dUXTgu0gp6VTNeNQkCAwEAAQ=="
rsa_key = RSA.import_key(base64.b64decode(public_key))  # 导入读取到的公钥
cipher = PKCS1_v1_5.new(rsa_key)  # 生成对象
cipher_text = base64.b64encode(cipher.encrypt(data.encode(encoding="utf-8")))
print(cipher_text)

使用 Web Cryptography API 在浏览器中实现 RSA 加密

Web Cryptography API提供了一套比较底层的API,允许直接在浏览器中操作加密密钥和执行加密操作。下面是一个简单的示例,展示如何使用这个API生成RSA密钥对和进行加密解密操作:

// 生成密钥对
window.crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",
        modulusLength: 2048, // 密钥长度
        publicExponent: new Uint8Array([1, 0, 1]),
        hash: {name: "SHA-256"}, // 使用的哈希函数
    },
    true, // 是否可导出
    ["encrypt", "decrypt"] // 密钥用途
).then((keyPair) => {
    // 使用公钥加密信息
    const encoder = new TextEncoder();
    const data = encoder.encode('Hello RSA!');
    window.crypto.subtle.encrypt(
        {
            name: "RSA-OAEP"
        },
        keyPair.publicKey,
        data
    ).then((encrypted) => {
        // 在这里处理加密后的数据
        console.log(new Uint8Array(encrypted));
        
        // 使用私钥解密信息
        window.crypto.subtle.decrypt(
            {
                name: "RSA-OAEP"
            },
            keyPair.privateKey,
            encrypted
        ).then((decrypted) => {
            // 在这里处理解密后的数据
            const decoder = new TextDecoder();
            console.log(decoder.decode(decrypted)); // 输出解密后的信息
        });
    });
});

使用Web Cryptography API时,可能需要考虑浏览器的兼容性。

Frida Hook Java层的加密算法 通杀自吐脚本

关键字:frida hook java 解密 自吐

参考链接:
原生安卓开发app的框架frida自吐算法开发:https://blog.51cto.com/u_13389043/6230053
frida hook java原生算法同时打印调用堆栈:https://blog.csdn.net/weixin_34365417/article/details/93088342
frida java层加密通杀算法 自吐脚本算法:https://blog.csdn.net/rni88/article/details/134364285

frida hook AES DES RSA frida自吐算法脚本:https://www.codenong.com/jsecbee028022b/

脚本功能

直接hook所有常用加密,打印出加密参数、加密结果和堆栈。

使用流程

1.找到App软件的包名,填入程序中启动
2.找到需要hook的位置运行程序
3.分析打印结果(输出较多,一般直接搜索加密生成的值)

import frida, sys
#doFinal参数打印出来的参数为不可见时可将打印类型替换为bytesToBase64
#打印不出doFinal data参数时将参数替换为toBase64

j1 = """
var N_ENCRYPT_MODE = 1
var N_DECRYPT_MODE = 2
function showStacks() {
    var Exception = Java.use("java.lang.Exception");
    var ins = Exception.$new("Exception");
    var straces = ins.getStackTrace();
    if (undefined == straces || null == straces) {
        return;
    }
    console.log("============================= Stack strat=======================");
    console.log("");
    for (var i = 0; i < straces.length; i++) {
        var str = "   " + straces[i].toString();
        console.log(str);
    }
    console.log("");
    Exception.$dispose();
}
//工具相关函数
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
    base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));
function stringToBase64(e) {
    var r, a, c, h, o, t;
    for (c = e.length, a = 0, r = ''; a < c;) {
        if (h = 255 & e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4),
                r += '==';
            break
        }
        if (o = e.charCodeAt(a++), a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
                r += base64EncodeChars.charAt((15 & o) << 2),
                r += '=';
            break
        }
        t = e.charCodeAt(a++),
            r += base64EncodeChars.charAt(h >> 2),
            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
            r += base64EncodeChars.charAt(63 & t)
    }
    return r
}
function base64ToString(e) {
    var r, a, c, h, o, t, d;
    for (t = e.length, o = 0, d = ''; o < t;) {
        do
            r = base64DecodeChars[255 & e.charCodeAt(o++)];
        while (o < t && r == -1);
        if (r == -1)
            break;
        do
            a = base64DecodeChars[255 & e.charCodeAt(o++)];
        while (o < t && a == -1);
        if (a == -1)
            break;
        d += String.fromCharCode(r << 2 | (48 & a) >> 4);
        do {
            if (c = 255 & e.charCodeAt(o++), 61 == c)
                return d;
            c = base64DecodeChars[c]
        } while (o < t && c == -1);
        if (c == -1)
            break;
        d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);
        do {
            if (h = 255 & e.charCodeAt(o++), 61 == h)
                return d;
            h = base64DecodeChars[h]
        } while (o < t && h == -1);
        if (h == -1)
            break;
        d += String.fromCharCode((3 & c) << 6 | h)
    }
    return d
}
function hexToBase64(str) {
    return base64Encode(String.fromCharCode.apply(null, str.replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
function base64ToHex(str) {
    for (var i = 0, bin = base64Decode(str), hex = []; i < bin.length; ++i) {
        var tmp = bin.charCodeAt(i).toString(16);
        if (tmp.length === 1)
            tmp = "0" + tmp;
        hex[hex.length] = tmp;
    }
    return hex.join("");
}
function hexToBytes(str) {
    var pos = 0;
    var len = str.length;
    if (len % 2 != 0) {
        return null;
    }
    len /= 2;
    var hexA = new Array();
    for (var i = 0; i < len; i++) {
        var s = str.substr(pos, 2);
        var v = parseInt(s, 16);
        hexA.push(v);
        pos += 2;
    }
    return hexA;
}
function bytesToHex(arr) {
    var str = '';
    var k, j;
    for (var i = 0; i < arr.length; i++) {
        k = arr[i];
        j = k;
        if (k < 0) {
            j = k + 256;
        }
        if (j < 16) {
            str += "0";
        }
        str += j.toString(16);
    }
    return str;
}
function stringToHex(str) {
    var val = "";
    for (var i = 0; i < str.length; i++) {
        if (val == "")
            val = str.charCodeAt(i).toString(16);
        else
            val += str.charCodeAt(i).toString(16);
    }
    return val
}
function stringToBytes(str) {
    var ch, st, re = [];
    for (var i = 0; i < str.length; i++) {
        ch = str.charCodeAt(i);
        st = [];
        do {
            st.push(ch & 0xFF);
            ch = ch >> 8;
        }
        while (ch);
        re = re.concat(st.reverse());
    }
    return re;
}
//将byte[]转成String的方法
function bytesToString(arr) {
    var str = '';
    arr = new Uint8Array(arr);
    for (var i in arr) {
        str += String.fromCharCode(arr[i]);
    }
    return str;
}
function bytesToBase64(e) {
    var r, a, c, h, o, t;
    for (c = e.length, a = 0, r = ''; a < c;) {
        if (h = 255 & e[a++], a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4),
                r += '==';
            break
        }
        if (o = e[a++], a == c) {
            r += base64EncodeChars.charAt(h >> 2),
                r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
                r += base64EncodeChars.charAt((15 & o) << 2),
                r += '=';
            break
        }
        t = e[a++],
            r += base64EncodeChars.charAt(h >> 2),
            r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
            r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
            r += base64EncodeChars.charAt(63 & t)
    }
    return r
}
function base64ToBytes(e) {
    var r, a, c, h, o, t, d;
    for (t = e.length, o = 0, d = []; o < t;) {
        do
            r = base64DecodeChars[255 & e.charCodeAt(o++)];
        while (o < t && r == -1);
        if (r == -1)
            break;
        do
            a = base64DecodeChars[255 & e.charCodeAt(o++)];
        while (o < t && a == -1);
        if (a == -1)
            break;
        d.push(r << 2 | (48 & a) >> 4);
        do {
            if (c = 255 & e.charCodeAt(o++), 61 == c)
                return d;
            c = base64DecodeChars[c]
        } while (o < t && c == -1);
        if (c == -1)
            break;
        d.push((15 & a) << 4 | (60 & c) >> 2);
        do {
            if (h = 255 & e.charCodeAt(o++), 61 == h)
                return d;
            h = base64DecodeChars[h]
        } while (o < t && h == -1);
        if (h == -1)
            break;
        d.push((3 & c) << 6 | h)
    }
    return d
}
//stringToBase64 stringToHex stringToBytes
//base64ToString base64ToHex base64ToBytes
//               hexToBase64  hexToBytes    
// bytesToBase64 bytesToHex bytesToString
Java.perform(function () {
    var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
    secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
        showStacks();
        var result = this.$init(a, b);
        console.log("======================================");
        console.log("算法名:" + b + "|str密钥:" + bytesToString(a));
        console.log("算法名:" + b + "|Hex密钥:" + bytesToHex(a));
        return result;
    }
    var DESKeySpec = Java.use('javax.crypto.spec.DESKeySpec');
    DESKeySpec.$init.overload('[B').implementation = function (a) {
        showStacks();
        var result = this.$init(a);
        console.log("======================================");
        var bytes_key_des = this.getKey();
        console.log("des密钥  |str " + bytesToString(bytes_key_des));
        console.log("des密钥  |hex " + bytesToHex(bytes_key_des));
        return result;
    }
    DESKeySpec.$init.overload('[B', 'int').implementation = function (a, b) {
        showStacks();
        var result = this.$init(a, b);
        console.log("======================================");
        var bytes_key_des = this.getKey();
        console.log("des密钥  |str " + bytesToString(bytes_key_des));
        console.log("des密钥  |hex " + bytesToHex(bytes_key_des));
        return result;
    }
    var mac = Java.use('javax.crypto.Mac');
    mac.getInstance.overload('java.lang.String').implementation = function (a) {
        showStacks();
        var result = this.getInstance(a);
        console.log("======================================");
        console.log("算法名:" + a);
        return result;
    }
    mac.update.overload('[B').implementation = function (a) {
        //showStacks();
        this.update(a);
        console.log("======================================");
        console.log("update:" + bytesToString(a))
    }
    mac.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
        //showStacks();
        this.update(a, b, c)
        console.log("======================================");
        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
    }
    mac.doFinal.overload().implementation = function () {
        //showStacks();
        var result = this.doFinal();
        console.log("======================================");
        console.log("doFinal结果: |str  :"     + bytesToString(result));
        console.log("doFinal结果: |hex  :"     + bytesToHex(result));
        console.log("doFinal结果: |base64  :"  + bytesToBase64(result));
        return result;
    }
    mac.doFinal.overload('[B').implementation = function (a) {
        //showStacks();
        var result = this.doFinal(a);
        console.log("======================================");
        console.log("doFinal参数: |str  :"     + bytesToString(a));
        console.log("doFinal结果: |str  :"     + bytesToString(result));
        console.log("doFinal结果: |hex  :"     + bytesToHex(result));
        console.log("doFinal结果: |base64  :"  + bytesToBase64(result));
        return result;
    }
    var md = Java.use('java.security.MessageDigest');
    md.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function (a, b) {
        //showStacks();
        console.log("======================================");
        console.log("算法名:" + a);
        return this.getInstance(a, b);
    }
    md.getInstance.overload('java.lang.String').implementation = function (a) {
        //showStacks();
        console.log("======================================");
        console.log("算法名:" + a);
        return this.getInstance(a);
    }
    md.update.overload('[B').implementation = function (a) {
        //showStacks();
        console.log("======================================");
        console.log("update:" + bytesToString(a))
        return this.update(a);
    }
    md.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
        //showStacks();
        console.log("======================================");
        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
        return this.update(a, b, c);
    }
    md.digest.overload().implementation = function () {
        //showStacks();
        console.log("======================================");
        var result = this.digest();
        console.log("digest结果:" + bytesToHex(result));
        console.log("digest结果:" + bytesToBase64(result));
        return result;
    }
    md.digest.overload('[B').implementation = function (a) {
        //showStacks();
        console.log("======================================");
        console.log("digest参数:" + bytesToString(a));
        var result = this.digest(a);
        console.log("digest结果:" + bytesToHex(result));
        console.log("digest结果:" + bytesToBase64(result));
        return result;
    }
    var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
    ivParameterSpec.$init.overload('[B').implementation = function (a) {
        //showStacks();
        var result = this.$init(a);
        console.log("======================================");
        console.log("iv向量: |str:" + bytesToString(a));
        console.log("iv向量: |hex:" + bytesToHex(a));
        return result;
    }
    var cipher = Java.use('javax.crypto.Cipher');
    cipher.getInstance.overload('java.lang.String').implementation = function (a) {
        //showStacks();
        var result = this.getInstance(a);
        console.log("======================================");
        console.log("模式填充:" + a);
        return result;
    }
    cipher.init.overload('int', 'java.security.Key').implementation = function (a, b) {
        //showStacks();
        var result = this.init(a, b);
        console.log("======================================");
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function (a, b) {
        //showStacks();
        var result = this.init(a, b);
        console.log("======================================");
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        return result;
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (a, b, c) {
        //showStacks();
        var result = this.init(a, b, c);
        console.log("======================================");
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function (a, b, c) {
        //showStacks();
        var result = this.init(a, b, c);
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        return result;
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function (a, b, c) {
        //showStacks();
        var result = this.init(a, b, c);
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
         var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function (a, b, c) {
        //showStacks();
        var result = this.init(a, b, c);
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function (a, b, c, d) {
        //showStacks();
        var result = this.init(a, b, c, d);
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
        var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function (a, b, c, d) {
        //showStacks();
        var result = this.update(a, b, c, d);
        if (N_ENCRYPT_MODE == a)
        {
            console.log("init  | 加密模式");    
        }
        else if(N_DECRYPT_MODE == a)
        {
            console.log("init  | 解密模式");    
        }
         var bytes_key = b.getEncoded();
        console.log("init key:" + "|str密钥:" + bytesToString(bytes_key));
        console.log("init key:" + "|Hex密钥:" + bytesToHex(bytes_key));
        return result;
    }
    cipher.update.overload('[B').implementation = function (a) {
        //showStacks();
        var result = this.update(a);
        console.log("======================================");
        console.log("update:" + bytesToString(a));
        return result;
    }
    cipher.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
        //showStacks();
        var result = this.update(a, b, c);
        console.log("======================================");
        console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
        return result;
    }
    cipher.doFinal.overload().implementation = function () {
        //showStacks();
        var result = this.doFinal();
        console.log("======================================");
        console.log("doFinal结果: |str  :"     + bytesToString(result));
        console.log("doFinal结果: |hex  :"     + bytesToHex(result));
        console.log("doFinal结果: |base64  :"  + bytesToBase64(result));
        return result;
    }
    cipher.doFinal.overload('[B').implementation = function (a) {
        //showStacks();
        var result = this.doFinal(a);
        console.log("======================================");
        console.log("doFinal参数: |str  :"     + bytesToBase64(a));
        console.log("doFinal结果: |str  :"     + bytesToString(result));
        console.log("doFinal结果: |hex  :"     + bytesToHex(result));
        console.log("doFinal结果: |base64  :"  + bytesToBase64(result));
        return result;
    }
    var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');
    x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
        //showStacks();
        var result = this.$init(a);
        console.log("======================================");
        console.log("RSA密钥:" + bytesToBase64(a));
        return result;
    }
    var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');
    rSAPublicKeySpec.$init.overload('java.math.BigInteger', 'java.math.BigInteger').implementation = function (a, b) {
        //showStacks();
        var result = this.$init(a, b);
        console.log("======================================");
        //console.log("RSA密钥:" + bytesToBase64(a));
        console.log("RSA密钥N:" + a.toString(16));
        console.log("RSA密钥E:" + b.toString(16));
        return result;
    }
    var KeyPairGenerator = Java.use('java.security.KeyPairGenerator');
    KeyPairGenerator.generateKeyPair.implementation = function ()
    {
        //showStacks();
        var result = this.generateKeyPair();
        console.log("======================================");
        var str_private = result.getPrivate().getEncoded();
        var str_public = result.getPublic().getEncoded();
        console.log("公钥  |hex" + bytesToHex(str_public));
        console.log("私钥  |hex" + bytesToHex(str_private));
        return result;
    }
    KeyPairGenerator.genKeyPair.implementation = function ()
    {
        //showStacks();
        var result = this.genKeyPair();
        console.log("======================================");
        var str_private = result.getPrivate().getEncoded();
        var str_public = result.getPublic().getEncoded();
        console.log("公钥  |hex" + bytesToHex(str_public));
        console.log("私钥  |hex" + bytesToHex(str_private));
        return result;
    }
});
"""
# 小肩膀大佬通杀
j2 = """
//打印堆栈
function showStacks() {
    console.log(
        Java.use("android.util.Log")
            .getStackTraceString(
                Java.use("java.lang.Throwable").$new()
            )
    );
}
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
//输出base64格式数据
function toBase64(tag, data) {
    console.log(tag + " Base64: ", ByteString.of(data).base64());
}
//输出hex格式数据
function toHex(tag, data) {
    console.log(tag + " Hex: ", ByteString.of(data).hex());
}
//输出10格式数据
function toUtf8(tag, data) {
    console.log(tag + " Utf8: ", ByteString.of(data).utf8());
}
var messageDigest = Java.use("java.security.MessageDigest");
messageDigest.update.overload('byte').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.update('byte') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
messageDigest.update.overload('[B').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.update('[B') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " update data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.update('[B', 'int', 'int') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " update data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    console.log("start:", start);
    console.log("length:", length);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data, start, length);
}
messageDigest.digest.overload().implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.digest() is called!");
    var result = this.digest();
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " digest result";
    toHex(tag, result);
    toBase64(tag, result);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
messageDigest.digest.overload('[B').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.digest('[B') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " digest data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    var result = this.digest(data);
    var tags = algorithm + " digest result";
    toHex(tags, result);
    toBase64(tags, result);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " digest data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    var result = this.digest(data, start, length);
    var tags = algorithm + " digest result";
    toHex(tags, result);
    toBase64(tags, result);
    console.log("start:", start);
    console.log("length:", length);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
var mac = Java.use("javax.crypto.Mac");
mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init(key, AlgorithmParameterSpec);
}
mac.init.overload('java.security.Key').implementation = function (key) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.init('java.security.Key') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " init Key";
    var keyBytes = key.getEncoded();
    toUtf8(tag, keyBytes);
    toHex(tag, keyBytes);
    toBase64(tag, keyBytes);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init(key);
}
mac.update.overload('byte').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.update('byte') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.update('java.nio.ByteBuffer') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
mac.update.overload('[B').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.update('[B') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " update data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.update('[B', 'int', 'int') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " update data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    console.log("start:", start);
    console.log("length:", length);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data, start, length);
}
mac.doFinal.overload().implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Mac.doFinal() is called!");
    var result = this.doFinal();
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " doFinal result";
    toHex(tag, result);
    toBase64(tag, result);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
// DES/DESede/AES/RSA
var cipher = Java.use("javax.crypto.Cipher");
cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " init Key";
    var className = JSON.stringify(arguments[1]);
    if (className.indexOf("OpenSSLRSAPrivateKey") === -1) {
        var keyBytes = arguments[1].getEncoded();
        toUtf8(tag, keyBytes);
        toHex(tag, keyBytes);
        toBase64(tag, keyBytes);
    }
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " init Key";
    var keyBytes = arguments[1].getEncoded();
    toUtf8(tag, keyBytes);
    toHex(tag, keyBytes);
    toBase64(tag, keyBytes);
    var tags = algorithm + " init iv";
    var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));
    var ivBytes = iv.getIV();
    toUtf8(tags, ivBytes);
    toHex(tags, ivBytes);
    toBase64(tags, ivBytes);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.init.apply(this, arguments);
}
cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('[B', 'int') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload().implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal() is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('[B') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " doFinal data";
    var data = arguments[0];
    //修改一次
    toBase64(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    var result = this.doFinal.apply(this, arguments);
    var tags = algorithm + " doFinal result";
    toHex(tags, result);
    toBase64(tags, result);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
cipher.doFinal.overload('[B', 'int', 'int').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Cipher.doFinal('[B', 'int', 'int') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " doFinal data";
    var data = arguments[0];
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    var result = this.doFinal.apply(this, arguments);
    var tags = algorithm + " doFinal result";
    toHex(tags, result);
    toBase64(tags, result);
    console.log("arguments[1]:", arguments[1],);
    console.log("arguments[2]:", arguments[2]);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
//签名算法
var signature = Java.use("java.security.Signature");
signature.update.overload('byte').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Signature.update('byte') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
signature.update.overload('java.nio.ByteBuffer').implementation = function (data) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Signature.update('java.nio.ByteBuffer') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data);
}
signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Signature.update('[B', 'int', 'int') is called!");
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " update data";
    toUtf8(tag, data);
    toHex(tag, data);
    toBase64(tag, data);
    console.log("start:", start);
    console.log("length:", length);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.update(data, start, length);
}
signature.sign.overload('[B', 'int', 'int').implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Signature.sign('[B', 'int', 'int') is called!");
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return this.sign.apply(this, arguments);
}
signature.sign.overload().implementation = function () {
    console.log("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓");
    console.log("Signature.sign() is called!");
    var result = this.sign();
    var algorithm = this.getAlgorithm();
    var tag = algorithm + " sign result";
    toHex(tag, result);
    toBase64(tag, result);
    showStacks();
    console.log("↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑");
    return result;
}
"""
 
 
def on_message(message, data):
    if message['type'] == 'send':
        print("[*]{0}".format(message['payload']))
    else:
        print(message)
 

# 脚本控制app自动启动
pid = frida.get_remote_device().spawn('这里写应用包名')
process = frida.get_remote_device().attach(pid)
frida.get_remote_device().resume(pid)

#可选择j1或者j2算法,使用起来功能一样,看哪个用的更顺手
script = process.create_script(j2)
script.on('message', on_message)
script.load()
# 不让程序断掉
sys.stdin.read()

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值