前言
如果要逆向做一些模拟请求,加密算法是绕不过去的坎。以前逆向一个app更多的是破解一些单机的app一般只涉及到一些重打包之类的,现在看更多的是获取到里面的请求加密算法然后通过python实现一遍进而可以用机器代替人工去请求去实现一些有商业价值的事情。所以如果熟悉加密算法的话看逆向的代码会更加得心应手。
下面的每个算法都会用java和python代码实现一遍。
算法简介
- 对称加密算法:在对称加密算法中,加密和解密使用相同的密钥。常见的对称加密算法有AES、DES、3DES等。
- 非对称加密算法:在非对称加密算法中,加密和解密使用不同的密钥。常见的非对称加密算法有RSA、DSA、ECC等。
- 消息摘要算法:消息摘要算法可以将任意长度的消息压缩成固定长度的摘要。常见的消息摘要算法有MD5、SHA-1、SHA-256等。
- 散列函数:散列函数是一种将任意长度的数据映射到固定长度的数据的函数。常见的散列函数有SHA-1、SHA-256等。
- 数字签名算法:数字签名算法可以用于验证消息的完整性和身份认证。常见的数字签名算法有RSA、DSA、ECDSA等。
这些加密算法在计算机安全领域中被广泛使用,用于保护数据的机密性、完整性和可用性。
对称加密
对称加密算法。步骤是生成秘钥,然后用同一个秘钥加密、解密。
AES
AES是目前使用最广泛的对称加密算法之一,它的密钥长度可以是128、192或256位,相较于DES和DES3,它的安全性更高,加密速度也更快。由于AES加密强度高,常被用于加密敏感数据,如金融交易信息和国家机密信息等。
代码实现
- java
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
/**
* AES加密算法
*/
public class AESUtil {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String CHARSET = "UTF-8";
private static final int KEY_SIZE = 128;
/**
* 生成AES密钥
*
* @return 密钥字节数组
* @throws Exception
*/
public static byte[] generateKey() throws Exception {
// 生成一个随机的
// KeyGenerator keyGen = KeyGenerator.getInstance("AES");
// SecureRandom random = new SecureRandom();
// keyGen.init(KEY_SIZE, random);
// SecretKey secretKey = keyGen.generateKey();
// return secretKey.getEncoded();
byte[] keyBytes = {0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xab, (byte) 0xcd, (byte) 0xef,
(byte) 0xfe, (byte) 0xdc, (byte) 0xba, 0x08, 0x76, 0x54, 0x32, 0x10};
return keyBytes;
}
/**
* AES加密
*
* @param plaintext 明文
* @param key 密钥
* @param iv 初始化向量
* @return 密文
* @throws Exception
*/
public static String encrypt(String plaintext, byte[] key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKey secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encrypted = cipher.doFinal(plaintext.getBytes(CHARSET));
return Base64.getEncoder().encodeToString(encrypted);
}
/**
* AES解密
*
* @param ciphertext 密文
* @param key 密钥
* @param iv 初始化向量
* @return 明文
* @throws Exception
*/
public static String decrypt(String ciphertext, byte[] key, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKey secretKey = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decrypted, CHARSET);
}
public static void main(String[] args) throws Exception {
// 生成密钥
byte[] key = generateKey();
// 加密
String plaintext = "Hello, world!";
byte[] iv = new byte[16];
// 写死16个字节,方便验证
Arrays.fill(iv, (byte) 0x01);
// SecureRandom random = new SecureRandom();
// random.nextBytes(iv);
String ciphertext = encrypt(plaintext, key, iv);
// 解密
String decrypted = decrypt(ciphertext, key, iv);
// 打印结果
System.out.println("Key: " + Base64.getEncoder().encodeToString(key));
System.out.println("IV: " + Base64.getEncoder().encodeToString(iv));
System.out.println("Plaintext: " + plaintext);
System.out.println("Ciphertext: " + ciphertext);
System.out.println("Decrypted: " + decrypted);
}
}
输出结果
IV: AQEBAQEBAQEBAQEBAQEBAQ==
Plaintext: Hello, world!
Ciphertext: eHoXGnTtcLAa+OY0qbatTQ==
Decrypted: Hello, world!
- python
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from base64 import b64encode, b64decode
from Crypto.Util.Padding import pad, unpad
def generate_key():
# key = get_random_bytes(16)
key = bytearray([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
0xfe, 0xdc, 0xba, 0x08, 0x76, 0x54, 0x32, 0x10])
return key
def encrypt(plaintext, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(plaintext)
return b64encode(ciphertext).decode('utf-8')
def decrypt(ciphertext, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted = cipher.decrypt(b64decode(ciphertext))
return decrypted.decode('utf-8')
if __name__ == '__main__':
plaintext = b'Hello, world!' # 待加密数据
block_size = 16 # 块大小为16字节
padded_data = pad(plaintext, block_size) # 进行填充
key = generate_key()
iv = bytearray(16)
iv[:] = b'\x01' * 16
ciphertext = encrypt(padded_data, key, iv)
decrypted = decrypt(ciphertext, key, iv)
print('Key:', b64encode(key).decode('utf-8'))
print('IV:', b64encode(iv).decode('utf-8'))
print('Plaintext:', plaintext)
print('Ciphertext:', ciphertext)
print('Decrypted:', decrypted)
输出结果
Key: ASNFZ4mrze/+3LoIdlQyEA==
IV: AQEBAQEBAQEBAQEBAQEBAQ==
Plaintext: b'Hello, world!'
Ciphertext: eHoXGnTtcLAa+OY0qbatTQ==
Decrypted: Hello, world!
注意事项
- 逆向时肯定会有”AES“的字样,这个是没办法混淆的,因为是别人定义好的key
SecretKey secretKey = new SecretKeySpec(key, "AES");
- 加密模式,如果使用不同的加密模式加密出来的数据是不一样的
- ECB(Electronic Codebook)模式:将明文分成块后,对每个块进行独立加密,相同的明文块将得到相同的密文块,存在较大的安全隐患,已经不再被推荐使用。
- CBC(Cipher Block Chaining)模式:每个明文块与前一个密文块进行异或运算后再加密,加密后的密文块与下一个明文块进行异或运算,增加了密码算法的随机性,能够一定程度上避免ECB模式的安全问题。
- CFB(Cipher Feedback)模式:将前一个密文块解密后与当前明文块进行异或运算,再进行加密,从而得到密文块。CFB模式是一个自同步的模式,能够保证明文与密文的一致性,但是存在传输错误时的误差传播问题。
- OFB(Output Feedback)模式:将前一个密文块作为输入进行加密,得到伪随机序列,然后与当前明文块进行异或运算得到密文块。OFB模式可以将块密码转换为流密码,能够保证明文与密文的一致性,但是存在伪随机序列的安全性问题。
- CTR(Counter)模式:将明文与一个递增的计数器进行异或运算,然后再将异或结果与密钥进行加密,得到密文块。CTR模式能够将块密码转换为流密码,具有较好的随机性,可以支持并行加解密。
- GCM(Galois/Counter Mode)模式:是一种对称加密模式,除了实现加密外,还提供了完整性验证和认证功能,被广泛应用于TLS、IPSec等安全协议中。
- XTS(XTS-AES)模式:主要用于磁盘加密,能够提供加密和完整性保护。XTS模式需要两个独立的密钥,能够防止相关密钥攻击。
// java,或者有别的方式初始化,但是CBC这些字样也是固定的
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
Cipher cipher = Cipher.getInstance(ALGORITHM);
// python
cipher = AES.new(key, AES.MODE_CBC, iv)
- 初始化向量(IV)、生成密钥时的key。因为要两端加解密这两个也是固定的不然没办法对的上。一般都会写成一个全局变量
// java
byte[] key = {0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xab, (byte) 0xcd, (byte) 0xef,
(byte) 0xfe, (byte) 0xdc, (byte) 0xba, 0x08, 0x76, 0x54, 0x32, 0x10};
byte[] iv = new byte[16];
Arrays.fill(iv, (byte) 0x01);
// pythn
key = bytearray([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
0xfe, 0xdc, 0xba, 0x08, 0x76, 0x54, 0x32, 0x10])
iv = bytearray(16)
iv[:] = b'\x01' * 16
- 还原算法时需要注意加密的数据长度,如果是CBC模式需要保证数据是16的倍数,不然会报以下错误
ValueError: Data must be padded to 16 byte boundary in CBC mode
3DES
des只有56位,现在已经不用了,是一个不安全的算法,意思是什么都不用知道直接暴力破解都很容易破解。3DES它的密钥长度可以为128位、192位或256位更加安全,但是由于运算速度慢,所以一般都只是使用AES堆成加密
代码实现
- java
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class TripleDESUtil {
private static final String ALGORITHM = "DESede"; // 3DES算法
private static final String MODE = "ECB"; // 加密模式
private static final String PADDING = "PKCS5Padding"; // 填充方式
// 加密方法
public static String encrypt(String input, String key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM + "/" + MODE + "/" + PADDING);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] encrypted = cipher.doFinal(input.getBytes());
return Base64.getEncoder().encodeToString(encrypted);
}
// 解密方法
public static String decrypt(String input, String key) throws Exception {
Cipher cipher = Cipher.getInstance(ALGORITHM + "/" + MODE + "/" + PADDING);
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes("UTF-8"), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(input));
return new String(decrypted);
}
public static void main(String[] args) throws Exception {
String input = "Hello World!"; // 要加密的数据
String key = "0ThisIsASecretKeyFor3DES"; // 密钥(长度必须为24字节)
String encrypted = encrypt(input, key);
String decrypted = decrypt(encrypted, key);
System.out.println("原始数据:" + input);
System.out.println("加密后:" + encrypted);
System.out.println("解密后:" + decrypted);
}
}
输出结果
原始数据:Hello World!
加密后:uS25visNGcCP38sOltGuFA==
解密后:Hello World!
- python
from Crypto.Cipher import DES3
import base64
def pad(data):
length = 16 - (len(data) % 16)
return data + bytes([length] * length)
def unpad(data):
length = data[-1]
return data[:-length]
def encrypt(input, key):
cipher = DES3.new(key, DES3.MODE_ECB)
padded_input = pad(input.encode('utf-8'))
encrypted = cipher.encrypt(padded_input)
return base64.b64encode(encrypted).decode('utf-8')
def decrypt(input, key):
cipher = DES3.new(key, DES3.MODE_ECB)
decoded = base64.b64decode(input.encode('utf-8'))
decrypted = cipher.decrypt(decoded)
return unpad(decrypted).decode('utf-8')
if __name__ == '__main__':
input = 'Hello World!'
key = '0ThisIsASecretKeyFor3DES'
# 如果不足24位回报错ValueError: Not a valid TDES key,可以做一下补齐
key_bytes = key.encode('utf-8') + bytes(24 - len(key.encode('utf-8'))) # 调整长度为24字节
encrypted = encrypt(input, key_bytes)
decrypted = decrypt(encrypted, key_bytes)
print('原始数据:', input)
print('加密后:', encrypted)
print('解密后:', decrypted)
输出结果
原始数据: Hello World!
加密后: uS25visNGcCP38sOltGuFA==
解密后: Hello World!
注意事项
- 几个变量要注意加密算法、加密模式、填充方式
// java
private static final String ALGORITHM = "DESede"; // 3DES算法
private static final String MODE = "ECB"; // 加密模式
private static final String PADDING = "PKCS5Padding"; // 填充方式
Cipher cipher = Cipher.getInstance(ALGORITHM + "/" + MODE + "/" + PADDING);
// python
# 创建DES3对象并指定填充方式为PKCS#7
cipher = DES3.new(key, DES3.MODE_CBC, iv, padding=pad)
// 也可以写成默认的
cipher = DES3.new(key, DES3.MODE_ECB)
// 如果是ISO10126,也是改一下ISO。但是一般都用默认的,因为比较安全
cipher = DES3.new(key, DES3.MODE_CBC, iv, padding=ISO10126.pad, segment_size=64)
填充方式有以下几种
-
- NoPadding:不做任何填充,要求明文长度必须是分组长度的整数倍,否则无法加密。加密后的密文长度等于明文长度。
-
- ZeroPadding:使用0x00字节来填充,要求明文长度必须是分组长度的整数倍,否则无法加密。加密后的密文长度等于明文长度加上填充字节数。
-
- ISO10126Padding:使用随机生成的字节来填充,最后一个字节表示填充的字节数,其他填充字节随机生成。加密后的密文长度等于明文长度加上填充字节数。
-
- ANSI X.923 Padding:使用0x00字节来填充,最后一个字节表示填充的字节数,其他填充字节都是0x00。加密后的密文长度等于明文长度加上填充字节数
- key的长度必须是24位,不够24位会报错.补齐时要看源代码是怎么补齐的
# 如果不足24位会报错ValueError: Not a valid TDES key,可以做一下补齐
key = '0ThisIsASecretKeyFor3DES'
# 不足24位补齐24位
key_bytes = key.encode('utf-8') + bytes(24 - len(key.encode('utf-8'))) # 调整长度为24字节
encrypted = encrypt(input, key_bytes)