普通视图

发现新文章,点击刷新页面。
昨天以前ITBOB'S BLOG

Python 数据分析三剑客之 Matplotlib(八):等高线/等值线图的绘制

作者 BOB
2020年5月12日 22:35

文章目录


Matplotlib 系列文章:


专栏:


推荐学习资料与网站:


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106066852未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

【1x00】等高线概念

参考百度百科,等高线概念总结如下:等高线指的是地形图上高程相等的相邻各点所连成的闭合曲线。把地面上海拔高度相同的点连成的闭合曲线,并垂直投影到一个水平面上,并按比例缩绘在图纸上,就得到等高线。等高线也可以看作是不同海拔高度的水平面与实际地面的交线,所以等高线是闭合曲线。在等高线上标注的数字为该等高线的海拔。

  • 位于同一等高线上的地面点,海拔高度相同。但海拔高度相同的点不一定位于同一条等高线上;
  • 在同一幅图内,除了陡崖以外,不同高程的等高线不能相交;
  • 在图廓内相邻等高线的高差一般是相同的,因此地面坡度与等高线之间的等高线平距成反比,等高线平距愈小,等高线排列越密,说明地面坡度越大;等高线平距愈大,等高线排列越稀,则说明地面坡度愈小;
  • 等高线是一条闭合的曲线,如果不能在同一幅内闭合,则必在相邻或者其他图幅内闭合。
  • 等高线经过山脊或山谷时改变方向,因此,山脊线或者山谷线应垂直于等高线转折点处的切线,即等高线与山脊线或者山谷线正交。

在 Matplotlib 等高线的绘制中,需要传递三个基本参数:某个点的 x、y 轴坐标以及其高度。

01

02

【2x00】理解 numpy.meshgrid()

numpy.meshgrid() 方法用于生成网格点坐标矩阵。

import numpy as npa = np.array([1, 2, 3])b = np.array([7, 8, 9])res = np.meshgrid(a, b)print(res)

输出结果:

[array([[1, 2, 3],       [1, 2, 3],       [1, 2, 3]]), array([[7, 7, 7],       [8, 8, 8],       [9, 9, 9]])]

给定两个数组,a[1, 2, 3]b[7, 8, 9],a 作为 x 轴数据,b 作为 y 轴数据,那么一共可以绘制出 9 个点: (1,7)、(1,8)、(1,9)、(2,7)、(2,8)、(2,9)、(3,7)、(3,8)、(3,9),而 numpy.meshgrid() 方法就是起这样的作用,返回的两个二维数组,横坐标矩阵 a 中的每个元素,与纵坐标矩阵 b 中对应位置元素,共同构成一个点的完整坐标。

因为在 matplotlib.pyplot.contour() 等高线绘制函数中接收的是二维坐标信息,所以在绘制等高线图之前要将原数据经过 numpy.meshgrid() 方法处理,也可以自己构建类似于上述的二维数组。

分割线

【3x00】绘制方法 matplotlib.pyplot.contour()

matplotlib.pyplot.contour() 方法可用于绘制等高线图。

基本语法:matplotlib.pyplot.contour(\*args, data=None, \*\*kwargs)

通用格式:matplotlib.pyplot.contour([X, Y,] Z, [levels], **kwargs)

基本参数:

参数描述
X, Y数组形式的点的 x 和 y 轴坐标,两者都必须是二维的,形状与 Z 相同
Z绘制轮廓的高度值,二维数组,每个元素是其对应点的高度
levels确定等高线的数目和位置,如果是整数 N,则使用 N 个数据间隔,即绘制 N+1 条等高线
如果是数组形式,则绘制指定的等高线。值必须按递增顺序排列

其他参数:

参数描述
colors等高线的颜色,颜色字符串或颜色序列
cmap等高线的颜色,字符串或者 Colormap
通常包含一系列的渐变色或其他颜色组合,取值参见【6x00】Colormap 取值
alpha透明度,介于0(透明)和1(不透明)之间
origin通过指定 Z[0,0] 的位置来确定 Z 的方向和确切位置,仅当未指定 X, Y 时才有意义
None:Z[0,0] 位于左下角的 X=0, Y=0 处
'lower':Z [0, 0] 位于左下角的 X = 0.5, Y = 0.5 处
'upper':Z[0,0] 位于左上角的 X=N+0.5, Y=0.5 处
'image':使用 rcParams[“image.origin”] = 'upper'的值
antialiased是否启用抗锯齿渲染,默认 True
linewidths等高线的线宽,如果是数字,则所有等高线都将使用此线宽
如果是序列,则将按指定的顺序以升序打印线宽
默认为 rcParams[“lines.linewidth”] = 1.5
linestyles等高线的样式,如果线条颜色为单色,则负等高线默认为虚线
'-' or 'solid', '--' or 'dashed', '-.' or 'dashdot' ':' or 'dotted', 'none' or ' ' or ''

分割线

【4x00】填充方法 matplotlib.pyplot.contourf()

matplotlib.pyplot.contourf() 方法与 matplotlib.pyplot.contour() 的区别在于:contourf() 会对等高线间的区域进行颜色填充(filled contours)。除此之外两者的函数签名和返回值都相同。

基本语法:matplotlib.pyplot.contourf(\*args, data=None, \*\*kwargs)

通用格式:matplotlib.pyplot.contour([X, Y,] Z, [levels], **kwargs)

基本参数:

参数描述
X, Y数组形式的点的 x 和 y 轴坐标,两者都必须是二维的,形状与 Z 相同
Z绘制轮廓的高度值,二维数组,每个元素是其对应点的高度
levels确定等高线的数目和位置,如果是整数 N,则使用 N 个数据间隔,即绘制 N+1 条等高线
如果是数组形式,则绘制指定的等高线。值必须按递增顺序排列

其他参数:

参数描述
colors等高线的填充颜色,颜色字符串或颜色序列
cmap等高线的填充颜色,字符串或者 Colormap
通常包含一系列的渐变色或其他颜色组合,取值参见【6x00】Colormap 取值
alpha透明度,介于0(透明)和1(不透明)之间
origin通过指定 Z[0,0] 的位置来确定 Z 的方向和确切位置,仅当未指定 X, Y 时才有意义
None:Z[0,0] 位于左下角的 X=0, Y=0 处
'lower':Z [0, 0] 位于左下角的 X = 0.5, Y = 0.5 处
'upper':Z[0,0] 位于左上角的 X=N+0.5, Y=0.5 处
'image':使用 rcParams[“image.origin”] = 'upper'的值
antialiased是否启用抗锯齿渲染,默认 True
linewidths等高线的线宽,如果是数字,则所有等高线都将使用此线宽
如果是序列,则将按指定的顺序以升序打印线宽
默认为 rcParams[“lines.linewidth”] = 1.5
linestyles等高线的样式,如果线条颜色为单色,则负等高线默认为虚线
'-' or 'solid', '--' or 'dashed', '-.' or 'dashdot' ':' or 'dotted', 'none' or ' ' or ''

分割线

【5x00】标记方法 matplotlib.pyplot.clabel()

matplotlib.pyplot.clabel(CS, \*args, \*\*kwargs) 方法可用于标记等高线图。

参数描述
CSContourSet(等高线集)对象,即 pyplot.contour() 返回的对象
levels需要标记的等高线集,数组类型,如果未指定则默认标记所有等高线
fontsize标记的字体大小,可选项:
'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'
colors标记的颜色,颜色字符串或颜色序列
inline是否在标签位置移除轮廓显示,bool 类型,默认 True
inline_spacing标签位置移除轮廓的宽度,float 类型,默认为 5
fmt标签的格式字符串。str 或 dict 类型,默认值为 %1.3f
rightside_up是否将标签旋转始终与水平面成正负90度,bool 类型,默认 True
use_clabeltext默认为 False,如果为 True,则使用 ClabelText 类(而不是 Text)创建标签
ClabelText 在绘图期间重新计算文本的旋转角度,如果轴的角度发生变化,则可以使用此功能

分割线


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106066852未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

【6x00】Colormap 取值

matplotlib.pyplot.contour()matplotlib.pyplot.contourf()cmap 参数用于设置等高线的颜色,取值通常为 Colormap 中的值,通常包含一系列的渐变色或其他颜色组合。具体参加下图。

官方文档:https://matplotlib.org/tutorials/colors/colormaps.html

03

分割线

【7x00】简单示例

import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = np.arange(-2.0, 2.0, 0.01)y = np.arange(-2.0, 2.0, 0.01)m, n = np.meshgrid(x, y)        # 生成网格点坐标矩阵# 指定一个函数用于计算每个点的高度,也可以直接使用二维数组储存每个点的高度def f(a, b):    return (1 - b ** 5 + a ** 5) * np.exp(-a ** 2 - b ** 2)# 绘制等高线图,8 个数据间隔,颜色为黑色plt.contour(m, n, f(m, n), 8, colors='k')plt.title('等高线图简单示例')plt.xlabel('x axis label')plt.ylabel('y axis label')plt.show()

04

分割线

【8x00】添加标记

matplotlib.pyplot.clabel() 方法用于给等高线添加标记。

import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = np.arange(-2.0, 2.0, 0.01)y = np.arange(-2.0, 2.0, 0.01)m, n = np.meshgrid(x, y)        # 生成网格点坐标矩阵# 指定一个函数用于计算每个点的高度,也可以直接使用二维数组储存每个点的高度def f(a, b):    return (1 - b ** 5 + a ** 5) * np.exp(-a ** 2 - b ** 2)# 绘制等高线图,8 个数据间隔,颜色为黑色C = plt.contour(m, n, f(m, n), 8, colors='k')# 添加标记,标记处不显示轮廓线,颜色为黑红绿蓝四种,保留两位小数plt.clabel(C, inline=True, colors=['k', 'r', 'g', 'b'], fmt='%1.2f')plt.title('等高线图添加标记示例')plt.xlabel('x axis label')plt.ylabel('y axis label')plt.show()

05

分割线

【9x00】轮廓线颜色和样式

matplotlib.pyplot.contour() 方法中,colors 参数即可为等高线轮廓设置颜色,可以是单色,也可以是一个颜色列表,linestyles 参数可以设置轮廓线样式,注意,如果线条颜色为单色,则负等高线(高度值为负)默认为虚线。

import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = np.arange(-2.0, 2.0, 0.01)y = np.arange(-2.0, 2.0, 0.01)m, n = np.meshgrid(x, y)        # 生成网格点坐标矩阵# 指定一个函数用于计算每个点的高度,也可以直接使用二维数组储存每个点的高度def f(a, b):    return (1 - b ** 5 + a ** 5) * np.exp(-a ** 2 - b ** 2)colors = ['k', 'r', 'g', 'b']# 绘制等高线图,8 个数据间隔,颜色为黑色,线条样式为 --C = plt.contour(m, n, f(m, n), 8, colors=colors, linestyles='--')# 添加标记,标记处不显示轮廓线,颜色为黑红绿蓝四种,保留两位小数plt.clabel(C, inline=True, colors=colors, fmt='%1.2f')plt.title('等高线图设置颜色/样式示例')plt.xlabel('x axis label')plt.ylabel('y axis label')plt.show()

06

如果想启用渐变色,则可以设置 cmap,取值参见【6x00】Colormap 取值colorbar() 方法可以显示颜色对照条。

import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = np.arange(-2.0, 2.0, 0.01)y = np.arange(-2.0, 2.0, 0.01)m, n = np.meshgrid(x, y)        # 生成网格点坐标矩阵# 指定一个函数用于计算每个点的高度,也可以直接使用二维数组储存每个点的高度def f(a, b):    return (1 - b ** 5 + a ** 5) * np.exp(-a ** 2 - b ** 2)# 绘制等高线图,8 个数据间隔,颜色为 plasmaC = plt.contour(m, n, f(m, n), 8, cmap='plasma')# 添加标记,标记处不显示轮廓线,颜色为黑色,保留两位小数plt.clabel(C, inline=True, colors='k', fmt='%1.2f')# 显示颜色条plt.colorbar()plt.title('等高线图设置渐变色示例')plt.xlabel('x axis label')plt.ylabel('y axis label')plt.show()

07

分割线

【10x00】颜色填充

matplotlib.pyplot.contourf() 方法用于对等高线之间的地方进行颜色填充。

import numpy as npimport matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = np.arange(-2.0, 2.0, 0.01)y = np.arange(-2.0, 2.0, 0.01)m, n = np.meshgrid(x, y)        # 生成网格点坐标矩阵# 指定一个函数用于计算每个点的高度,也可以直接使用二维数组储存每个点的高度def f(a, b):    return (1 - b ** 5 + a ** 5) * np.exp(-a ** 2 - b ** 2)# 绘制等高线图,8 个数据间隔,颜色为 plasmaplt.contourf(m, n, f(m, n), 8, cmap='plasma')C = plt.contour(m, n, f(m, n), 8, cmap='plasma')# 添加标记,标记处不显示轮廓线,颜色为黑色,保留两位小数plt.clabel(C, inline=True, colors='k', fmt='%1.2f')# 显示颜色条plt.colorbar()plt.title('等高线图颜色填充示例')plt.xlabel('x axis label')plt.ylabel('y axis label')plt.show()

08


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106066852未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

Python 数据分析三剑客之 Matplotlib(七):饼状图的绘制

作者 BOB
2020年5月12日 00:43

文章目录


Matplotlib 系列文章:


专栏:


推荐学习资料与网站:


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106025845未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

【1x00】方法描述

matplotlib.pyplot.pie() 方法用于绘制饼状图。

基本语法:

matplotlib.pyplot.pie(        x[, explode=None, labels=None, colors=None,        autopct=None, pctdistance=0.6, shadow=False,        labeldistance=1.1, startangle=None, radius=None,        counterclock=True, wedgeprops=None, textprops=None,        center=(0, 0), frame=False, rotatelabels=False, \*, data=None]        )
参数描述
x每个扇形块的大小,数组形式,大小单位是比例
explode指定对应扇形块脱离饼图的半径大小,数组形式,其中元素个数应该是 len(x)
labels每个扇形块上的文本标签,列表形式
labeldistance每个扇形块上的文本标签与扇形中心的距离,float 类型,默认 1.1
colors每个扇形块对应的颜色,数组形式
autopct用于计算每个扇形块所占比例,字符串或者函数类型
例如:autopct='%1.1f%%' 表示浮点数,保留一位小数,并添加百分比符号
pctdistance每个扇形块的中心与 autopct 生成的文本之间的距离,float 类型,默认 0.6
shadow是否为扇形添加阴影效果
startangle将饼图按照逆时针旋转指定的角度,float 类型
radius饼图的半径,如果是 None,则将被设置为 1,float 类型
counterclock是否按照逆时针对扇形图进行排列,bool 类型,默认 True
wedgeprops传递给绘制每个扇形图对象的参数,字典形式,参数值参见 Wedge
例如:wedgeprops = {'linewidth': 3} 设置扇形边框线宽度为 3
textprops传递给文本对象的参数,字典形式
例如:textprops={'color': 'r', 'fontsize': 15} 设置文字为红色,大小为15
center饼图圆心在画布上是坐标,默认 (0, 0)
frame是否显示 x, y 坐标轴外框,默认 False
rotatelabels是否按照角度进行调整每块饼的 label 文本标签,默认 False

【2x00】简单示例

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Golang', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']# 指定4个扇区所占比例以及扇区的颜色,扇区文本标签距离扇区中心1.1plt.pie(x, labels=labels, colors=colors, labeldistance=1.1)plt.title('饼状图简单示例')plt.show()

01

【3x00】按角度调整扇形标签

rotatelabels 属性可以设置是否按照角度调整每块饼的 label(标签)显示方式。

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Go', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']# 指定4个扇区所占比例以及扇区的颜色,扇区文本标签距离扇区中心1.1,按角度调整 labelsplt.pie(x, labels=labels, colors=colors, labeldistance=1.1, rotatelabels=True)plt.title('饼状图按角度调整 labels 示例')plt.show()

02

【4x00】显示图例

与前面文章中绘制线性图、散点图、条形图一样,调用 matplotlib.pyplot.legend() 方法可绘制图例,该方法的参数解释参见前文《Python 数据分析三剑客之 Matplotlib(三):图例 / LaTeX / 刻度 / 子图 / 补丁等基本图像属性》

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Go', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']plt.pie(x, labels=labels, colors=colors, labeldistance=1.1)plt.title('饼状图显示图例示例')plt.legend(bbox_to_anchor=(1, 1))plt.show()

03


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106025845未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

【5x00】突出显示扇形块

explode 参数可以实现突出显示某一块扇区,接收数组形式的参数,这个数组中的元素个数应该是 len(x),即和扇区块的数量相同。

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Golang', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']# 指定第一个扇区块脱离饼图的半径大小为0.3,其它扇区不脱离plt.pie(x, labels=labels, colors=colors, labeldistance=1.1, explode=[0.3, 0, 0, 0])plt.title('饼状图突出显示扇形块示例')plt.legend(bbox_to_anchor=(1, 1))plt.show()

04

【6x00】显示各扇区所占百分比

autopct 参数可用于计算每个扇形块所占比例,接收字符串或者函数类型,例如:autopct='%1.1f%%' 表示浮点数,保留一位小数,并添加百分比符号。pctdistance 参数用于调整每个扇形块的中心与 autopct 生成的文本之间的距离,float 类型,默认 0.6。

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Golang', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']plt.pie(    x,                        # 每个扇形块所占比例    labels=labels,            # 扇形块文本标签    colors=colors,            # 扇形块颜色    labeldistance=1.1,        # 扇形块标签距离中心的距离    explode=[0.3, 0, 0, 0],   # 第一个扇形块突出显示    autopct='%1.1f%%',        # 显示百分比,保留一位小数    pctdistance=0.5           # 百分比文本距离饼状图中心的距离)plt.title('饼状图显示各扇区所占百分比示例')plt.legend(bbox_to_anchor=(1, 1))  # 显示图例plt.show()

05

【7x00】旋转饼状图

startangle 参数可以选择饼状图,改变饼状图放置的角度。注意是按照逆时针旋转。

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Golang', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']plt.pie(    x,                        # 每个扇形块所占比例    labels=labels,            # 扇形块文本标签    colors=colors,            # 扇形块颜色    labeldistance=1.1,        # 扇形块标签距离中心的距离    explode=[0.3, 0, 0, 0],   # 第一个扇形块突出显示    autopct='%1.1f%%',        # 显示百分比,保留一位小数    pctdistance=0.5,          # 百分比文本距离饼状图中心的距离    startangle=-90            # 逆时针旋转-90°,即顺时针旋转90°)plt.title('饼状图旋转角度示例')plt.legend(bbox_to_anchor=(1, 1))  # 显示图例plt.show()

06

【8x00】自定义每个扇形和文字属性

wedgeprops 参数以字典形式为每个扇形添加自定义属性,例如:wedgeprops = {'linewidth': 3} 设置扇形边框线宽度为 3,更多其他参数值参见 Wedge

textprops 参数同样以字典形式为文本对象添加自定义属性,例如:textprops={'color': 'r', 'fontsize': 15} 设置文字为红色,大小为15,更多其他参数值参见 Text

import matplotlib.pyplot as pltplt.rcParams['font.sans-serif'] = ['Microsoft YaHei']x = [10, 30, 45, 15]labels = ['Java', 'Golang', 'Python', 'C++']colors = ['red', 'yellow', 'blue', 'green']plt.pie(    x,                           # 每个扇形块所占比例    labels=labels,               # 扇形块文本标签    colors=colors,               # 扇形块颜色    labeldistance=1.1,           # 扇形块标签距离中心的距离    explode=[0.3, 0, 0, 0],      # 第一个扇形块突出显示    autopct='%1.1f%%',           # 显示百分比,保留一位小数    pctdistance=0.6,             # 百分比文本距离饼状图中心的距离    shadow=True,                 # 显示阴影效果    wedgeprops={                 # 为每个扇形添加属性        'width': 0.7,            # 扇形宽度0.7        'edgecolor': '#98F5FF',  # 扇形边缘线颜色        'linewidth': 3           # 扇形边缘线宽度    },    textprops={                  # 为文字添加属性        'fontsize': 13,          # 文字大小        'fontweight': 'bold',    # 文字粗细        'color': 'k'             # 文字颜色,黑色    })plt.title('饼状图自定义每个扇形和文字属性示例', fontweight='bold')plt.legend(bbox_to_anchor=(1, 1), borderpad=0.6)  # 显示图例plt.show()

07


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 ITBOB。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/106025845未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

CTF&爬虫:掌握这些特征,一秒识别密文加密方式

作者 BOB
2022年1月13日 17:40

encryption_algorithm

欢迎加入爬虫逆向微信交流群:添加微信 IT-BOB(备注交流群)

文章目录



前言

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

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

  • 编码:Base 系列、Unicode、Escape、URL、Hex;
  • 算法:MD5、SHA 系列、HMAC 系列、RSA、AES、DES、3DES、RC4、Rabbit、SM 系列;
  • 混淆:Obfuscator、JJEncode、AAEncode、JSFuck、Jother、Brainfuck、Ook!、Trivial brainfuck substitution;
  • 其他:恺撒密码、栅栏密码、猪圈密码、摩斯密码、培根密码、维吉尼亚密码、与佛论禅、当铺密码。

PS:常见加密算法原理以及在 Python 和 JavaScript 中的实现方法可参见以前的文章:爬虫常见加密解密算法总结

编码系列

Base 系列编码

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 表情组成。

示例:

编码类型 示例一 示例二
明文 01234567890 administrators
Base16 3031323334353637383930 61646D696E6973747261746F7273
Base32 GAYTEMZUGU3DOOBZGA====== MFSG22LONFZXI4TBORXXE4Y=
Base58 cX8j8pvGzppMKVb BNF5dFLUTN5XwM1yLoF
Base64 MDEyMzQ1Njc4OTA= YWRtaW5pc3RyYXRvcnM=
Base85 0JP==1c70M3&rY @:X4hDJ=06Eaa'.EcV
Base100 🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐧 👘👛👤👠👥👠👪👫👩👘👫👦👩👪

Unicode 编码

Unicode 又称为统一码、万国码、单一码,是一种在计算机上使用的字符编码。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。其主要特征如下:

  • \u&#&#x 开头,后面是数字加字母组合

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

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

Escape 编码

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

  • %u 开头,后面是数字加字母组合
编码类型 示例一 示例二
明文 爬虫 我爱Python
Escape %u722C%u866B %u6211%u7231Python

URL / Hex 编码

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

  • % 开头,后面是数字加字母组合
编码类型 示例
明文 https://www.kuaidaili.com/
Unicode https://%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

加密算法

MD5

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

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

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

  • 0123456789ABCDEF0123456789abcdef
  • 1732584193-271733879-1732584194271733878

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

示例:

编码类型 示例一 示例二
明文 123456 admin
MD5(16位小写) 49ba59abbe56e057 7a57a5a743894a0e
MD5(16位大写) 49BA59ABBE56E057 7A57A5A743894A0E
MD5(32位小写) e10adc3949ba59abbe56e057f20f883e 21232f297a57a5a743894a0e4a801fc3
MD5(32位大写) E10ADC3949BA59ABBE56E057F20F883E 21232F297A57A5A743894A0E4A801FC3

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-1 7c4a8d09ca3762af61e59520943dc26494f8941b
SHA-256 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92
SHA3-256 c888c9ce9e098d5864d3ded6ebcc140a12142263bace3a23a36f9905f12bd64a

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-MD5 432bb95bb00005ddce4a1c757488ed95
HMAC-SHA-1 37a04076b7736c44460d330ee0d00014428b175e
HMAC-SHA-256 50cb1345366df11140fb91b43caaf69627e3f5529705ddf6b0d0cae67986e585
HMAC-SHA3-256 b808ed9f66436e89fba527a01d1d6044318fea8599d9f39bfb6bec4843964bf3

RSA

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

  • 搜索关键词 new JSEncrypt()JSEncrypt 等,一般会使用 JSEncrypt 库,会有 new 一个实例对象的操作;

  • 搜索关键词 setPublicKeysetKeysetPrivateKeygetPublicKey 等,一般实现的代码里都含有设置密钥的过程;

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

    私钥长度(Base64) 公钥长度(Base64) 明文长度 密文长度
    428 128 1~53 88
    812 216 1~117 172
    1588 392 1~245 344

AES、DES、3DES、RC4、Rabbit 等

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("ITBOB"))console.log(AESDecrypt("r78lMXzImDRcDx9ADakCmg=="))

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

  • CryptoJScrypto-jsivmodepaddingcreateEncryptorcreateDecryptor

  • ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=0xffffffff0xffff

定位到 CryptoJS 后,观察加密方法,比如 AES 就是 CryptoJS.AES.encrypt,DES 就是 CryptoJS.DES.encrypt,3DES 就是 CryptoJS.TripleDES.encrypt,注意他的 iv、mode、padding,拿下来就可以本地复现了。

SM 系列

SM 代表商密,即商业密码,是我国发布的一系列国产加密算法,SM 系列包括:SM1、SM2、SM3 、SM4、SM7、SM9,其中 SM1 和 SM7 的算法不公开,SM 系列算法在我国一些 gov 网站上有应用,有关国产加密算法前期文章有介绍:《认识 SM1/SM2/SM3/SM4/SM7/SM9/ZUC 国密算法》,本文不再赘述。

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

  • SM2SM3SM4
  • FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF
  • FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC
  • 28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93
  • abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
  • getPublicKeyFromPrivateKeydoEncryptdoDecryptdoSignature

混淆系列

Obfuscator

Obfuscator 就是混淆的意思,简称 OB 混淆,实战可参考以前的文章:【JS 逆向百例】W店UA,OB反混淆,抓包替换CORS跨域错误分析,OB 混淆具有以下特征:

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

一段正常的代码如下:

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

经过 OB 混淆后的结果:

function _0x3f26() {    var _0x2dad75 = ['5881925kTCKCP', 'Hello\x20World!', '600mDvfGa', '699564jYNxbu', '1083271cEvuvT', 'log', '18sKjcFY', '214857eMgFSU', '77856FUKcuE', '736425OzpdFI', '737172JqcGMg'];    _0x3f26 = function () {        return _0x2dad75;    };    return _0x3f26();}(function (_0x307c88, _0x4f8223) {    var _0x32807d = _0x1fe9, _0x330c58 = _0x307c88();    while (!![]) {        try {            var _0x5d6354 = parseInt(_0x32807d(0x6f)) / 0x1 + parseInt(_0x32807d(0x6e)) / 0x2 + parseInt(_0x32807d(0x70)) / 0x3 + -parseInt(_0x32807d(0x69)) / 0x4 + parseInt(_0x32807d(0x71)) / 0x5 + parseInt(_0x32807d(0x6c)) / 0x6 * (parseInt(_0x32807d(0x6a)) / 0x7) + -parseInt(_0x32807d(0x73)) / 0x8 * (parseInt(_0x32807d(0x6d)) / 0x9);            if (_0x5d6354 === _0x4f8223) break; else _0x330c58['push'](_0x330c58['shift']());        } catch (_0x3f18e4) {            _0x330c58['push'](_0x330c58['shift']());        }    }}(_0x3f26, 0xaa023));function _0x1fe9(_0xa907e7, _0x410a46) {    var _0x3f261f = _0x3f26();    return _0x1fe9 = function (_0x1fe950, _0x5a08da) {        _0x1fe950 = _0x1fe950 - 0x69;        var _0x82a06 = _0x3f261f[_0x1fe950];        return _0x82a06;    }, _0x1fe9(_0xa907e7, _0x410a46);}function hi() {    var _0x12a222 = _0x1fe9;    console[_0x12a222(0x6b)](_0x12a222(0x72));}hi();

JJEncode

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

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

正常的一段 JS 代码:

alert("Hello, JavaScript" )

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

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

AAEncode

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

  • 仅由日式表情符号组成

正常的一段 JS 代码:

alert("Hello, JavaScript")

经过 AAEncode 混淆之后的代码:

゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) - (゚Θ゚))+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

JSFuck

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

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

正常的一段 JS 代码:

alert(1)

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

[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]])

Jother

Jother 混淆和 JSFuck 有点儿类似,唯一的区别就是密文比 JSFuck 多了 {},其解密方式和 JSFuck 是一样的,Jother 混淆现在不太常见了,也很难找到在线混淆之类的工具了,原作者有个在线页面也关闭了,不过仍然可以了解一下,Jother 混淆具有以下特征:

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

正常的一段代码:

function anonymous() {return location}

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

[][(![]+[])[!![]+!![]+!![]]+({}+[])[+!![]]+(!![]+[])[+!![]]+(!![]+[])[+[]]][({}+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+({}[[]]+[])[+!![]]+(![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+(!![]+[])[+!![]]+({}[[]]+[])[+[]]+({}+[])[!![]+!![]+!![]+!![]+!![]]+(!![]+[])[+[]]+({}+[])[+!![]]+(!![]+[])[+!![]]]((!![]+[])[+!![]]+(!![]+[])[!![]+!![]+!![]]+(!![]+[])[+[]]+({}[[]]+[])[+[]]+(!![]+[])[+!![]]+({}[[]]+[])[+!![]]+({}+[])[!![]+!![]+!![]+!![]+!![]+!![]+!![]]+(![]+[])[!![]+!![]]+({}+[])[+!![]]+({}+[])[!![]+!![]+!![]+!![]+!![]]+(![]+[])[+!![]]+(!![]+[])[+[]]+({}[[]]+[])[!![]+!![]+!![]+!![]+!![]]+({}+[])[+!![]]+({}[[]]+[])[+!![]])()

Brainfuck

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

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

正常的一段代码:

alert("Hello, Brainfuck")

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

--[----->+<]>-----.+++++++++++.-------.+++++++++++++.++.+[--->+<]>+.------.++[->++<]>.-[->+++++<]>++.+++++++..+++.[->+++++<]>+.------------.+[->++<]>.---[----->+<]>-.+++[->+++<]>++.++++++++.+++++.--------.-[--->+<]>--.+[->+++<]>+.++++++++.+[++>---<]>.+++++++.

Ook!

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

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

正常的一段代码:

alert("Hello, Ook!")

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

Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook? Ook. Ook? Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook? Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook? Ook! Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook? Ook. Ook? Ook! Ook. Ook? Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook! Ook. Ook. Ook. Ook! Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook. Ook! Ook. Ook? Ook.

Trivial brainfuck substitution

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

其他

恺撒密码

恺撒密码(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

猪圈密码

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

01.gif

摩斯密码

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

26个字母的摩斯密码表

字符 摩斯码 字符 摩斯码 字符 摩斯码 字符 摩斯码
A .━ B ━ ... C ━ .━ . D ━ ..
E F ..━ . G ━ ━ . H ....
I .. J .━ ━ ━ K ━ .━ L .━ ..
M ━ ━ N ━ . O ━ ━ ━ P .━ ━ .
Q ━ ━ .━ R .━ . S ... T
U ..━ 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,并且五个一组,那么就是培根加密了。

第一种方式:

字符 培根密码 字符 培根密码 字符 培根密码 字符 培根密码
A aaaaa H aabbb O abbba V babab
B aaaab I abaaa P abbbb W babba
C aaaba J abaab Q baaaa X babbb
D aaabb K ababa R baaab Y bbaaa
E aabaa L ababb S baaba Z bbaab
F aabab M abbaa T baabb
G aabba N abbab U babaa

第二种方式:

字符 培根密码 字符 培根密码 字符 培根密码 字符 培根密码
a AAAAA h AABBB p ABBBA x BABAB
b AAAAB i-j ABAAA q ABBBB y BABBA
c AAABA k ABAAB r BAAAA z BABBB
d AAABB l ABABA s BAAAB
e AABAA m ABABB t BAABA
f AABAB n ABBAA u-v BAABB
g AABBA o ABBAB w BABAA

示例:

  • 明文:kuaidaili
  • 密文:ABABABABAAAAAAAABAAAAAABBAAAAAABAAAABABBABAAA

维吉尼亚密码

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

02.png

示例:

  • 明文: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

示例:

  • 明文:爬虫
  • 密文:佛曰:俱曳栗羅。諳故大多罰顛冥有諳姪帝罰知俱薩心俱智伊

当铺密码

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

示例:

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

认识 SM1/SM2/SM3/SM4/SM7/SM9/ZUC 国密算法

作者 BOB
2021年11月4日 12:39

sm_algorithm

欢迎加入爬虫逆向微信交流群:添加微信 IT-BOB(备注交流群)

文章目录



简介

国密即国家密码局认定的国产加密算法,爬虫工程师在做 JS 逆向的时候,会遇到各种各样的加密算法,其中 RSA、AES、SHA 等算法是最常见的,这些算法都是国外的,在我以前的文章里也有介绍:《史上最全总结!爬虫常见加密解密算法》

事实上从 2010 年开始,我国国家密码管理局就已经开始陆续发布了一系列国产加密算法,这其中就包括 SM1、SM2、SM3 、SM4、SM7、SM9、ZUC(祖冲之加密算法)等,SM 代表商密,即商业密码,是指用于商业的、不涉及国家秘密的密码技术。SM1 和 SM7 的算法不公开,其余算法都已成为 ISO/IEC 国际标准。

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


算法概述

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

算法详解

SM1 分组加密算法

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

椭圆曲线公钥加密算法

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

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

杂凑算法

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

分组加密算法

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

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

分组加密算法

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

标识加密算法

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

祖冲之算法

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


编程语言实现

Python 语言实现

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

其中 gmssl-python 是 gmssl 的改进版,gmssl-python 新增支持了 SM9 算法,不过截止本文编写时,gmssl-python 并未发布 pypi,也未 PR 到 gmssl,使用 pip install gmssl 安装的 gmssl 不支持 SM9 算法。若要使用 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,目前支持 SM2、SM3 和 SM4,需要注意的是,SM2 非对称加密的结果由 C1、C2、C3 三部分组成,其中 C1 是生成随机数的计算出的椭圆曲线点,C2 是密文数据,C3 是 SM3 的摘要值,最开始的国密标准的结果是按 C1C2C3 顺序的,新标准的是按 C1C3C2 顺序存放的,sm-crypto 支持设置 cipherMode,也就是 C1C2C3 的排列顺序。

sm-crypto:https://www.npmjs.com/package/sm-crypto

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

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

const sm2 = require('sm-crypto').sm2// 1 - C1C3C2,0 - C1C2C3,默认为1const 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

其他语言实现以及参考资料


附:GM/T 密码行业标准

Python 数据结构之栈的实现

作者 BOB
2020年11月30日 14:48

文章目录


栈的概念

栈(stack)又名堆栈,栈是一种线性数据结构,用先进后出或者是后进先出的方式存储数据,栈中数据的插入删除操作都是在栈的顶端进行,这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

01


栈的特点

元素后进先出(Last in First Out,LIFO)


栈的操作

  • push(item):进栈(向栈顶添加元素)
  • pop():出栈(删除栈顶元素)
  • top():查看栈顶元素
  • empty():判断栈是否为空

Python 实现栈

栈并不是 Python 的内建类型,在必要的时候可以使用列表来模拟基于数组的栈。如果将列表的末尾看作是栈的顶,列表方法 append() 就是将元素压入到栈中(进栈),而列表方法 pop() 会删除并返回栈顶的元素(出栈),列表索引的方式 arr[-1] 可以查看栈顶元素。具体代码实现如下:

class Stack:    def __init__(self):        self.stack = []    def push(self, item):        self.stack.append(item)    def pop(self):        if self.empty():            return None        else:            return self.stack.pop()    def top(self):        if self.empty():            return None        else:            return self.stack[-1]    def empty(self):        return len(self.stack) == 0

栈的简单应用:括号匹配问题

问题描述:

给定一个字符串,字符串中只包含小括号 ()、中括号 []、大括号 {},求该字符串中的括号是否匹配。匹配规则:成对出现或者左右对称出现,例如:

()[]{}:匹配;{[()]}:匹配;({}]:不匹配;()]:不匹配;({)}:不匹配

通过栈来解决:

有字符串 ()[{}],依次取每个括号,只要是左括号就进栈,只要是右括号就判断栈顶是否为对应的左括号,具体步骤如下:

  • 遇到左小括号 (,执行进栈操作;
  • 遇到右小括号 ),判断此时栈顶是否为左小括号 (,是则让左小括号 ( 出栈,此时栈为空;
  • 遇到左中括号 [,执行进栈操作;
  • 遇到左大括号 {,执行进栈操作;
  • 遇到右大括号 },判断此时栈顶是否为左大括号 {,是则让左大括号 { 出栈,此时栈为空;
  • 遇到右中括号 ],判断此时栈顶是否为左中括号 [,是则让左中括号 [ 出栈,此时栈为空;
  • 判断最终的栈是否为空,是则表示匹配,不是则表示不匹配。其中第 ② ⑤ ⑥ 步中,若判断为不是,则直接表示不匹配。

Python 代码实现:

class Stack:    def __init__(self):        self.stack = []    def push(self, item):        self.stack.append(item)    def pop(self):        if self.empty():            return None        else:            return self.stack.pop()    def top(self):        if self.empty():            return None        else:            return self.stack[-1]    def empty(self):        return len(self.stack) == 0def brackets_match(s):    match_dict = {'}': '{', ']': "[", ')': '('}    stack = Stack()    for ch in s:        if ch in ['(', '[', '{']:    # 如果为左括号,则执行进栈操作            stack.push(ch)        else:                        # 如果为右括号            if stack.empty():        # 如果栈为空,则不匹配,即多了一个右括号,没有左括号匹配                return False            elif stack.top() == match_dict[ch]:  # 如果栈顶的元素为对应的左括号,则让栈顶出栈                stack.pop()            else:                    # 如果栈顶元素不是对应的左括号,则不匹配                return False    if stack.empty():                # 最后的栈如果为空,则匹配,否则不匹配        return True    else:        return Falseprint(brackets_match('[{()}(){()}[]({}){}]'))print(brackets_match('()[{}]'))print(brackets_match('({)}'))print(brackets_match('[]}'))

输出结果:

TrueTrueFalseFalse

栈的简单应用:倒序输出一组元素

把元素存入栈,再顺序取出:

class Stack:    def __init__(self):        self.stack = []    def push(self, item):        self.stack.append(item)    def pop(self):        if self.empty():            return None        else:            return self.stack.pop()    def top(self):        if self.empty():            return None        else:            return self.stack[-1]    def empty(self):        return len(self.stack) == 0def reverse_list(s):    stack = Stack()    for ch in s:        stack.push(ch)    new_list = []    while not stack.empty():        new_list.append(stack.pop())    return new_listprint(reverse_list(['A', 'B', 'C', 'D', 'E']))

输出结果:

['E', 'D', 'C', 'B', 'A']

Python 算法之递归与尾递归,斐波那契数列以及汉诺塔的实现

作者 BOB
2020年10月28日 22:05

文章目录


递归概念

递归:程序调用自身的编程技巧称为递归( recursion)。用一种通俗的话来说就是自己调用自己,它通常把一个大型复杂的问题层层转化为一个与原问题相似的、但是规模较小的问题来求解,当问题小到一定规模的时候,需要一个递归出口返回。递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。

递归函数:在编程语言中,函数直接或间接调用函数本身,则该函数称为递归函数;在数学上的定义如下:对于某一函数 f(x)f(x),其定义域是集合 A,那么若对于 A 集合中的某一个值 X0X_0,其函数值 f(x0)f(x_0)f(f(x0))f(f(x_0)) 决定,那么就称 f(x)f(x) 为递归函数。


递归要素

  • 递归必须包含一个基本的出口(结束条件),否则就会无限递归,最终导致栈溢出;

  • 递归必须包含一个可以分解的问题,例如要想求得 fact(n)fact(n),就需要用 nfact(n1)n * fact(n-1)

  • 递归必须必须要向着递归出口靠近,例如每次递归调用都会 n1n-1,向着递归出口 n==0n == 0 靠近。


递归与迭代的区别

  • 递归(recursion):递归则是一步一步往前递推,直到递归基础,寻找一条路径, 然后再由前向后计算。(A调用A)

  • 迭代(iteration):迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,因此迭代是从前往后计算的。(A重复调用B)


示例一:阶乘

一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且 0 的阶乘为 1。即 n!=1×2×3×...×(n1)×nn!=1×2×3×...×(n-1)×n,以递归方式定义:n!=(n1)!×nn!=(n-1)!×n

def factorial(n):    if n == 0:        return 1    else:        return n * factorial(n-1)

示例二:斐波那契数列

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家莱昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”。

有一个数列:0、1、1、2、3、5、8、13、21、34、55、89…,这个数列从第3项开始,每一项都等于前两项之和。以递推的方法定义:F(n)=F(n1)+F(n2)(n3,nN)F(n)=F(n - 1)+F(n - 2) (n ≥ 3, n ∈ N^*)

def fibonacc(n):    if n == 1 or n == 2:        return 1    else:        return fibonacc(n-1) + fibonacc(n-2)

以上方法的时间复杂度为O(2n)O(2^n),稍微大一点的数都会算很久,有一个简单的解决方案,使用 lru_cache 缓存装饰器,缓存一些中间结果:

from functools import lru_cache# 缓存斐波那契函数已经计算出的结果,最多占用1024字节内存@lru_cache(maxsize=1024)def fibonacc(n):    if n == 1 or n == 2:        return 1    else:        return fibonacc(n-1) + fibonacc(n-2)

另外还有更加节省时间和空间的方法:

def fibonacc(n, current=0, next=1):    if n == 0:        return current    else:        return fibonacc(n-1, next, current+next)

示例三:汉诺塔问题

汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。64片黄金圆盘移动完毕之日,就是世界毁灭之时。

01汉诺塔

对于 n 个盘子,移动步骤如下:

  • 把 n-1 个盘子由 A 经过 C 移动到 B
  • 把最后一个盘子移动到 C
  • 把 n-1 个盘子由 B 经过 A 移动到 C

02汉诺塔

递归代码实现:

def hanoi(n, a, b, c):                                # n 个盘子,a,b,c三个柱子    if n > 0:        hanoi(n-1, a, c, b)                           # 把 n-1 个盘子由 a 经过 c 移动到 b        print('moving from {0} to {1}'.format(a, c))  # 把最后一个盘子移动到 c        hanoi(n-1, b, a, c)                           # 把 n-1 个盘子由 b 经过 a 移动到 c

示例:

def hanoi(n, a, b, c):    if n > 0:        hanoi(n-1, a, c, b)        print('moving from {0} to {1}'.format(a, c))        hanoi(n-1, b, a, c)hanoi(3, 'A', 'B', 'C')
moving from A to Cmoving from A to Bmoving from C to Bmoving from A to Cmoving from B to Amoving from B to Cmoving from A to C

尾递归

如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。通俗来讲就是递归调用放在了函数的最后。

# 一般递归def func(n):    if n > 0:        func(n-1)        print(n)# 一般递归def func(n):    if n > 0:        return func(n-1) + n# 尾递归def func(n):    a = n    if n > 0:        a += 1        print(a, n)        return func(n-1)

对于普通的递归,每一级递归都产生了新的局部变量,必须创建新的调用栈,随着递归深度的增加,创建的栈越来越多,容易造成爆栈。

def normal_recursion(n):    if n == 1:        return 1    else:        return n + normal_recursion(n-1)

normal_recursion(5) 执行:

normal_recursion(5)5 + normal_recursion(4)5 + 4 + normal_recursion(3)5 + 4 + 3 + normal_recursion(2)5 + 4 + 3 + 2 + normal_recursion(1)5 + 4 + 3 + 35 + 4 + 65 + 1015

尾递归基于函数的尾调用,每一级调用直接返回递归函数更新调用栈,没有新局部变量的产生,类似迭代的实现。

def tail_recursion(n, total=0):    if n == 0:        return total    else:        return tail_recursion(n-1, total+n)

normal_recursion(5) 执行:

tail_recursion(5, 0)tail_recursion(4, 5)tail_recursion(3, 9)tail_recursion(2, 12)tail_recursion(1, 14)tail_recursion(0, 15)15

在 Python,Java,Pascal 等语言中是无法实现尾递归优化的,所以采用了 for,while,goto 等特殊结构以迭代的方式来代替尾递归。


Python 中尾递归的解决方案

使用普通的递归来实现斐波那契数列的计算,代码段如下:

def fibonacc(n, current=0, next=1):    if n == 0:        return current    else:        return fibonacc(n-1, next, current+next)a = fibonacc(1000)print(a)

此时会报错,因为超过了最大递归深度(默认深度900-1000左右):

Traceback (most recent call last):  File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 57, in <module>    a = fibonacc(1000)  File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonacc    return fibonacc(n-1, next, current+next)  File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonacc    return fibonacc(n-1, next, current+next)  File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 47, in fibonacc    return fibonacc(n-1, next, current+next)  [Previous line repeated 995 more times]  File "F:/PycharmProjects/algorithm/fibonacc_test.py", line 44, in fibonacc    if n == 0:RecursionError: maximum recursion depth exceeded in comparison

如果是递归深度不是很大的情况,可以手动重设递归深度来解决:

import syssys.setrecursionlimit(10000)  # 递归深度设置为 10000

如果递归深度非常大,那么就可以采用尾递归优化,但是 Python 官方是并不支持尾递归的(不知道为啥),然而这难不到广大的程序员们,早在 2006 年 Crutcher Dunnavant 就想出了一个解决办法,实现一个 tail_call_optimized 装饰器,原文链接:https://code.activestate.com/recipes/474088/,原代码是 Python 2.4 实现的,用 Python 3.x 实现如下:

# This program shows off a python decorator# which implements tail call optimization. It# does this by throwing an exception if it is# it's own grandparent, and catching such# exceptions to recall the stack.import sysclass TailRecurseException(BaseException):    def __init__(self, args, kwargs):        self.args = args        self.kwargs = kwargsdef tail_call_optimized(g):    """    This function decorates a function with tail call    optimization. It does this by throwing an exception    if it is it's own grandparent, and catching such    exceptions to fake the tail call optimization.    This function fails if the decorated5    function recurses in a non-tail context.    """    def func(*args, **kwargs):        f = sys._getframe()        if f.f_back and f.f_back.f_back and f.f_back.f_back.f_code == f.f_code:            raise TailRecurseException(args, kwargs)        else:            while 1:                try:                    return g(*args, **kwargs)                except TailRecurseException as e:                    args = e.args                    kwargs = e.kwargs    func.__doc__ = g.__doc__    return func

使用该装饰器再来实现比较大的斐波那契数列的计算:

@tail_call_optimizeddef fibonacc(n, current=0, next=1):    if n == 0:        return current    else:        return fibonacc(n-1, next, current+next)a = fibonacc(1000)print(a)

输出结果:

43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875

tail_call_optimized 实现尾递归优化的原理:当递归函数被该装饰器修饰后,递归调用在装饰器while循环内部进行,每当产生新的递归调用栈帧时,f.f_back.f_back.f_code == f.f_code: 就捕获当前尾调用函数的参数,并抛出异常,从而销毁递归栈并使用捕获的参数手动调用递归函数,所以递归的过程中始终只存在一个栈帧对象,达到优化的目的。


这里是一段物理防爬虫文本,请读者忽略。本文原创首发于 CSDN,作者 TRHX•鲍勃。博客首页:https://itrhx.blog.csdn.net/本文链接:https://itrhx.blog.csdn.net/article/details/109322815未经授权,禁止转载!恶意转载,后果自负!尊重原创,远离剽窃!

百度滑块、点选、旋转验证码 v1、v2 逆向分析

作者 BOB
2023年4月6日 20:20

captcha_reverse

欢迎加入爬虫逆向微信交流群:添加微信 IT-BOB(备注交流群)

文章目录



声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请通过邮件 admin@itbob.cn 联系我立即删除!

逆向目标

  • 目标:百度滑块验证码、点选验证码、旋转验证码,v1、v2 逆向分析
  • v1 旋转验证码:
aHR0cHM6Ly93YXBwYXNzLmJhaWR1LmNvbS9zdGF0aWMvY2FwdGNoYS90dXhpbmcuaHRtbD9haz0zM2M0ODg4NGI3ZGY4M2Q0MjMwZTA3Y2JjZDBkMDdmZCZiYWNrdXJsPWh0dHBzJTNBJTJGJTJGYWlxaWNoYS5iYWlkdS5jb20mdGltZXN0YW1wPTE2MzE0MzQ0MjUmc2lnbmF0dXJlPWM2ODRhODJiNzk4MjAyOTg3NWJmZDhlMGE2NjBiNzdm
  • v2 旋转验证码:
aHR0cHM6Ly93YXBwYXNzLmJhaWR1LmNvbS9zdGF0aWMvY2FwdGNoYS90dXhpbmcuaHRtbD8mYWs9YzI3YmJjODlhZmNhMDQ2MzY1MGFjOWJkZTY4ZWJlMDY=
  • v2 滑块验证码:
aHR0cHM6Ly93YXBwYXNzLmJhaWR1LmNvbS9zdGF0aWMvY2FwdGNoYS90dXhpbmcuaHRtbD8mYW1wO2FrPWMyN2JiYzg5YWZjYTA0NjM2NTBhYzliZGU2OGViZTA2
  • v2 点选验证码:
aHR0cHM6Ly93YXBwYXNzLmJhaWR1LmNvbS92Ni9nZXRQYXNz

PS:v1、v2 是作者自己为了区分而命名的版本号,主要依据是核心 JS 文件分为 mkd.js 和 mkd_v2.js 两个版本,如下图所示:

01

此外,在界面上也有所区别,v2 版本的旋转、滑块图片有很明显的阴影、线条干扰,如下图所示:

02

上面给的地址中,点选验证码的地址有时候是点选,有时候会变成旋转,估计是异常等级不同导致的,此外,传闻还有一种无感验证,不过作者到处找也没找到个地址,估计逻辑都是差不多的,无感验证如下图所示:

03

抓包分析

以下以 v1 旋转验证码为例(v2 接口名称不一样,但逻辑是一样的),第一次 viewlog 接口,请求的 ak 是固定值,当然不同场景不同网站是不一样的,callback 回调值,_ 时间戳,返回值 astk 都是后面会用到的。

04

05

然后是一个 getstyle 接口,其中的 tk 就是前面 viewlog 接口返回的,返回值里 backstr 后续参数加密会用到,img 就是旋转图片地址,info 是一些版权信息。

06

07

旋转验证码开始验证,此时第二次出现 viewlog 接口,astk 参数是第一次 viewlog 返回的,fs 参数需要我们逆向,包含了旋转角度等信息,如果旋转角度正确且参数没问题,则返回值里的 op 为 1,另外返回的 dstk 后续还会用到。

08

09

上一步验证走完后,并不意味着通过验证了,后续还会有一个 viewlog/c 的接口需要进一步验证,其中的 tkds 参数就是上一步返回的,如果验证失败,返回值 code 为 1,验证成功,code 则为 0。

10

11

12

逆向分析 fs

接下来分析主要加密参数 fs,跟栈到 mkd.js:

13

14

可以看到 o 就是 fs,而 o 又是 r.rzData 经过加密后得到的,输出一下 r.rzData,结构如下图所示:

15

重要参数:

  • ac_c:一看就知道和旋转的角度有关;
  • backstrgetstyle 接口返回的;
  • cl:x,y 坐标以及时间戳,量一下就知道这个坐标是鼠标点击下面那个滑动条按钮的时候的坐标;
  • mv:鼠标轨迹,鼠标动一下就记录一下坐标和时间戳;
  • cr:屏幕长宽高等信息;
  • 其他值都是空或者0。

实际测试,clmv 都不校验,写死或者置空都行,当然想要自己伪造一下也是可以的,量一下滑动按钮在屏幕中的位置,cl 根据这个位置随机生成就行了。重点看看 ac_c,直接搜索即可定位:

16

17

可以看到这个值的计算方法为 parseFloat(o / a).toFixed(2)a 是定值 212,实际上就是滑动条能够滑动的最大长度,o 是滑动的距离,如果我们识别出来的是旋转角度 angle,则 ac_c 计算方法如下:

var o = angle * 212 / 360var ac_c = parseFloat(o / 212).toFixed(2)// 也可以直接写成:var ac_c = parseFloat(angle / 360).toFixed(2)

r.rzData 搞定后,就只有个 r.encrypt() 方法了,直接跟进去就是我们熟悉的 AES 算法,其中 iv 是 viewlog 接口返回的 as 值加上一个定值 appsapi0,其他就不用多说了。至此加密参数就搞完了,还是非常简单的。

18

19

旋转角度识别

这里推荐一个国外大佬的 RotNet 项目,可以用于预测图像的旋转角度以纠正其方向,还有基于此项目开发的,Nanda 大佬的 RotateCaptchaBreak、另一个大佬的 rotate-captcha-crack 等,链接如下:

深度学习大佬可以基于这些项目进一步训练,像我这种对这方面一窍不通的当然是选择打码平台了,云码打码还不错,只不过官网只放出了 v1 版本没有阴影干扰的,找他们客服可以拿到 v2 版本有阴影干扰的类型,这里就不多说了,免得被认为是打广告了哈哈哈。

20

v2 版本分析

v2 版本和 v1 版本基本上差不多,区别在于 rzData 的结构不太一样,ac_c 的计算方法不一样,以及 AES 的 IV 不一样,先看 AES 的 IV,v2 版本是 as 值加上固定值 appsapi2

21

然后再看看 rzDatacommon 字段下基本上就是 v1 的 rzData 的格式,captchalist 下,至少有 spin-0(旋转)、``puzzle-0(滑块)、click-0(点选)三种,ac_c` 依旧是旋转角度占比、滑动占比以及点选坐标信息,其他的依旧是写死或者置空就行。

22

然后就是 ac_c 的计算方法了,首先是旋转验证码,直接搜索 ac_c

23

往上跟栈,有个 percent 的地方,一个三目表达式,e 是固定值 290,e - 52 = 238,238 也就是滑动条能够滑动的最大长度:

24

如果我们识别出来的是旋转角度 angle,则 ac_c 计算方法如下:

var distance = angle * 238 / 360var ac_c = Number((distance / (290 - 52)).toFixed(2))// 也可以直接写成:var ac_c = Number((angle / 360).toFixed(2))

而对于滑块验证码就有所不同,同样是这个地方的三目表达式,但是要走后面的逻辑:

25

如果我们识别出来的是滑动距离 distance,则滑块 ac_c 的计算方法如下:

var ac_c = Number((distance / 290).toFixed(2))

同样对于点选验证码来说,也不一样,ac_c 的值是点击的 xy 坐标以及时间戳:

26

其他问题

前面我们说了百度的验证应该有两次,对于第二次验证,也就是 v1 的 viewlog/c 接口,v2 的 cap/c 接口,即便你第一次校验通过了,这个 c 接口校验也有可能不通过,出现这种情况的原因是通过的时间太短了,随机 time.sleep 1-3 秒即可,如果时间太短,c 接口可能会报以下验证错误:

{'code': 1, 'isRectified': False, 'msg': 'Verification Failed'}

还有一种情况就是提示存在安全风险,请再次验证,出现这种情况你会发现去浏览器手动滑也是一样的,所以在本地加个再次验证的逻辑就行了,一般来说第二次验证就能通过。

{'code': 0, 'msg': 'success', 'data': {'f': {'feedback': 'https://www.baidu.com/passport/ufosubmit.html', 'reason': '存在安全风险,请再次验证'}}}

然后就是请求 header 里没有 Referer 或者 Referer 不正确的话,会报错:

// v1 没有 Referer{'code': 1, 'msg': 'Unregistered Host'}// v1 Referer 不正确{'code': 1, 'msg': 'Invalid Request', 'data': []}// v2 没有 Referer 或者 Referer 不正确{'code': 100600, 'msg': 'Unauthorized Host'}

还有一个小技巧,如果你想自己验证一下旋转的角度对不对,怎么去测量这个角度呢?我们可以借助一些做图软件,简单点儿的比如美图秀秀,新建一个画布,然后直接将验证码图片拖进去,就可以自由旋转了,旋转的时候软件会自动标注出旋转的角度,如下图所示:

27

结果验证

28

29

30

31

极验业务安全,深知 V2 业务风控逆向分析

作者 BOB
2023年1月16日 20:42

captcha_reverse

欢迎加入爬虫逆向微信交流群:添加微信 IT-BOB(备注交流群)

文章目录



声明

本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关!

本文章未经许可禁止转载,禁止任何修改后二次传播,擅自使用本文讲解的技术而导致的任何意外,作者均不负责,若有侵权,请通过邮件 admin@itbob.cn 联系我立即删除!

逆向目标

01

  • 目标:某验深知 V2 业务风控逆向分析
  • 主页:aHR0cHM6Ly93d3cuZ2VldGVzdC5jb20vZGVtby9kay12Mi5odG1s

深知简介

某验深知通过无感采集客户端数据,对用户的环境、标识、行为操作等进行智能化分析,结合业务场景有效识别有潜在风险的用户。整个识别过程不干扰用户,不打断业务既有流程。完整通讯流程如下:

02

抓包分析

访问首页,会引入一个 v2.sense.js,后面接了个 id,需要将其提取出来,后续有用到,当然一般情况下,同一个业务这个 id 应该是一样的,直接复制下来写死也行。

03

接着有个 gettype 的请求,这里主要返回一些资源路径,其中有个 gct.xxx.js,这个 JS 名称每隔一段时间就会变化,这个 JS 会生成一个键值对,例如 {'xnbw': '1158444372'},JS 变化,这个键值对也会变化,这个键值对参与了后面加密参数的生成,在某验系列产品中都有这个东西,少量测试将其固定发现也可以通过验证,盲猜大量请求或者某些校验严格的网站可能有影响,建议还是动态去请求这个 JS 来获取最新的键值对,这个后文具体再说。

04

然后是 judge 的请求,这个请求页面一加载就完成了,不需要手动点击请求,其中 Query String Parameters 里有个 app_id 就是我们前面提到的 idRequest Payload 就是一串超长的字符串,这个也是我们需要逆向的参数。该请求如果验证成功,会返回一个 session_id

05

06

然后就是业务接口了,本例中业务接口是 verify-dk-v2,也就是一个登录接口,带上前面 judge 接口返回的 session_id 即可请求成功。

07

08

逆向分析

由于我们逆向的参数 Request Payload 没有键名导致不能直接搜索关键字,所以只能跟栈或者下个 XHR 断点,跟栈可以在 sense.2.3.0.js 第 6144 行找到一个 e + h[AUJ_(1173)],这个就是正确的 Request Payload 值。

09

上图中其实核心代码就四行,后文也是围绕这四行代码来分析的:

var h = o[AUJ_(1156)]()  , e = CoUE[ymDv(24)](NFeB)  , l = EbF_[ymDv(409)](e, h[ymDv(1194)])  , e = DWYi[ymDv(1137)](l)

获取 h 值

先来看 h 的值,由一个方法生成一个对象,对象里面分别是 aeskeyrsa,每次也都是随机变化的。

10

继续跟到这个方法里,重点在于 e 和 t 的值,最后返回的就是 {aeskey: e, rsa: t}

11

先看这个 e 的值,也就是 RwyT() 方法,搞过某验其他产品的就知道这里是 16 位随机值。

12

然后 t 的值,和某验其他系列产品一样,用到了 RSA 加密算法,这里图中 BPqG() 就是 RSA 算法,t 的值就是 RSA 加密后的结果,扣的时候注意找到算法开头的地方,将整个 BPqG() 方法扣下来即可。

13

14

获取 e 值

接下来是 e 的值,e = CoUE[ymDv(24)](NFeB),很明显是将 NFeB 的值进行了处理,NFeB 是个对象,里面有一些 dataid 等信息,如下图所示:

15

所以我们得先找一下 NFeB 这个值是怎么来的,直接搜索发现只有四个地方,在第 6109 行就是定义的地方,挨个看,首先有个 s 参数,将 id 传入到一个函数进行处理,函数没啥特别的,直接扣就行,通常经过处理后,s 的值为空,即 s=""

16

再来看有个 u 值,由一个方法生成了一大串包含很多感叹号的字符串,本案例实际测试中,直接将这个值置空也行,可能其他校验严格或者大批量请求的情况下,说不定也会校验的,所以我们最好也跟进去找一下生成逻辑。

17

跟进这个方法,里面是一些浏览器环境的值,比如屏幕高宽、canvas、ua、浏览器插件、时间、时区、语言等等,基本上都能写死,后续会将这些值以 !! 相连接最终生成 u 的值。

18

然后继续看,接下来是 c 值,是一个对象,值为 {"key":0,"value":[]},我这里直接写死了。

19

再往下就是 NFeB 了:

20

Unicode 转换一下,简单解一下混淆,就长下面这样:

NFeB = {    "id": a["id"],    "page_id": a["page_id"],    "lang": a["lang"] || AUJ_(31),    "data": {        "insights": u || null,        "track_key": c["value"] ? c["key"] : null,        "track": c["value"] || null,        "ep": o["KZrg"](i),        "eco": window["GEERANDOMTOKEN"] || "",        "ww3": ""    }};

id 不用说,page_id 就是个时间戳,lang 中文就是 zh-cninsights 是前面得到的 u 值,track_keytrackc 的键和值,epi 传入了一个函数进行处理,i 是固定的字符串 client,这个 KZrg 方法可以跟进去看看,里面其实有很多都是定值,唯一需要注意的是 t["tm"] 这个值,和某验其他系列一样,是 window.performance.timing 的值,自己获取一下时间戳随机加减伪造一下就行了。

21

然后就是 eco 的值,取的 window.GEERANDOMTOKEN,打印一下 window,除了有这个 token 以外,还可以看到 localStoresession 里面也有这个值。

22

23

由于某验的 JS 都是混淆后的,不太好定位这个值生成的地方,所以拿出我们的 Hook 大法,先清除一下缓存,不然的话是 Hook 不到值的,Hook 代码如下:

(function() {    var token = "";    Object.defineProperty(window, 'GEERANDOMTOKEN', {        set: function(val) {            console.log('GEERANDOMTOKEN->', val);            debugger;            token = val;            return val;        },        get: function(){            return token;        }    });})();

24

断下后往前跟栈,window[o] = to 就是 GEERANDOMTOKENt 就是我们想要的值。

25

往上就可以找到 t 的生成方法,核心就是生成一个 32 位的随机字符串,然后加上时间戳,再进行 MD5 加密得到最终值,生成位置以及实现的代码如下:

26

var MD5 = require("md5")function getToken(){    var t = MD5(function(e) {        for (var t = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"], n = "", r = 0; r < e; r++)            n += t[parseInt(61 * Math.random(), 10)];        return n;    }(32) + new Date().getTime());    return t;}

当你把以上这些参数都搞完了,你可能认为都齐了,其实不然,后面接着还有一句 Yvwp(NFeB, r),将 r 的值增加到了 NFeB 里,这个 r 的值类似于 {olbo: "1588069361"},这个键值对都是每隔一段时间会变的,这个在某验系列其他文章里也提过。

27

进一步分析,这个 r 是传进来的,所以往上跟栈,有个 r[psPG(1183)]() 方法就生成了这个对象:

28

继续跟到这个方法里去,首先定义了 e 这个对象,然后赋值 e = {ep: "test data", lang: "zh"},然后经过 window[tYlM(1126)]() 方法处理后,e 里面就新增了 {olbo: "1588069361"},后续将 ep 和 lang 两个值删除后返回。

29

所以我们继续跟进 window[tYlM(1126)]() 方法,会跳转到 gct.xxxx.js 里,这个 JS 就是我们开头讲过的,他的名称会每隔一段时间变化,内容也会变,所以导致生成的键值对也会变化,继续跟,有个 t[e] = xxx 的语句,其中 e 和等号右边的值,就是我们需要的键值对。

30

这个键值对在我们本地也可以动态获取,只需要请求正确的 JS 文件,将要调用的方法全局导出就行了,以下给一个我的处理方法示例(注意里面请求 url 已经脱敏处理,所以不可直接运行,自行抓包补上):

import reimport timeimport jsonimport execjsimport requestsfrom loguru import loggerheaders = {    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36",}def get_gct():    url = "https://dkapi.脱敏处理.com/deepknow/v2/gettype"    params = {        "callback": "脱敏处理_" + str(int(time.time() * 1000))    }    response = requests.get(url, headers=headers, params=params).text    response = json.loads(re.findall(r"geetest_\d+\((.*?)\)", response)[0])    # gettype 接口返回的 gct.xxx.js 的地址    gct_path = "https://static.脱敏处理.com" + response["gct_path"]    logger.info("gct_path: %s" % gct_path)    gct_js = requests.get(gct_path, headers=headers).text    # 正则匹配需要调用的方法名称    function_name = re.findall(r"\)\)\{return (.*?)\(", gct_js)[0]    # 查找需要插入全局导出代码的位置    break_position = gct_js.find("return function(t){")    # window.gct 全局导出方法    gct_js_new = gct_js[:break_position] + "window.gct=" + function_name + ";" + gct_js[break_position:]    # 添加自定义方法调用 window.gct 获取键值对    gct_js_new = "window = global;" + gct_js_new + """    function getGct(){        var e = {"lang": "zh", "ep": "test data"};        window.gct(e);        delete e["lang"];        delete e["ep"];        return e;    }"""    gct = execjs.compile(gct_js_new).call("getGct")    logger.info("gct: %s" % gct)    return gct

到这里我们 NFeB 就生成完毕了,回到 e 的值,这里其实就是把 NFeB 转成字符串,直接 JSON.stringify() 即可。

31

获取 l 值

l 的值比较简单,就是将前面生成的 h["aeskey"] 作为 key,e 作为待加密字符串,经过 AES 加密后即可得到 l 的值。

32

本地复现如下(有些变量名称不一样无影响,我是直接复用的某验其他产品的方法):

var CryptoJS = require("crypto-js")function aesEncrypt(e, i) {    var key = CryptoJS.enc.Utf8.parse(i),    iv = CryptoJS.enc.Utf8.parse("0000000000000000"),    srcs = CryptoJS.enc.Utf8.parse(e),    encrypted = CryptoJS.AES.encrypt(srcs, key, {        iv: iv,        mode: CryptoJS.mode.CBC,        padding: CryptoJS.pad.Pkcs7    });    for (var r = encrypted, o = r.ciphertext.words, i = r.ciphertext.sigBytes, s = [], a = 0; a < i; a++) {        var c = o[a >>> 2] >>> 24 - a % 4 * 8 & 255;        s.push(c);    }    return s;}

进一步处理 l

最后一步 e = DWYi[ymDv(1137)](l),将 l 的值经过了 tc_t 这个方法进行处理,就会得到最终 Request Payload 的一部分。

33

跟进这个 tc_t 方法,又是熟悉的 return e["res"] + e["end"],同样和某验其他产品一样的。

34

跟到处理 e 的这个方法里,最后返回的是 {"res": a, "end": s},没啥特别的,直接扣即可,这里注意和某验其他产品里的方法有些小区别,里面有些常量的值是不一样的,最开始我直接复用了其他产品的方法,发现结果是错的。

35

自此整个流程分析完毕,最终 e + h[AUJ_(1173)] 的值与 Request Payload 的值一致。

36

37

结果验证

38

❌
❌