DES算法
DES加密算法
简介
❝DES是一种使用56位秘钥对64位长分组进行加密的加密算法。
❞
DES分组密码
DES是一种对称密码,加密和解密使用的是相同的密钥。DES对明文当中的每个分组加密过程都包含了16轮,每一轮的操作相同,且每一轮加密使用的子密钥为主密钥推导而来。
DES迭代结构
在DES加密当中使用了Feistal结构,下面给出DES的Feistal结构图。
DES的Fesital结构
算法描述
下面在这里具体描述一下DES的算法结构。
初始置换和逆初始置换
初始置换和逆初始置换都是按位置换,这里通过初始置换表和逆初始置换表来实现,做了一个映射。
初始置换和逆初始置换
f函数
f函数是DES安全性的核心,首先通过扩展算法把32位的块扩展到48位,然后和每一轮的密钥(48bit)进行异或,之后通过SBox替换得到32位的输出,最后通过置换得到最终的加密结果。
F函数
密钥编排
对于DES来说,实际密钥有效的只有56位,然后通过这56位的密钥生成16轮的子密钥,每个子密钥长度为48位,其规则如下。
- 将原始的密钥分成两部分
- 在第1, 2, 9, 16轮当中,左右两部分分别向左移动一位
- 其他轮中,左右两部分向左移动两位
这样做之后会使得C0 = C16
, D0 = D16
。
解密过程
解密过程和加密过程类似,倒过来安排一遍就好了。
完整算法实现
// region constant pub static IP: [u8; 64] = [ 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 ]; pub static IP_REVERSE: [u8; 64] = [ 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 ]; pub static E: [u8; 48] = [ 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 ]; pub static S1: [u8; 64] = [ 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 ]; pub static S2: [u8; 64] = [ 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 ]; pub static S3: [u8; 64] = [ 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 ]; pub static S4: [u8; 64] = [ 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 ]; pub static S5: [u8; 64] = [ 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 ]; pub static S6: [u8; 64] = [ 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 ]; pub static S7: [u8; 64] = [ 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 ]; pub static S8: [u8; 64] = [ 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 ]; pub static BOX_LOOKUP: [usize; 64] = [ 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, 32, 48, 33, 49, 34, 50, 35, 51, 36, 52, 37, 53, 38, 54, 39, 55, 40, 56, 41, 57, 42, 58, 43, 59, 44, 60, 45, 61, 46, 62, 47, 63 ]; pub static BOXES: [[u8; 64]; 8] = [ S1, S2, S3, S4, S5, S6, S7, S8 ]; pub static P: [u8; 32] = [ 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 ]; pub static PC1: [u8; 56] = [ 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 ]; pub static PC2: [u8; 48] = [ 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 ]; pub static LSHIFTS: [u8; 16] = [ 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 ]; // endregion pub struct DES { } impl DES { fn permute(block: u64, block_size: u8, permutation: &[u8]) -> u64 { let mut result = 0u64; for &i in permutation.iter() { result <<= 1; let p = block_size - i; result |= (block & (1 << p)) >> p; } return result; } fn generate_round_keys(key: u64) -> [u64; 16] { let mut keys = [0u64; 16]; let mut cd = DES::permute(key, 64, &PC1); let data_mask = (1 << 55) | (1 << 27); let zero_mask = (0xFF << 56) | (1 << 28); for (i, &j) in LSHIFTS.iter().enumerate() { for _ in 0..j { let data = (cd & data_mask) >> 27; cd = (cd << 1) & !zero_mask | data; } keys[i] = DES::permute(cd, 56, &PC2); } return keys; } fn f(block: u64, key: u64) -> u64 { let mut result = 0u64; let tmp = DES::permute(block, 32, &E) ^ key; let mask = 0b111111u64; for (i, s_box) in BOXES.iter().enumerate() { let val: u64 = (tmp & (mask << (42 - (i * 6)))) >> (42 - (i * 6)); result = (result << 4) | s_box[BOX_LOOKUP[val as usize]] as u64; } return DES::permute(result, 32, &P); } fn feistel(block: u64, keys: [u64; 16]) -> u64 { let lr = DES::permute(block, 64, &IP); let mut l: u64 = (lr & 0xFF_FF_FF_FF_00_00_00_00) >> 32; let mut r: u64 = lr & 0x00_00_00_00_FF_FF_FF_FF; for &key in keys.iter() { let tmp: u64 = l; l = r; r = tmp ^ DES::f(r, key); } let switched: u64 = (r << 32) | l; return DES::permute(switched, 64, &IP_REVERSE); } fn encrypt_block(block: u64, keys: [u64; 16]) -> u64 { return DES::feistel(block, keys); } fn decrypt_block(block: u64, keys: [u64; 16]) -> u64 { let mut rks = [0u64; 16]; for (i, &key) in keys.iter().rev().enumerate() { rks[i] = key; } return DES::feistel(block, rks); } fn encrypt(input: Vec<u64>, key: u64) -> Vec<u64> { let keys = DES::generate_round_keys(key); let mut result = vec![0u64; input.len()]; for (i, &block) in input.iter().enumerate() { result[i] = DES::encrypt_block(block, keys); } return result; } fn decrypt(input: Vec<u64>, key: u64) -> Vec<u64> { let keys = DES::generate_round_keys(key); let mut result = vec![0u64; input.len()]; for (i, &block) in input.iter().enumerate() { result[i] = DES::decrypt_block(block, keys); } return result; } } #[cfg(test)] mod test { use crate::des::DES; #[test] fn test() { let result = DES::encrypt(vec![100], 50); println!("{:?}", result); let result = DES::decrypt(result, 50); println!("{: ?}", result); } }
❝代码仅供参考学习使用(简单的实现),未经过严格测试,请不要直接用于生产用途,避免造成损失
❞
算法实战
还是以Android作为例子,如果仅仅使用Java层,还是非常容易通过hook
从而拿到加密算法和密钥,先来看一个简单的Java实现的例子:
private String encrypt(String plaintext, String key) { String ciphertext = ""; try { DESKeySpec keySpec = new DESKeySpec(key.getBytes()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); @SuppressLint("GetInstance") Cipher cipher = Cipher.getInstance("DES/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, keyFactory.generateSecret(keySpec)); ciphertext = Base64.encodeToString(cipher.doFinal(plaintext.getBytes()), Base64.DEFAULT); } catch (Exception e) { e.printStackTrace(); } return ciphertext; }
这里我采用了一个不安全的模式ECB
, 并且不设置Padding), 默认输入的内容是满足分组要求的,这个模式是不需要初始向量的,并且各个分组之间也没有什么关系,有关模式的详细知识,给我自己挖一个坑,后续有时间单独写一篇文章来说明模式。
❝注意: 生产环境大家要采取安全的模式,不要用ECB!!! 这里仅做研究学习使用!!!
❞
这里采取Java原生的写法实际上是很容易被看出来的,如果不加字符串混淆的话,直接搜索字符串即可拿到算法,添加字符串混淆也可以非常容易的通过hook来直接确定算法。
简单用frida
写一个测试脚本,用xposed
也可以实现,为了方便测试直接用frida
写了。
function hook () { Java.perform(function () { const javax_crypto_Cipher_class = Java.use('javax.crypto.Cipher') javax_crypto_Cipher_class['getInstance'].overload('java.lang.String').implementation = function (alg) { console.log(alg) return this.getInstance(alg) } javax_crypto_Cipher_class['init'].overload('int', 'java.security.Key').implementation = function (mode, key) { console.log(mode, key.getEncoded()) return this.init(mode, key) } javax_crypto_Cipher_class['doFinal'].overload('[B').implementation = function (content) { console.log(content) return this.doFinal(content) } }) }
这样,轻松的拿到了明文和密钥,如果想加强安全性,核心算法还是放到native层吧。下面用c++实现一下算法, 依然采用ECB/NoPadding
的模式。
#include <cstdio> #include <cstring> #include <cmath> #include <android/log.h> const unsigned char IP[] = { 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 }; const unsigned char IP_REVERSE[] = { 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 }; const unsigned char E[] = { 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 }; const unsigned char BOX_LOOKUP[] = { 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23, 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31, 32, 48, 33, 49, 34, 50, 35, 51, 36, 52, 37, 53, 38, 54, 39, 55, 40, 56, 41, 57, 42, 58, 43, 59, 44, 60, 45, 61, 46, 62, 47, 63 }; const unsigned char BOXES[8][64] = { { 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 }, { 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 }, { 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 }, { 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 }, { 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 }, { 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 }, { 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 }, { 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 }, }; const unsigned char P[] = { 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25 }; const unsigned char PC1[] = { 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 }; const unsigned char PC2[] = { 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 }; const unsigned char LSHIFTS[] = { 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 }; void u64to8(char msg[], uint64_t num) { for (int i = 0; i < 8; i++) { msg[i] = num >> (8 - 1 - i) * 8; } } uint64_t u8to64(const char a[]) { uint64_t n; n = (((uint64_t) a[0] << 56) & 0xFF00000000000000LLU) | (((uint64_t) a[1] << 48) & 0x00FF000000000000LLU) | (((uint64_t) a[2] << 40) & 0x0000FF0000000000LLU) | (((uint64_t) a[3] << 32) & 0x000000FF00000000LLU) | ((a[4] << 24) & 0x00000000FF000000LLU) | ((a[5] << 16) & 0x0000000000FF0000LLU) | ((a[6] << 8) & 0x000000000000FF00LLU) | (a[7] & 0x00000000000000FFLLU); return n; } uint64_t permute(uint64_t block, int block_size, const uint8_t permutation[], size_t size) { uint64_t result = 0; for (int i = 0; i < size; ++i) { result <<= 1; int p = block_size - permutation[i]; result |= (block & (1ULL << p)) >> p; } return result; } void generate_round_keys(uint64_t key, uint64_t *keys) { uint64_t cd = permute(key, 64, PC1, 56); uint64_t data_mask = (1ULL << 55) | (1ULL << 27); // 这里如果用!有问题,写死固定值了 uint64_t non_zero_mask = 72057593769492479; for (int i = 0; i < 16; ++i) { for (int j = 0; j < LSHIFTS[i]; ++j) { uint64_t data = (cd & data_mask) >> 27; cd = (cd << 1) & non_zero_mask | data; } keys[i] = permute(cd, 56, PC2, 48); } } uint64_t f(uint64_t block, uint64_t key) { uint64_t result = 0; uint64_t tmp = permute(block, 32, E, 48) ^key; uint64_t mask = 0b111111; for (int i = 0; i < 8; ++i) { uint64_t val = (tmp & (mask << (42 - (i * 6)))) >> (42 - (i * 6)); result = (result << 4) | BOXES[i][BOX_LOOKUP[val]]; } return permute(result, 32, P, 32); } uint64_t feistel(uint64_t block, const uint64_t *keys) { uint64_t lr = permute(block, 64, IP, 64); uint64_t l = (lr & 0xFFFFFFFF00000000LLU) >> 32; uint64_t r = lr & 0x00000000FFFFFFFFLLU; for (int i = 0; i < 16; ++i) { uint64_t tmp = l; l = r; r = tmp ^ f(r, keys[i]); } uint64_t switched = (r << 32) | l; return permute(switched, 64, IP_REVERSE, 64); } uint64_t encrypt_block(uint64_t block, uint64_t *keys) { return feistel(block, keys); } uint64_t decrypt_block(uint64_t block, const uint64_t *keys) { uint64_t rks[16] = {}; for (int i = 15; i >= 0; --i) { rks[15 - i] = keys[i]; } return feistel(block, rks); } /** * DES(NoPadding/ECB) * @param input * @param key * @param output */ void encrypt(const char *input, uint64_t key, char output[], size_t input_size) { size_t count = ceil(input_size / 8.0); uint64_t keys[16] = {0}; generate_round_keys(key, keys); for (int i = 0; i < count; ++i) { uint64_t input_64 = u8to64(input); uint64_t stream = encrypt_block(input_64, keys); u64to8(output, stream); input += 8; output += 8; } }
简单的做一个native层的调用,就直接贴代码了。
extern "C" JNIEXPORT jbyteArray JNICALL Java_com_littleq_cryptography_DESDemoActivity_nativeEncrypt( JNIEnv *env, jobject thiz, jbyteArray plaintext, jbyteArray key) { size_t plaintext_size = env->GetArrayLength(plaintext); auto *plaintext_dst = (jbyte *) malloc(plaintext_size * sizeof(jbyte)); auto *key_dst = (jbyte *) malloc(8 * sizeof(jbyte)); env->GetByteArrayRegion(plaintext, 0, plaintext_size, plaintext_dst); env->GetByteArrayRegion(key, 0, 8, key_dst); char *output = (char *) malloc(plaintext_size * sizeof(char)); encrypt(reinterpret_cast<const char *>(plaintext_dst), u8to64(reinterpret_cast<const char *>(key_dst)), output, plaintext_size); jbyteArray byteArray = env->NewByteArray(plaintext_size); env->SetByteArrayRegion(byteArray, 0, plaintext_size, (jbyte *) output); return byteArray; }
放到native层,分析出这个算法的难度就比较高了,直接通过hook系统函数的方法就失效了,如果这算法没魔改过的话,采用原始的DES实现,找S-Box
是一个不错的切入点,比如直接用010Editor
打开so, 然后搜索相关的字面量,由于我知道我这里用的是unsigned char
, 所以没有其他补位,直接搜索0E 04 0D 01
, 如果存储类型不为unsigned char
, 搜索内容可能不一样,比如如果使用int
进行存储,那么需要搜索0E 00 00 00 04 00 00 00 0D 00 00 00 01 00 00 00
。
DES-S-Box
不过这个方案有一定的局限性,有这个不一定能确定他一定是DES算法,如果我把所有的算法的常量都给扔进去,然后随机用用,添加点无效指令,感觉这误导性还是蛮强的,实际分析so在这里我就不展开分析了,自己写的算法,分析起来,目的性还是挺强的,有兴趣的大佬可以自行尝试一下吧。
总结
如果加密算法完整放在Java层,对于逆向分析而言,我个人感觉分析起来还是比较容易的,直接hook Java层的函数,便可以快速的分析出算法。而对于放在native层的加密算法来说,可以通过S-Box的特征来大概确定一下算法(如果只凭借S-Box, 无法最终确定是不是DES),后续还需要一定的分析。最后,本文采用了不安全的分组模式ECB
(仅供学习参考使用,实现简单,没有其他的干扰), 切记,不要用于生产系统,以免造成损失!!!