常见加解密算法04 - 分组密码DES

270 阅读11分钟

​各位才华横溢,风度翩翩的读者们,你们好。今天我们讨论一下DES算法以及逆向识别。

DES算法要比RC4复杂的多,但是幸运的是它的逆向识别比RC4要简单很多,当你了解DES大致的实现原理之后就明白为什么了。

DES算法介绍

DES算法,全称是数据加密标准(Data Encryption Standard),是一种对称密钥加密技巧,也就是说,加密和解密都用同一个密钥。它是分组加密算法。分组肯定要补位,关于补位的知识,我们后面再说。

DES算法实现过程

我找了一个讲的非常好的视频,还是一个妹子讲的:

www.bilibili.com/video/BV1KQ…

其实看视频就完全懂了,这里为了后面方便回忆,贴一张图:

ctf-wiki.org/crypto/bloc…

图中,加密过程分成了两部分。

第一部分是由一个64位的密钥来生成16个48位的子密钥。生成过程如下:

  • 先将密钥按照一定的规则打乱,其实是使用了一个置换表,这个置换表就是识别该算法的一个特征
  • 打乱时我们只保留了56位(剩下的8位用于奇偶校验,暂时不用管),分成两部分各28位。然后对这两个二进制流按一定的次数进行旋转,置换,再合并,得到一个48位的子密钥。搞16次,就得到了16个子密钥。这里也有一个置换表,是一个特征。
  • 密钥生成后,就可以与明文进行异或了。

第二部分是将明文使用密钥进行加密:

  • 将明文分成两个32位的,将右边的与子密钥进行异或,但是一个32位,一个48位,没法直接异或,所以采用一定的规则将32位的数据扩展成48位的,然后再进行异或。有兴趣的可以去看看视频,非常的精彩。
  • 异或的结果,分为 8 组,每一组是 6-bit 的数据,丢进对应的 S 盒,输出 4-bit 的信息。把这些输出收集起来,一共是 4*8 = 32 位,做一次置换 (P 置换),得到 32-bit 的结果。这个步骤里面的 S 盒与 P 置换都是算法识别的特征。而且为了加快DES速度,还可以将 S 与 P 进行合并。

好了,现在你已经精通DES算法了,去手搓一个吧。

未标题-1.jpg

DES算法识别

Java版

一般Java里面都是使用自带的 Cipher 库来实现,所以我们可以直接搜索相关类就行了,虽然有些App使用反射与字符串加密来隐藏实现,但是我们也可以直接使用 frida 等来进行Hook。

    /**
     * DES算法,加密
     *
     * @param data 待加密字符串
     * @param key  加密私钥,长度不能够小于8位
     * @return 加密后的字节数组,一般结合Base64编码使用
     */
    public static String encode(String keybyte[] data) {
        try {
            //Class CipherClass=MainActivity.class.getClassLoader().loadClass("javax.crypto.Cipher");
            Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            IvParameterSpec iv = new IvParameterSpec(IVPARAMETERSPEC.getBytes());
            cipher.init(Cipher.ENCRYPT_MODE, getRawKey(key), iv);
            byte[] bytes = cipher.doFinal(data);
            return Base64.encodeToString(bytes, Base64.DEFAULT);
        } catch (Exception e) {
            return null;
        }
    }

Hook代码:

function main() {
    Java.perform(function () {
        Java.use('javax.crypto.Cipher').getInstance.overload('java.lang.String').implementation = function (arg0) {
            console.log('javax.crypto.Cipher.getInstance is called!', arg0);
            var result = this.getInstance(arg0);
            return result;
        };

        Java.use('javax.crypto.Cipher').init.overload('int''java.security.Key''java.security.spec.AlgorithmParameterSpec').implementation = function (arg0, arg1, arg2) {
            var mode = arg0;
            var key = arg1;
            var iv = arg2;
            var KeyClass = Java.use('java.security.Key');

            var keyobj = Java.cast(key, KeyClass);
            var key_bytes = keyobj.getEncoded();
            var IVClass = Java.use('javax.crypto.spec.IvParameterSpec');
            var ivobj = Java.cast(iv, IVClass);
            var iv_bytes = ivobj.getIV();
            console.log('javax.crypto.Cipher.init is called!', mode, JSON.stringify(key_bytes), JSON.stringify(iv_bytes));
            var result = this.init(arg0, arg1, arg2);
            return result;
        };

        Java.use('javax.crypto.Cipher').doFinal.overload('[B').implementation = function (arg0) {
            console.log('javax.crypto.Cipher.doFinal is called!'JSON.stringify(arg0));
            var data = arg0;
            var result = this.doFinal(arg0);
            console.log('javax.crypto.Cipher.doFinal is called!'JSON.stringify(data), "encrypt:"JSON.stringify(result));
            return result;
        };
    })

}

setImmediate(main);

逻辑很直白,就不解释了,找到对应的函数干就完事了。

C版

贴部分代码,一个标准的DES实现里面的一些表:

//IP初始置换表
int IP_Table[64] = {
        585042342618102605244362820124,
        625446383022146645648403224168,
        57494133251791595143352719113,
        615345372921135635547393123157
};

//IP逆置换表
int IPR_Table[64] = {
        408481656246432397471555236331,
        386461454226230375451353216129,
        364441252206028353431151195927,
        34242105018582633141949175725
};

//f函数中E位选择表(扩展置换表)
int E_Table[48] = {
        3212345456789,
        8910111213121314151617,
        161718192021202122232425,
        24252627282928293031321
};
//f函数中的p表(4x8)
int P_Table[32] = {
        16720212912281711523265183110,
        28241432273919133062211425
};

int PC1_Table[56] = {
        57494133251791585042342618,
        10259514335271911360524436,
        635547393123157625446383022,
        1466153453729211352820124
};
//PC2选位表(密钥生成置换表2)
int PC2_Table[48] = {
        14171124153281562110,
        23191242681672720132,
        415231374755304051453348,
        444939563453464250362932
};
//左移位数表
int LOOP_Table[16] = {
        1122222212222221
};

// S盒
int S_Box[8][4][16] = {
        // S1
        1441312151183106125907,
        0157414213110612119538,
        4114813621115129731050,
        1512824917511314100613,
        //S2
        1518146113497213120510,
        3134715281412011069115,
        0147111041315812693215,
        1381013154211671205149,
        //S3
        1009146315511312711428,
        1370934610285141211151,
        1364981530111212510147,
        1101306987415143115212,
        //S4
        7131430691012851112415,
        1381156150347212110149,
        1069012117131513145284,
        3150610113894511127214,
        //S5
        2124171011685315130149,
        1411212471315015103986,
        4211110137815912563014,
        1181271142136150910453,
        //S6
        1211015926801334147511,
        1015427129561131401138,
        9141552812370410113116,
        4321295151011141760813,
        //S7
        4112141508133129751061,
        1301174911014351221586,
        1411131237141015680592,
        6111381410795015142312,
        //S8
        1328461511110931450127,
        1151381037412561101492,
        7114191214206101315358,
        2114741081315129035611
};

上面的各种数组表,都是识别DES算法的特征。因为这些数组的值都是固定的,在编译之后也是按顺序储存在 data 段的。

所以,就有一些项目是根据特定的数据流来识别算法,比如:

github.com/polymorf/fi…

这是一个IDA插件,我们直接看它定义的一些DES识别规则:

rule DES_sbox
{ meta:
  author = "_pusher_"
  date = "2015-05"
  description = "DES [sbox]"
 strings:
  $c0 = { 00 04 01 01 00 00 00 00 00 00 01 00 04 04 01 01 04 00 01 01 04 04 01 00 04 00 00 00 00 00 01 00 00 04 00 00 00 04 01 01 04 04 01 01 00 04 00 00 04 04 00 01 04 00 01 01 00 00 00 01 04 00 00 00 }
 condition:
  $c0
}

这里定义了一个数据流:00 04 xx xx xx。

我们将它与标准算法的 S 盒比较,发现它并不一样,这是因为现在有一些实现为了速度,将 s 与 p 合并了,具体实现可参考如下项目代码:

github.com/weidai11/cr…

看里面的 spbox:

const word32 RawDES::Spbox[8][64] = {
{
0x01010400,0x00000000,0x00010000,0x010104040x01010004,0x00010404,0x00000004,0x00010000,
0x00000400,0x01010400,0x01010404,0x000004000x01000404,0x01010004,0x01000000,0x00000004,
0x00000404,0x01000400,0x01000400,0x000104000x00010400,0x01010000,0x01010000,0x01000404,
0x00010004,0x01000004,0x01000004,0x000100040x00000000,0x00000404,0x00010404,0x01000000,
0x00010000,0x01010404,0x00000004,0x010100000x01010400,0x01000000,0x01000000,0x00000400,
0x01010004,0x00010000,0x00010400,0x010000040x00000400,0x00000004,0x01000404,0x00010404,
0x01010404,0x00010004,0x01010000,0x010004040x01000004,0x00000404,0x00010404,0x01010400,
0x00000404,0x01000400,0x01000400,0x000000000x00010004,0x00010400,0x00000000,0x01010004},
...
}

这个序列就与 findcrypt 里面定义的是一样的了。

知道了 findcrypt 的原理后,我们也可以添加一些标准的 DES 算法识别规则。