跳到主要内容

常见加密解密算法

内容较难,务必耐心。

计算机领域有很多加密算法,这里只整理在逆向和爬虫中的加密算法。就是网站经常使用的那些加密算法。同时使用JS和PY加以说明。

常见加密

加密

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

编码

还有一个混淆编码算法,一般情况下,此Base64算法用的不多,经常是Base64变体用的多,如按照Base64规则自定义的加密算法较多。 常见编码算法:Base64

JS加密算法常用

Crypto-JS

Crypto-JS 支持 MD5、SHA、RIPEMD-160、HMAC、PBKDF2、AES、DES、3DES(Triple DES)、Rabbit、RC4 等,不支持 RSA、ECC 地址: https://github.com/brix/crypto-js

Node-RSA

Node-RSA 补充支持 RSA ,主要问题是RSA过分复杂了。

地址:https://github.com/rzcoder/node-rsa

JSEncrypt

JSEncrypt 主要支持 RSA 算法

地址:https://github.com/travist/jsencrypt

Python加密解密库

Cryptodome & Crypto

Cryptodome 支持几乎所有主流加密算法,包括 MD5、SHA、BLAKE2b、BLAKE2s、HMAC、PBKDF2、AES、DES、3DES(Triple DES)、ECC、RSA、RC4 等 地址: https://www.pycryptodome.org/

强调一点: Cryptodome 是 Crypto 的替代品,Crypto 有很多未知错误,以后不要用。这里就不放地址了。

Cryptodome 使用命令 pip install pycryptodome 进行安装,Crypto 使用命令 pip install pycrypto 进行安装。

Hashlib

比较好用,特别是针对只会前端JS的Python程序员。 地址:https://docs.python.org/3/library/hashlib.html hashlib 提供了常见的摘要算法,如 MD5,SHA、BLAKE2b、BLAKE2s 等

HMAC

Python 的标准库 hmac 对 HMAC 算法提供了支持。

hmac 库:https://docs.python.org/3/library/hmac.html

pyDes

Python 的第三方库 pyDes 对 DES 算法提供了支持。使用命令 pip install pydes 进行安装。

参考资料:pyDes 库:https://github.com/twhiteman/pyDes

ESA

Python 的第三方库 rsa 对 RSA 算法提供了支持。使用命令 pip install rsa 进行安装。

参考资料:rsa 库:https://stuvel.eu/python-rsa-doc/

所有加密算法的都会有的参数

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

初始向量 iv

在密码学的领域里,初始化向量(英语:initialization vector,缩写为IV),或译初向量,又称初始变量(starting letiable,缩写为SV),是一个固定长度的输入值。一般的使用上会要求它是随机数或拟随机数(pseudorandom)。

加密模式 mode

注意:不要觉得多,未来有一天总会用到的。往往高级程序员和低级程序员的区别就在此处。

目前流行的加密和数字认证算法,都是采用块加密方式,就是将需要加密的明文分成固定大小的数据块,然后对其执行密码算法,得到密文。数据块的大小通常采用跟密钥一样的长度。加密模式在加密算法的基础上发展出来,同时也可以独立于加密算法而存在,加密模式定义了怎样通过重复利用加密算法将大于一个数据块大小的明文转化为密文,描述了加密每一数据块的过程。目前利用较多的加密模式有以下几种:

  • ECB:Electronic Code Book(电子码本模式),是一种基础的加密方式,密文被分割成分组长度相等的块(不足补齐),然后单独一个个加密,一个个输出组成密文。

  • CBC:Cipher Block Chaining(密码块链接模式),是一种循环模式,前一个分组的密文和当前分组的明文异或操作后再加密,这样做的目的是增强破解难度。

  • PCBC:Propagating Cipher Block Chaining(填充密码块链接模式),也称为明文密码块链接模式(Plaintext Cipher Block Chaining),是一种可以使密文中的微小更改在解密时导致明文大部分错误的模式,并在加密的时候也具有同样的特性。

  • CFB:Cipher Feedback(密码反馈模式),可以将块密码变为自同步的流密码,类似于 CBC,CFB 的解密过程几乎就是颠倒的 CBC 的加密过程。

  • OFB:Output Feedback(输出反馈模式),可以将块密码变成同步的流密码,它产生密钥流的块,然后将其与明文块进行异或,得到密文。与其它流密码一样,密文中一个位的翻转会使明文中同样位置的位也产生翻转。

  • CTR:Counter mode(计数器模式),也被称为 ICM 模式(Integer Counter Mode,整数计数模式)和 SIC 模式(Segmented Integer Counter),在 CTR 模式中,有一个自增的算子,这个算子用密钥加密之后的输出和明文异或的结果得到密文,相当于一次一密。这种加密方式简单快速,安全可靠,而且可以并行加密,但是在计算器不能维持很长的情况下,密钥只能使用一次。

填充方式 padding

块密码只能对确定长度的数据块进行处理,而消息的长度通常是可变的。因此部分模式最后一块数据在加密前需要进行填充。有数种填充方法,其中最简单的一种是在明文的最后填充空字符以使其长度为块长度的整数倍。常见填充方式有以下几种:

  • PKCS7:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为需要填充的字节长度值。

  • PKCS5:PKCS5 作为 PKCS7 的子集算法,概念上没有什么区别,只是在 blockSize 上固定为 8 bytes,即块大小固定为 8 字节。

  • ZeroPadding:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度), 在填充字节序列中所有字节填充为 0 。

  • ISO10126:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充随机数值。

  • ANSIX923:在填充时首先获取需要填充的字节长度 = 块长度 - (数据长度 % 块长度),在填充字节序列中最后一个字节填充为需要填充的字节长度值,填充字节中其余字节均填充数字零。

Base64基础版本

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

为什么说是基础版本? 解释:Base64在很多时候,均不用语言自带的实现,为了加密和安全,均有自己的Base64实现函数。

js实现

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

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

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

let text = "Base64!"

let encodeData = base64Encode()
let decodeData = base64Decode()

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

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__':
text = 'Python'
encode_data = base64_encode(text)
decode_data = base64_decode(encode_data)
print('Base64 编码:', encode_data)
print('Base64 解码:', decode_data)

MD5

摘要算法,又称哈希算法、散列算法,之所以叫摘要算法,它的算法就是提取明文重要的特征。摘要算法是单向加密的,它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

js

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

function MD5Test() {
let text = "MD5"
return CryptoJS.MD5(text).toString()
}

python

import hashlib


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


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


if __name__ == '__main__':
md5_test1()
md5_test2()
# md5_test1() md5_test2() 结果是一样的

PBKDF2

PBKDF2 将伪随机函数, 把明文和一个盐值(salt)作为输入参数,然后进行重复运算,并最终产生密钥,如果重复的次数足够大,破解的成本就会变得很高。

这个方法,常用在登录系统中,对密码进行加密,即明文密码是123456,但是存储的时候会随机分配一个salt值,然后拿着123456和salt做加密后存到数据库。

由于salt是系统按需分配的, 所以不同的账号下的即使密码相同如123456,但是因为salt不同,得到的加密结果也不同。主要解决的问题是,数据库账号表被泄漏后,增加密码暴力破解,可以说在有限时间(十几万年)内无解。

js

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

function pbkdf2Encrypt() {
let text = "123456"
let salt = "456789"
// key 长度 128,10 次重复运算
let encryptedData = CryptoJS.PBKDF2(text, salt, {keySize: 128/32,iterations: 10});
return encryptedData.toString()
}

python

import binascii
from Cryptodome.Hash import SHA1
from Cryptodome.Protocol.KDF import PBKDF2


text = '123456'
salt = b'456789'
result = PBKDF2(text, salt, count=10, hmac_hash_module=SHA1)
result = binascii.hexlify(result)
print(result)

SHA

安全哈希算法, SHA 通常指 SHA 家族的五个算法,分别是 SHA-1、SHA-224、SHA-256、SHA-384、SHA-512,后四者有时并称为 SHA-2,SHA 是比 MD5 更安全一点的摘要算法,MD5 的密文是 32 位,而 SHA-1 是 40 位,版本越强,密文越长,代价是速度越慢。 不要觉得名称里面有安全两字就安全了,一般情况是系统破解常遇到的是SHA-256算法,偶尔遇到SHA-512算法,这些算法有一点的可逆性,主要体现在摘要的时候有哈希冲突。 出现了哈希冲突的时候,基本上就可以在1小时以内拿到加密前的数据了(重要)。

js

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

function SHA1Encrypt() {
let text = "!"
return CryptoJS.SHA1(text).toString();
}

python

import hashlib


def sha1_test1():
sha1 = hashlib.new('sha1', 'Hello'.encode('utf-8'))
print(sha1.hexdigest())


def sha1_test2():
sha1 = hashlib.sha1()
sha1.update('Hello'.encode('utf-8'))
print(sha1.hexdigest())
# sha1_test1 和sha1_test2 等价,结果一样

HMAC

散列消息认证码、密钥相关的哈希运算消息认证码,HMAC 加密算法是一种安全的基于加密 Hash 函数和共享密钥的消息认证协议,它要求通信双方共享密钥 key、约定算法、对报文进行 Hash 运算,形成固定长度的认证码。通信双方通过认证码的校验来确定报文的合法性。 js

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

function HMACEncrypt() {
let text = "python"
let key = "secret"
return CryptoJS.HmacMD5(text, key).toString();
}

python


import hmac


def hmac_test1():
message = b'hi world'
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('hi'.encode('utf8'))
sha1.update('world'.encode('utf8'))
print(sha1.hexdigest())

DES

数据加密标准,加密与解密使用同一密钥,属于对称加密算法,DES 是一个分组加密算法,使用 56 位的密钥(一般认为密钥是 64 位,但是密钥的每个第 8 位设置为奇偶校验位,所以实际上有效位只有 56 位),由于 56 位密钥长度相对较短,所以 DES 是不安全的,现在基本上已被更高级的加密标准 AES 取代。

mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。

padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。 js

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

function desEncrypt() {
let 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() {
let 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);
}

let text = "zero" // 待加密对象
let desKey = "123456789123456" // 密钥
let desIv = "0123456789ABCDEF" // 初始向量

let encryptedData = desEncrypt()
let decryptedData = desDecrypt()

python

import binascii
# 加密模式 CBC,填充方式 PAD_PKCS5
from pyDes import des, CBC, PAD_PKCS5

def des_encrypt(key, text, iv):
k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
en = k.encrypt(text, padmode=PAD_PKCS5)
return binascii.b2a_hex(en)


def des_decrypt(key, text, iv):
k = des(key, CBC, iv, pad=None, padmode=PAD_PKCS5)
de = k.decrypt(binascii.a2b_hex(text), padmode=PAD_PKCS5)
return de


if __name__ == '__main__':
secret_key = '12345678' # 密钥
text = 'zero' # 加密对象
iv = secret_key # 偏移量
secret_str = des_encrypt(secret_key, text, iv)
clear_str = des_decrypt(secret_key, secret_str, iv)

3DES

三重数据加密算法,即通过增加 DES 的密钥长度来避免破解。只是增加了破解难度,算法与DES没什么区别。 js

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

function tripleDesEncrypt() {
let 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() {
let 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);
}

let text = "Hi" // 待加密对象
let desKey = "123456789123456" // 密钥
let desIv = "0123456789ABCDEF" // 偏移量

let encryptedData = tripleDesEncrypt()
let decryptedData = tripleDesDecrypt()

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 = 'Hello World' # 加密对象
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)

AES

高级加密标准,在密码学中又称 Rijndael 加密法,它本身只有一个密钥,即用来实现加密,也用于解密。 mode 支持:CBC,CFB,CTR,CTRGladman,ECB,OFB 等。 padding 支持:ZeroPadding,NoPadding,AnsiX923,Iso10126,Iso97971,Pkcs7 等。

js

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

function tripleAesEncrypt() {
let 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() {
let 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);
}

let text = "Hello World" // 待加密对象
let aesKey = "123456789123465" // 密钥,16 倍数
let aesIv = "0123456789ABCDEF" // 偏移量,16 倍数

let encryptedData = tripleAesEncrypt()
let decryptedData = tripleAesDecrypt()

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 = 'Hello World' # 加密对象
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)

RC4

流加密算法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法,也曾经是 TLS 可采用的算法之一,但在 RC4 中发现了多个漏洞,它特别容易受到攻击,RC4 作为一种老旧的验证和加密算法易于受到黑客攻击,现在逐渐不推荐使用了。 js

// 引用 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 = "hello world"
var key = "123456789123456"

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

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 = 'Hello World' # 加密对象
encrypted_str = rc4_encrypt(secret_key, text)
print('加密字符串:', encrypted_str)
decrypted_str = rc4_decrypt(secret_key, encrypted_str)
print('解密字符串:', decrypted_str)

Rabbit

Rabbit 加密算法是一个高性能的流密码加密方式,从 128 位密钥和 64 位初始向量(iv)创建一个密钥流. js

// 引用 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 = "hei zero"
var key = "123456789213456"

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

Python 的实现需要自己手动实现,没有官方的。特别的麻烦。可以忽略。

RSA

RSA 加密算法是一种非对称加密算法,RSA是第一个能同时用于加密和数字签名的算法,它能够抵抗到目前为止已知的所有密码攻击。在公开密钥加密和电子商业中RSA被广泛使用。它被普遍认为是目前比较优秀的公钥方案之一。 比如git的ssh就是这种。

js

// 引用 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 = "Hello World"

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

python

import rsa


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


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


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

在逆向和爬虫中,以上的加密方法均会体现。