2020-1-5 爬网页17-js逆向入门(AES加密-对称加密,crypto,分组模式CBC,填充模式)

本文只适用于初学者,只需要会打断点追踪就可以了。

前端js代码除了Base64编码和解码MD5加密之外,有时候还会用AES加密。

AES算法原理

关于AES原理,详细参见链接

简介

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。高级加密标准已然成为对称密钥加密中最流行的算法之一。

看网上有人介绍,微信小程序加密传输就是用AES加密算法的。

关于对称加密算法,就是加密和解密用相同的密钥,具体的加密流程如下图:
在这里插入图片描述

  • 明文P:没有经过加密的原始数据。
  • 密钥K:用来加密明文的密码,在对称加密算法中,加密与解密的密钥是相同的。密钥为接收方与发送方协商产生,但不可以直接在网络上传输,否则会导致密钥泄漏,通常是通过非对称加密算法加密密钥(例如RSA),然后再通过网络传输给对方,或者直接面对面商量密钥。密钥是绝对不可以泄漏的,否则会被攻击者还原密文,窃取机密数据。
  • AES加密函数:设AES加密函数为E,则 C = E(K, P),其中P为明文,K为密钥,C为密文。也就是说,把明文P和密钥K作为加密函数的参数输入,则加密函数E会输出密文C。
  • 密文C:经加密函数处理后的数据
  • AES解密函数:设AES解密函数为D,则 P = D(K, C),其中C为密文,K为密钥,P为明文。也就是说,把密文C和密钥K作为解密函数的参数输入,则解密函数会输出明文P。

对称加密算法与非对称加密算法的区别

  • 对称加密:加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。
  • 非对称加密算法:加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。

AES算法相关概念

1.分组加密模式
关于本节详见链接

AES是基于数据块的加密方式,每次处理的数据是一块(16字节)。当数据不是16字节的倍数时需要进行填充,这就是所谓的分组密码(区别于基于比特位的流密码),16字节是分组长度。

分组加密有几种模式:

  • ECB,电码本模式 Electronic Codebook Book
  • CBC,密码分组链接模式 Cipher Block Chaining
  • CTR,计算器模式Counter (CTR)
  • CFB,密码反馈模式Cipher FeedBack
  • OFB,输出反馈模式Output FeedBack

密码分组链接模式Cipher Block Chaining (CBC)
这里会详细说明一下这个模式,因为后面例子会用到。

这种模式是先将明文切分成若干小段,然后每一小段与初始块或者上一段的密文段进行异或运算后,再与密钥进行加密。
在这里插入图片描述
优点
不容易主动攻击,安全性好于ECB,适合传输长度长的报文,是SSL、IPSec的标准。

缺点
不利于并行计算,误差传递,需要初始化向量IV(或者叫偏移量)

2.填充方法
上面的说明中我们已经了解,AES不是将拿到的明文一次性加密,而是分组加密。就是先将明文切分成长度相等的块,每块大小128bit(16字节),再对每一小块进行加密。

如果原始明文串不能被等分成128bit,例如:明文大小200bit,那么第二个块只有72bit,此时就需要对第二个块进行填充处理,让第二个块的大小达到128bit。

常见的填充模式有:

  • ZeroPadding,数据长度不对齐时使用0填充,否则不填充。注意:使用0填充有个缺点,当元数据尾部也存在0时,在unpadding时可能会存在问题。
  • PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n。如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小。openssl里AES的默认填充方式就是PKCS7Padding
  • PKCS5Padding,PKCS7Padding的子集,块大小固定为8字节

3.密钥
AES支持三种密钥长度:128位,192位,256位,大家经常说的AES128,AES256就是指不同的密钥长度。

不同的密钥长长度意味着AES加密的轮数不同,128位加密10轮,192加密12轮,256加密14轮。从安全性角度来讲,256位安全性最高,但是128位因为加密轮数少,所以性能更好一些。

补充:
各轮AES加密循环(除最后一轮外)均包含4个步骤:

  • AddRoundKey(轮密钥加):矩阵中的每一个字节都与该次回合密钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
  • SubBytes(字节代换):通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
  • ShiftRows(行位移):将矩阵中的每个横列进行循环式移位。
  • MixColumns(列混合):为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey替换。

js使用AES加密

上面的知识,如果不是专门研究密码学,不详细了解也没有关系。
不过分组加密模式,填充方式还是要有些概念的,不然下面的内容看的会有点困难。

在这里插入图片描述
观察上面的登陆记录。我输入的密码是123456,但是Request Payload中提交的password是“JfYot+NjHbQacTYI6d5KMQ==”。显然是处理过的。

下面就是要追踪了
在这里插入图片描述
搜索一下password,打上断点。
其实从函数名称已经可以发现,它用的就是AES。

跟踪一下,2步就到了下面的地方。
在这里插入图片描述

这个是AES在js中的标准实现代码。
n是密钥,iv:a是初始化向量(或者说偏移量),t是明文,分组模式cbc,填充模式ZeroPadding。

这里你能很容易发现js中AES加密的缺点,密钥是直接写在代码中的。

在js中实现AES加密利用的是crypto-js模块。关于这个模块介绍,参见链接

首先,安装crypto-js模块

npm install crypto-js --save

然后nodejs运行下面代码

const CryptoJS = require('crypto-js');  //引用AES源码js

function l(e) {
        var n = CryptoJS.enc.Latin1.parse("h5LoginKey123456")
          , a = CryptoJS.enc.Latin1.parse("h5LoginIv1234567")
          , t = e
          , o = CryptoJS.AES.encrypt(t, n, {
            iv: a,
            mode: CryptoJS.mode.CBC,
            padding: CryptoJS.pad.ZeroPadding
        });

        return o.toString()
    }

aaaa="123456"
console.log(l(aaaa))

运行结果,完全一致

JfYot+NjHbQacTYI6d5KMQ==

又,安装完crypto-js模块后,我的nodejs运行时候有过报错如下

Error: Cannot find module 'crypto-js'

输入命令查询一下

npm config ls

关注结果中的prefix部分,把目录加入环境变量path中。
如果还不行,就把js文件拷贝到上面的那个目录中执行,应该就可以了。

我们再来尝试一下python
python中安装以下模块

pip install pycrypto

如果安装过程报错

 error: Microsoft Visual C++ 9.0 is required. Get it from http://aka.ms/vcpython27

那就去上面的链接下载VCForPython27.msi。
这个文件是windows平台使用的python编译扩展器,以前都是vc/vs系列来的,但是安装包特别大而且容易出错,这次微软官方推出的专门的版本。

安装完VCForPython27.msi后,应该就可以正常安装pycrypto了。

python中AES实现代码如下

import base64
from Crypto.Cipher import AES
import random

#ZeroPadding,数据长度不对齐时使用0填充,否则不填充
def ZeroPadding(text):   #明文使用Zero填充    最终调用AES加密方法时,传入的是一个byte数组,要求是16的整数倍,因此需要对明文进行处理    :param text: 待加密内容(明文)    :return:    
    bs = AES.block_size  # 16。分块长度为16
    length = len(text)
    bytes_length = len(text.encode('utf8'))  # utf-8编码时,英文占1个byte,而中文占3个byte
    #bytes_length = len(bytes(text, encoding='utf-8'))    # tips:utf-8编码时,英文占1个byte,而中文占3个byte
    
    padding_size = length if(bytes_length == length) else bytes_length  #数据长度。数据为‘123456’时候,长度为6
    print "bs=",bs
    print "padding_size=",padding_size
    
    padding = bs - padding_size % bs    #需要填充的长度为 16 - 6 % 16 = 10
    print "padding=",padding

    if padding== bs: #长度对齐,不用填充
        padding_text=""
    else: #长度不对齐,填充0
        # chr(padding)看与其它语言的约定,有的会使用'\0'
        padding_text = chr(0) * padding 
    return text + padding_text 

def encrypt(key, content,iv):    #AES加密    模式cbc     content: 加密内容    
    key_bytes = key.encode('utf8')
    #keykey_bytes = bytes(key, encoding='utf-8')
    iv = iv.encode('utf8')
    #iv = bytes(iv, encoding='utf-8')
    
    cipher = AES.new(key, AES.MODE_CBC, iv)    # 处理明文
    #content_padding = pkcs7padding(content)    # 填充,结果3b+VGZ+iMWmZX2VFlTybEA==
    content_padding = ZeroPadding(content)    # 填充,结果JfYot+NjHbQacTYI6d5KMQ==
    
    encrypt_bytes = cipher.encrypt(content_padding.encode('utf8'))    # 重新编码
    #encrypt_bytes = cipher.encrypt(bytes(content_padding, encoding='utf-8'))    # 重新编码
    
    result = str(base64.b64encode(encrypt_bytes)).encode('utf8')
    #result = str(base64.b64encode(encrypt_bytes), encoding='utf-8')
    return result

key="h5LoginKey123456"
iv="h5LoginIv1234567"
content="123456"

print encrypt(key,content,iv)

运行结果,和js中也是一致的。

bs= 16
padding_size= 6
padding= 10
JfYot+NjHbQacTYI6d5KMQ==

代码中详细说明了填充模式实现,对照前面的说明,应该不难理解。

如果填充模式改为PKCS7Padding
js实现代码如下

padding: CryptoJS.pad.Pkcs7

python实现代码如下

#PKCS7Padding,假设数据长度需要填充n(n>0)个字节才对齐,那么填充n个字节,每个字节都是n;
#如果数据本身就已经对齐了,则填充一块长度为块大小的数据,每个字节都是块大小(长度)

def pkcs7padding(text):   #明文使用PKCS7填充    最终调用AES加密方法时,传入的是一个byte数组,要求是16的整数倍,因此需要对明文进行处理    :param text: 待加密内容(明文)    :return:    
    bs = AES.block_size  # 16。分块长度为16
    length = len(text)
    bytes_length = len(text.encode('utf8'))  # utf-8编码时,英文占1个byte,而中文占3个byte
    #bytes_length = len(bytes(text, encoding='utf-8'))    # tips:utf-8编码时,英文占1个byte,而中文占3个byte
    
    padding_size = length if(bytes_length == length) else bytes_length  #数据长度。数据为‘123456’时候,长度为6
    print "bs=",bs
    print "padding_size=",padding_size
    
    padding = bs - padding_size % bs    #需要填充的长度为 16 - 6 % 16 = 10
    print "padding=",padding

    #填充一个长度为n且每个字节均为n的数据
    #填充公式为 n=数据块长度 - 分组后剩下不满足分组数据的长度,本例中n=10
    # chr(padding)看与其它语言的约定,有的会使用'\0'
    padding_text = chr(padding) * padding 
    return text + padding_text

运行结果如下

3b+VGZ+iMWmZX2VFlTybEA==

有兴趣可以自己尝试一下。

发布了122 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_42555985/article/details/103840701