侧边栏壁纸
博主头像
Fup1p1 's Blog 博主等级

梦想是财富自由~

  • 累计撰写 38 篇文章
  • 累计创建 24 个标签
  • 累计收到 9 条评论

目 录CONTENT

文章目录

【Reverse】逆向中常见加密(编码)整理(缓慢更新ing)

Fup1p1
2023-05-11 / 0 评论 / 1 点赞 / 1050 阅读 / 0 字 / 正在检测是否收录...

前言

发现还是得比较细致地了解一下常见的加密算法(想特别深入地了解,感觉目前水平做不到,需要特别强的数学功底。),这样比赛的时候或者实战的时候,能更加快地去定位到它所使用的加密算法。
你会说,为什么不用findcrypt这样的插件,感觉还是不能依赖,而且很多魔改过的加密其实这个插件也都识别不出来。
所以争取在国赛前,把目前碰到的一些加密算法都尽可能更深入地了解一下。希望今年的国赛,能为团队出点力,争取拿个奖吧!

单向散列算法

MD5

加密步骤

1.对明文进行Hex编码

比如
Fup1p1 -> 46 75 70 31 70 31

2.填充(Padding)

因为md5是一组一组加密的,一组的长度是512bit,所以如果你输入的长度不足448bit,那么就需要填充,也就是padding。

怎么padding呢?

先填充一个1,然后后面跟上对应个数的0

搜嘎,也就是 1000 0000 0000 0000 … ->hex 80 00 …

但是为什么是填充到448bit,而不是填充至512bit呢?

因为剩余的64bit需要表示消息长度!那么如果消息长度大于2^64呢?那就取最低64bit!而且,注意是小端序(MD5算法使用的是小端序字节序)!!!这同时也是为什么md5加密不限制长度的原因。

那么如果我的数据长度大于448bit,但是小于512bit,又怎么添加64bit的消息长度呢?

这时候,就需要填充的时候,填充长度至模512能刚好被448整除为止。

3.将明文进行分组,分成16块(M1~M16)

512 bit/16 = 32bit

4.初始化MD5常量

A=0x67452301
B=0xefcdab89
C=0x98badcfe
D=0x10325476

5.MD5Transform(核心)

image-1683812833350
MD5的核心就是重复这个运算64次(也可以理解为4轮运算,重复16次)。这个运算也很明显,每一轮计算一个常量的值,然后其他三个常量的值只是换位。

• 图中的田 代表相加

• 图中的F函数是由四个函数组合而成的

• K表在代码中体现为常量(为了效率),但是其实有公式去计算的 2^32*(int)abs(sin(i)), i从1到64。

• <<<s 代表的是循环左移!s是定值,或者有一个表。这个值一般不会改。(有安全性问题的SHA0,通过循环左移1位,解决了这个问题,也就是后面我们经常使用到的SHA1)

• 最后四个初始化常量不断变化后得到的值,拼接后就是最终的摘要结果。

FF(a,b,c,d,Mj,s,ti)  a=b+(a+F(b,c,d)+Mj+ti)<<s    

GG(a,b,c,d,Mj,s,ti)  a=b+(a+G(b,c,d)+Mj+ti)<< s    

HH(a,b,c,d,Mj,s,ti)  a=b+(a+H(b,c,d)+Mj+ti)<< s

II(a,b,c,d,Mj,s,ti)  a=b+(a+I(b,c,d)+Mj+ti)<< s

找一份C实现的源码来分析(当然,有很多实现方式,下面源码只是一种)

//MD5.h
#ifndef HOOKDEMO_MD5_H
#define HOOKDEMO_MD5_H

typedef struct {
    unsigned int count[2];//分成两个数组来存放长度的bit。
    unsigned int state[4];//存放计算后四个常量
    unsigned char buffer[64];//缓冲区
} MD5_CTX;

#define F(x,y,z) ((x & y) | (~x & z))
#define G(x,y,z) ((x & z) | (y & ~z))
#define H(x,y,z) (x^y^z)
#define I(x,y,z) (y ^ (x | ~z))
#define ROTATE_LEFT(x,n) ((x << n) | (x >> (32-n)))
#define FF(a,b,c,d,x,s,ac) \
          { \
          a += F(b,c,d) + x + ac; \
          a = ROTATE_LEFT(a,s); \
          a += b; \
          }
#define GG(a,b,c,d,x,s,ac) \
          { \
          a += G(b,c,d) + x + ac; \
          a = ROTATE_LEFT(a,s); \
          a += b; \
          }
#define HH(a,b,c,d,x,s,ac) \
          { \
          a += H(b,c,d) + x + ac; \
          a = ROTATE_LEFT(a,s); \
          a += b; \
          }
#define II(a,b,c,d,x,s,ac) \
          { \
          a += I(b,c,d) + x + ac; \
          a = ROTATE_LEFT(a,s); \
          a += b; \
          }
void MD5Init(MD5_CTX* context);
void MD5Update(MD5_CTX* context, unsigned char* input, unsigned int inputlen);
void MD5Final(MD5_CTX* context, unsigned char digest[16]);
void MD5Transform(unsigned int state[4], unsigned char block[64]);
void MD5Encode(unsigned char* output, unsigned int* input, unsigned int len);
void MD5Decode(unsigned int* output, unsigned char* input, unsigned int len);

#endif //HOOKDEMO_MD5_H
#include <iostream>
#include <memory.h>
#include <string.h>
#include "MD5.h"

using namespace std;

unsigned char PADDING[] = {0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                           0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};


void MD5Init(MD5_CTX *context) {
    context->count[0] = 0;
    context->count[1] = 0;
    context->state[0] = 0x67452301;
    context->state[1] = 0xEFCDAB89;
    context->state[2] = 0x98BADCFE;
    context->state[3] = 0x10325476;
}

void MD5Update(MD5_CTX *context, unsigned char *input, unsigned int inputlen) {
    unsigned int i = 0, index = 0, partlen = 0;
    index = (context->count[0] >> 3) 
    partlen = 64 - index;
    context->count[0] += inputlen << 3;& 0x3F;//字节转换成bit
    if (context->count[0] < (inputlen << 3))如果溢出
        context->count[1]++;//为什么需要+1,因为分成了两个数组来存放高位32和低位32,由于无符号32位只能存2^32-1,所以在进位的时候还得补上一个1!
    context->count[1] += inputlen >> 29;   //count[1]存放高位32数据。

    if (inputlen >= partlen) {				//如果大于512bit,就直接transform了
        memcpy(&context->buffer[index], input, partlen);
        MD5Transform(context->state, context->buffer);
        for (i = partlen; i + 64 <= inputlen; i += 64)
            MD5Transform(context->state, &input[i]);
        index = 0;
    } else {
        i = 0;
    }
    memcpy(&context->buffer[index], &input[i], inputlen - i);
}

void MD5Final(MD5_CTX *context, unsigned char digest[16]) {
    unsigned int index = 0, padlen = 0;
    unsigned char bits[8];
    index = (context->count[0] >> 3) & 0x3F;
    padlen = (index < 56) ? (56 - index) : (120 - index);
    MD5Encode(bits, context->count, 8);       //对消息程度进行了端序转换
    MD5Update(context, PADDING, padlen);//    填充padding
    MD5Update(context, bits, 8);   //	填充最后的64bit(消息长度)
    printf("%.8x\n",  context->state[0]);
    printf("%.8x\n",  context->state[1]);
    printf("%.8x\n",  context->state[2]);
    printf("%.8x\n",  context->state[3]);
    MD5Encode(digest, context->state, 16); //端序转换
}

void MD5Encode(unsigned char *output, unsigned int *input, unsigned int len) {
    unsigned int i = 0, j = 0;
    while (j < len) {
        output[j] = input[i] & 0xFF;
        output[j + 1] = (input[i] >> 8) & 0xFF;
        output[j + 2] = (input[i] >> 16) & 0xFF;
        output[j + 3] = (input[i] >> 24) & 0xFF;
        i++;
        j += 4;
    }
}

void MD5Decode(unsigned int *output, unsigned char *input, unsigned int len) {
    unsigned int i = 0, j = 0;
    while (j < len) {
        output[i] = (input[j]) |
                    (input[j + 1] << 8) |
                    (input[j + 2] << 16) |
                    (input[j + 3] << 24);
        i++;
        j += 4;
    }
}

void MD5Transform(unsigned int state[4], unsigned char block[64]) {   //这么写可能会很冗余,有些写法是直接switch case,比较美观,因为这64轮加密实际上就是4轮加密,每轮运算16次。
    unsigned int a = state[0];
    unsigned int b = state[1];
    unsigned int c = state[2];
    unsigned int d = state[3];
    unsigned int x[64];
    MD5Decode(x, block, 64);
    FF(a, b, c, d, x[0], 7, 0xd76aa478); /* 1 */
    FF(d, a, b, c, x[1], 12, 0xe8c7b756); /* 2 */
    FF(c, d, a, b, x[2], 17, 0x242070db); /* 3 */
    FF(b, c, d, a, x[3], 22, 0xc1bdceee); /* 4 */
    FF(a, b, c, d, x[4], 7, 0xf57c0faf); /* 5 */
    FF(d, a, b, c, x[5], 12, 0x4787c62a); /* 6 */
    FF(c, d, a, b, x[6], 17, 0xa8304613); /* 7 */
    FF(b, c, d, a, x[7], 22, 0xfd469501); /* 8 */
    FF(a, b, c, d, x[8], 7, 0x698098d8); /* 9 */
    FF(d, a, b, c, x[9], 12, 0x8b44f7af); /* 10 */
    FF(c, d, a, b, x[10], 17, 0xffff5bb1); /* 11 */
    FF(b, c, d, a, x[11], 22, 0x895cd7be); /* 12 */
    FF(a, b, c, d, x[12], 7, 0x6b901122); /* 13 */
    FF(d, a, b, c, x[13], 12, 0xfd987193); /* 14 */
    FF(c, d, a, b, x[14], 17, 0xa679438e); /* 15 */
    FF(b, c, d, a, x[15], 22, 0x49b40821); /* 16 */

    /* Round 2 */
    GG(a, b, c, d, x[1], 5, 0xf61e2562); /* 17 */
    GG(d, a, b, c, x[6], 9, 0xc040b340); /* 18 */
    GG(c, d, a, b, x[11], 14, 0x265e5a51); /* 19 */
    GG(b, c, d, a, x[0], 20, 0xe9b6c7aa); /* 20 */
    GG(a, b, c, d, x[5], 5, 0xd62f105d); /* 21 */
    GG(d, a, b, c, x[10], 9, 0x2441453); /* 22 */
    GG(c, d, a, b, x[15], 14, 0xd8a1e681); /* 23 */
    GG(b, c, d, a, x[4], 20, 0xe7d3fbc8); /* 24 */
    GG(a, b, c, d, x[9], 5, 0x21e1cde6); /* 25 */
    GG(d, a, b, c, x[14], 9, 0xc33707d6); /* 26 */
    GG(c, d, a, b, x[3], 14, 0xf4d50d87); /* 27 */
    GG(b, c, d, a, x[8], 20, 0x455a14ed); /* 28 */
    GG(a, b, c, d, x[13], 5, 0xa9e3e905); /* 29 */
    GG(d, a, b, c, x[2], 9, 0xfcefa3f8); /* 30 */
    GG(c, d, a, b, x[7], 14, 0x676f02d9); /* 31 */
    GG(b, c, d, a, x[12], 20, 0x8d2a4c8a); /* 32 */

    /* Round 3 */
    HH(a, b, c, d, x[5], 4, 0xfffa3942); /* 33 */
    HH(d, a, b, c, x[8], 11, 0x8771f681); /* 34 */
    HH(c, d, a, b, x[11], 16, 0x6d9d6122); /* 35 */
    HH(b, c, d, a, x[14], 23, 0xfde5380c); /* 36 */
    HH(a, b, c, d, x[1], 4, 0xa4beea44); /* 37 */
    HH(d, a, b, c, x[4], 11, 0x4bdecfa9); /* 38 */
    HH(c, d, a, b, x[7], 16, 0xf6bb4b60); /* 39 */
    HH(b, c, d, a, x[10], 23, 0xbebfbc70); /* 40 */
    HH(a, b, c, d, x[13], 4, 0x289b7ec6); /* 41 */
    HH(d, a, b, c, x[0], 11, 0xeaa127fa); /* 42 */
    HH(c, d, a, b, x[3], 16, 0xd4ef3085); /* 43 */
    HH(b, c, d, a, x[6], 23, 0x4881d05); /* 44 */
    HH(a, b, c, d, x[9], 4, 0xd9d4d039); /* 45 */
    HH(d, a, b, c, x[12], 11, 0xe6db99e5); /* 46 */
    HH(c, d, a, b, x[15], 16, 0x1fa27cf8); /* 47 */
    HH(b, c, d, a, x[2], 23, 0xc4ac5665); /* 48 */

    /* Round 4 */
    II(a, b, c, d, x[0], 6, 0xf4292244); /* 49 */
    II(d, a, b, c, x[7], 10, 0x432aff97); /* 50 */
    II(c, d, a, b, x[14], 15, 0xab9423a7); /* 51 */
    II(b, c, d, a, x[5], 21, 0xfc93a039); /* 52 */
    II(a, b, c, d, x[12], 6, 0x655b59c3); /* 53 */
    II(d, a, b, c, x[3], 10, 0x8f0ccc92); /* 54 */
    II(c, d, a, b, x[10], 15, 0xffeff47d); /* 55 */
    II(b, c, d, a, x[1], 21, 0x85845dd1); /* 56 */
    II(a, b, c, d, x[8], 6, 0x6fa87e4f); /* 57 */
    II(d, a, b, c, x[15], 10, 0xfe2ce6e0); /* 58 */
    II(c, d, a, b, x[6], 15, 0xa3014314); /* 59 */
    II(b, c, d, a, x[13], 21, 0x4e0811a1); /* 60 */
    II(a, b, c, d, x[4], 6, 0xf7537e82); /* 61 */
    II(d, a, b, c, x[11], 10, 0xbd3af235); /* 62 */
    II(c, d, a, b, x[2], 15, 0x2ad7d2bb); /* 63 */
    II(b, c, d, a, x[9], 21, 0xeb86d391); /* 64 */
    state[0] += a;
    state[1] += b;
    state[2] += c;
    state[3] += d;
}

int main(){

//    void MD5Init(MD5_CTX *context);
    MD5_CTX context;
    MD5Init(&context);
//    void MD5Update(MD5_CTX *context,unsigned char *input,unsigned int inputlen);
    unsigned char* plainText = (unsigned char *) "Fup1p1";
    MD5Update(&context, plainText, strlen(reinterpret_cast<const char *>(plainText)));
//    void MD5Final(MD5_CTX *context,unsigned char digest[16]);
    unsigned char result[16];
    MD5Final(&context, result);//对最后一段进行填充、计算。

    char temp[2] = {0};
    char finalResult[33] = {0};
    for(int i = 0; i < 16; i++){
        int index = i;
        sprintf(temp, "%.2x", result[index]);
        strcat(finalResult, temp);
    }

    cout << finalResult << endl;

    return 0;
}

SHA1

和MD5一样,都是由MD4改进过来的,很多加密步骤和MD5非常类似。
image-1683896133273

字节序

和MD5的小端序不同,SHA1是大端序!并且加密中也用到的也都是大端序!

初始化常量是5个

前四个常量和MD5一样,多了一个常量。
0x67452301
0xEFCDAB89
0x98BADCFE
0x10325476
0xC3D2E1F0

分组

分组长度也是512bit,明文也要分段,但是要分成80段,相比较于MD5的16段,多出的64段是扩展出来的,并且遵循大端序的字节。

但是在分组计算的时候,又和MD5不太一样。

MD5的M盒变换

void MD5Decode(unsigned int* output, unsigned char* input, unsigned int len) {
    unsigned int i = 0, j = 0;
    while (j < len) {
        output[i] = (input[j]) |
            (input[j + 1] << 8) |
            (input[j + 2] << 16) |
            (input[j + 3] << 24);
        i++;
        j += 4;
    }
}

SHA1的W盒变换

for (t = 0; t < 16; t++) {
        W[t] = context->Message_Block[t * 4] << 24;      //很明显,和上面的MD5的区别就只是端序问题了!
        W[t] |= context->Message_Block[t * 4 + 1] << 16;
        W[t] |= context->Message_Block[t * 4 + 2] << 8;
        W[t] |= context->Message_Block[t * 4 + 3];
    }

然后多了64轮

for (t = 16; t < 80; t++) {
        W[t] = SHA1CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]);
        //W[t] = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]; SHA0是这样子的,就少了一个循环左移,却存在安全问题。
    }

分组计算(核心)

SHA1 分为4轮,每轮20次计算,总共也就是80组,其中的压缩函数也与MD5不同,sha1每轮加上的是一个定值,后面代码中体现是一个K表(4个常量)。而MD5加上的是2^32 * abs(sin(i))。

源码分析(偷了一份代码来分析)

//sha1.h
#ifndef _SHA1_H_
#define _SHA1_H_

#include <stdint.h>

#ifndef _SHA_enum_
#define _SHA_enum_
enum {
    shaSuccess = 0,
    shaNull,         /* Null pointer parameter */
    shaInputTooLong, /* input data too long */
    shaStateError    /* called Input after Result */
};
#endif
#define SHA1HashSize 20

typedef struct SHA1Context {
    uint32_t Intermediate_Hash[SHA1HashSize / 4]; /* Message Digest */
    uint32_t Length_Low; /* Message length in bits */
    uint32_t Length_High; /* Message length in bits */
    /* Index into message block array */
    int_least16_t Message_Block_Index;
    uint8_t Message_Block[64]; /* 512-bit message blocks */
    int Computed; /* Is the digest computed? */
    int Corrupted; /* Is the message digest corrupted? */
} SHA1Context;

int SHA1Reset(SHA1Context *);
int SHA1Input(SHA1Context *, const uint8_t *, unsigned int);
int SHA1Result(SHA1Context *, uint8_t Message_Digest[SHA1HashSize]);

#endif

#include <stdio.h>
#include "sha1.h"

#ifdef __cplusplus
extern "C"
{
#endif


#define SHA1CircularShift(bits, word) \
    (((word) << (bits)) | ((word) >> (32-(bits))))


void SHA1PadMessage(SHA1Context *);

void SHA1ProcessMessageBlock(SHA1Context *);

int SHA1Reset(SHA1Context *context)//初始化状态
{
    if (!context) {
        return shaNull;
    }
    context->Length_Low = 0;
    context->Length_High = 0;
    context->Message_Block_Index = 0;
    context->Intermediate_Hash[0] = 0x67452301;//取得的HASH结果(中间数据)
    context->Intermediate_Hash[1] = 0xEFCDAB89;
    context->Intermediate_Hash[2] = 0x98BADCFE;
    context->Intermediate_Hash[3] = 0x10325476;
    context->Intermediate_Hash[4] = 0xC3D2E1F0;
    context->Computed = 0;
    context->Corrupted = 0;
    return shaSuccess;
}

int SHA1Result(SHA1Context *context, uint8_t Message_Digest[SHA1HashSize]) {
    int i;
    if (!context || !Message_Digest) {
        return shaNull;
    }
    if (context->Corrupted) {
        return context->Corrupted;
    }
    if (!context->Computed) {
        SHA1PadMessage(context);
        for (i = 0; i < 64; ++i) {
            /* message may be sensitive, clear it out */
            context->Message_Block[i] = 0;
        }
        context->Length_Low = 0; /* and clear length */
        context->Length_High = 0;
        context->Computed = 1;
    }
    printf("%x\n", context->Intermediate_Hash[0]);
    printf("%x\n", context->Intermediate_Hash[1]);
    printf("%x\n", context->Intermediate_Hash[2]);
    printf("%x\n", context->Intermediate_Hash[3]);
    printf("%x\n", context->Intermediate_Hash[4]);

    for (i = 0; i < SHA1HashSize; ++i) {
        Message_Digest[i] = context->Intermediate_Hash[i >> 2]
                >> 8 * (3 - (i & 0x03));
    }
    return shaSuccess;
}

int SHA1Input(SHA1Context *context, const uint8_t *message_array, unsigned length) {
    if (!length) {
        return shaSuccess;
    }
    if (!context || !message_array) {
        return shaNull;
    }
    if (context->Computed) {
        context->Corrupted = shaStateError;
        return shaStateError;
    }
    if (context->Corrupted) {
        return context->Corrupted;
    }
    while (length-- && !context->Corrupted) {
        context->Message_Block[context->Message_Block_Index++] =
                (*message_array & 0xFF);
        context->Length_Low += 8;
        if (context->Length_Low == 0) {
            context->Length_High++;
            if (context->Length_High == 0) {
                /* Message is too long */
                context->Corrupted = 1;
            }
        }
        if (context->Message_Block_Index == 64) {
            SHA1ProcessMessageBlock(context);
        }
        message_array++;
    }
    return shaSuccess;
}
void SHA1ProcessMessageBlock(SHA1Context *context) {
    const uint32_t K[] = { 
            0x5A827999,
            0x6ED9EBA1,
            0x8F1BBCDC,
            0xCA62C1D6
    };
    int t; /* Loop counter */
    uint32_t temp; /* Temporary word value */
    uint32_t W[80]; /* Word sequence */
    uint32_t A, B, C, D, E; /* Word buffers */
    /*
    * Initialize the first 16 words in the array W
    */
    for (t = 0; t < 16; t++) {
        W[t] = context->Message_Block[t * 4] << 24;
        W[t] |= context->Message_Block[t * 4 + 1] << 16;
        W[t] |= context->Message_Block[t * 4 + 2] << 8;
        W[t] |= context->Message_Block[t * 4 + 3];
    }
    for (t = 16; t < 80; t++) {
        W[t] = SHA1CircularShift(1, W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]);
        // W[t] = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16];
    }
    A = context->Intermediate_Hash[0];//5个初始化常量
    B = context->Intermediate_Hash[1];
    C = context->Intermediate_Hash[2];
    D = context->Intermediate_Hash[3];
    E = context->Intermediate_Hash[4];
    //分四轮,一共80次分组加密
    for (t = 0; t < 20; t++) {
        temp = SHA1CircularShift(5, A) +
               ((B & C) | ((~B) & D)) + E + W[t] + K[0];
        E = D;
        D = C;
        C = SHA1CircularShift(30, B);
        B = A;
        A = temp;
    }
    for (t = 20; t < 40; t++) {
        temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[1];
        E = D;
        D = C;
        C = SHA1CircularShift(30, B);
        B = A;
        A = temp;
    }
    for (t = 40; t < 60; t++) {
        temp = SHA1CircularShift(5, A) +
               ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
        E = D;
        D = C;
        C = SHA1CircularShift(30, B);
        B = A;
        A = temp;
    }
    for (t = 60; t < 80; t++) {
        temp = SHA1CircularShift(5, A) + (B ^ C ^ D) + E + W[t] + K[3];
        E = D;
        D = C;
        C = SHA1CircularShift(30, B);
        B = A;
        A = temp;
    }
    context->Intermediate_Hash[0] += A;
    context->Intermediate_Hash[1] += B;
    context->Intermediate_Hash[2] += C;
    context->Intermediate_Hash[3] += D;
    context->Intermediate_Hash[4] += E;
    context->Message_Block_Index = 0;
}


void SHA1PadMessage(SHA1Context *context) {

    if (context->Message_Block_Index > 55) {//如果超过56个字节,那么和MD5一样,也是一直填充完,然后填充到下一轮第56个字节处。
        context->Message_Block[context->Message_Block_Index++] = 0x80;
        while (context->Message_Block_Index < 64) {
            context->Message_Block[context->Message_Block_Index++] = 0;
        }
        SHA1ProcessMessageBlock(context);
        while (context->Message_Block_Index < 56) {//然后补上消息长度的8个字节
            context->Message_Block[context->Message_Block_Index++] = 0;
        }
    } else {
        context->Message_Block[context->Message_Block_Index++] = 0x80;
        while (context->Message_Block_Index < 56) {
            context->Message_Block[context->Message_Block_Index++] = 0;
        }
    }

    context->Message_Block[56] = context->Length_High >> 24;//大端序
    context->Message_Block[57] = context->Length_High >> 16;
    context->Message_Block[58] = context->Length_High >> 8;
    context->Message_Block[59] = context->Length_High;
    context->Message_Block[60] = context->Length_Low >> 24;
    context->Message_Block[61] = context->Length_Low >> 16;
    context->Message_Block[62] = context->Length_Low >> 8;
    context->Message_Block[63] = context->Length_Low;
    SHA1ProcessMessageBlock(context);
}


#ifdef __cplusplus
}
#endif
int main(){
    SHA1Context context;
    SHA1Reset(&context);
    unsigned char *plainText = (unsigned char *) "Fup1p1";
    SHA1Input(&context, plainText, strlen((const char*)plainText));
    unsigned char result[16];
    SHA1Result(&context, result);

    char temp[2] = {0};
    char finalResult[41] = {0};
    for (int i = 0; i < 20; i++) {
        int index = i;
        sprintf(temp, "%.2x", result[index]);
        strcat(finalResult, temp);
    }
    printf("%s\n", finalResult);
}

分组密码

分组加密的padding

一般来说,有这么几种填充方式
• Zero Padding (字面意思,后面缺多少个字节就填几个0,但是有个缺陷,就是如果原数据末尾是0,那么就会导致混乱)

• PKCS#7 假设每个区块大小为blockSize
  已对齐,填充一个长度为blockSize且每个字节均为blockSize的数据。
  未对齐,需要补充的字节个数为n,则填充一个长度为n且每个字节均为n的数据。

• PKCS#5 PKCS#7的子集,就是区块固定位8字节。

分组加密中的模式

当明文的长度大于一个分组长度时,就要考虑加密模式了。
有五种模式:ECB CBC CFB OFB CTR
常用的就前两种。
CFB、OFB、CTR均可将分组密码当流密码使用(所以不需要填充!),虽然他们与流密码的典型构造并不一致。

ECB(Electronic codebook)电子密码本

每个分组之间单独加密,互不关联
2个分组明文一样,结果也一样,那么只需爆破其中1个就可以了
每个分组互不关联,可以分段同时来爆破,不安全
可以通过替换某段密文来达到替换明文的目的

image-1684249483096
image-1684254152962

CBC(Cipher Block Chaining)密码块链接

每个明文分组先和上个分组的密文块进行异或,得到的结果进行加密
第1个明文块没有上个分组的密文块,需要指定一个64比特的输入,也就是初始化向量IV
image-1684249604634
image-1684254172731

CFB(Cipher Feedback)密文反馈

image-1684254015812
image-1684254244850
在CFB模式下,首先会对IV进行DES加密得到一个密文块,然后将此密文块与明文的第一个块进行异或运算,得到密文的第一个块。接下来,将得到的密文块再次进行块加密,得到下一个密文块,再与明文的第二个块进行异或运算,得到密文的第二个块。这个过程会一直进行下去,直到整个明文被加密为密文。
而且CFB的解密过程几乎就是颠倒的CBC的加密过程。

OFB(Output Feedback)输出反馈模式

image-1684254319328
和CFB很像,比起CFB就是多异或了一次。

DES

好文

前言

一般来说,密钥的长度是64bit,也就是8个字节。然而,其实有效密钥也就只有56bit(为什么,后文会提到!),所以DES因为安全性其实也被淘汰了。

子密钥的生成

首先,将密钥转换成二进制

00010011 00110100 01010111 01111001 10011011 10111100 11011111 11110001

密钥初始变换

然后根据PC1表,对密钥进行了一个重新的变换,怎么变换的呢?
就是根据下图这张表来的,

int PC1_Table[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};

可以这么理解,把二进制的密钥当成一个表,方便理解就把他看出一个64位的s列表好了,那么PC1_Table中所存的每一个值,都相当于一个下标。然后这个表的第8位(8,16,24…)都是一个校验位。所以变换后的密钥,就是s[table[1]] s[table[2]]…去掉校验位,每7位组成一个密钥。
之前的密钥就可以对应出来:

1111000 0110011 0010101 0101111 0101010 1011001 1001111 0001111

密钥拆分

1111000 0110011 0010101 0101111 0101010 1011001 1001111 0001111

就是将这一段重新编排后的密钥分成两段

C0=1111000 0110011 0010101 0101111
D0=0101010 1011001 1001111 0001111

密钥移位

根据长度为16的key_shift表,又要对上一轮计算的C,D进行循环移位(左移)
key_shift = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];(值也可以是负的哦)
C1,D1 根据key_shift[0]可以知道,需要循环左移1位,并且是上一轮的基础上,但是C0,D0其实就是初始值啦。

C1 = 1110000 1100110 0101010 1011111
D1 = 1010101 0110011 0011110 0011110

至于C2D2,也有两种思路:
• 对C0D0,分别循环左移2位(1+1)
• 对C1D1,分别循环左移1位

然后再将C1D1拼接起来:

1110000 1100110 0101010 1011111 1010101 0110011 0011110 0011110

密钥二次变换

然后我们又要根据PC2表,再一次继续变换。

int PC2_Table[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};

对每一个Cn Dn 都要进行重排,56bit长的Cn Dn就只剩下48bit了(只是压缩而已,并没有校验位,为了和后面的加密算法兼容)…然后给它重命名为Kn。
在后续的DES十六轮运算中,就需要用到Kn

K1 = 000110 110000 001011 101111 111111 000111 000001 110010
K2 = 011110 011010 111011 011001 110110 111100 100111 100101
K3 = 010101 011111 110010 001010 010000 101100 111110 011001
K4 = 011100 101010 110111 010110 110110 110011 010100 011101
K5 = 011111 001110 110000 000111 111010 110101 001110 101000
K6 = 011000 111010 010100 111110 010100 000111 101100 101111
K7 = 111011 001000 010010 110111 111101 100001 100010 111100
K8 = 111101 111000 101000 111010 110000 010011 101111 111011
K9 = 111000 001101 101111 101011 111011 011110 011110 000001
K10 = 101100 011111 001101 000111 101110 100100 011001 001111
K11 = 001000 010101 111111 010011 110111 101101 001110 000110
K12 = 011101 010111 000111 110101 100101 000110 011111 101001
K13 = 100101 111100 010111 010001 111110 101011 101001 000001
K14 = 010111 110100 001110 110111 111100 101110 011100 111010
K15 = 101111 111001 000110 001101 001111 010011 111100 001010
K16 = 110010 110011 110110 001011 000011 100001 011111 110101

DES 中明文的Padding

那么对于DES来说,每组固定是8字节,那么PKCS#7和PKCS#5的效果其实就是一样的咯!

• 如果没有明文,或者明文刚好是8字节的整数倍,这时候都需要再填充一个分组:
即 08 08 08 08 08 08 08 08

• 其他填充的情况
ab cd ef ab cd ef ab 01
ab cd ef ab cd ef 02 02
ab cd ef ab cd 03 03 03
如果明文只有半个字节捏?
比如 ab cd a
那么会给这个半字节前填充一个0
ab cd 0a 05 05 05 05 05

明文的运算

明文的编排

先把明文转成二进制,然后根据表继续置换,哇趣,好熟悉啊。

00000001 00100011 01000101 01100111 10001001 10101011 11001101 11101111

根据IP表,对明文进行初始置换

int IP_Table[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};

又根据表重新排列。

11001100 00000000 11001100 11111111 11110000 10101010 11110000 10101010

然后明文又要分成左右两半,位L0和R0

L0:11001100 00000000 11001100 11111111
R0:11110000 10101010 11110000 10101010

16轮迭代

Ln=Rn-1
Rn=Ln-1 xor f(Rn-1,Kn)

eg

以n = 1为例:

L1 = R0 = 11110000 10101010 11110000 10101010
R1 = L0 xor f(R0, K1)

f函数传入了R0与K1,即第1个子密钥

R0 = 11110000 10101010 11110000 10101010
K1 = 000110 110000 001011 101111 111111 000111 000001 110010

Kn,也就是之前密文变换的结果。

但是R0长度是32bit,K1为48bit,长度不同,那么就没有办法进行按位异或了。

根据E表进行扩展

所以要将32bit的R0扩展成48bit,然后就又需要一个表 E

int E_Table[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};

E(R0) = 011110 100001 010101 010101 011110 100001 010101 010101

然后进行密钥混合 E(R0) ^ K1

E(R0) = 011110 100001 010101 010101 011110 100001 010101 010101
K1 = 000110 110000 001011 101111 111111 000111 000001 110010
K1 xor E(R0)= 011000 010001 011110 111010 100001 100110 010100 100111

DES算法核心

将上一步的结果分为8块 6bit

B1 = 011000
B2 = 010001
B3 = 011110
B4 = 111010
B5 = 100001
B6 = 100110
B7 = 010100
B8 = 100111

把B1-B8的值当成索引,在S1-S8盒中取值,规则如下:

例子1

以B1为例,值为011000,将它分成0 1100 0,得到
i = 00 即 0
j = 1100 即 12
在S1中查找第0行第12列的值(这里从0开始算)

第0行第12列的值为5
B1 = 5 = 0101

例子2

再以B2为例
B2 = 0 1000 1
i = 01 = 1
j = 1000 = 8

B2 = 12 = 1100
B1在S1中找,B2在S2中找,以此类推

S 表
int S_Box[8][4][16] = {
        // S1
        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,
        // S2
        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,
        // S3
        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,
        // S4
        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,
        // S5
        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,
        // S6
        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,
        // S7
        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,
        // S8
        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
};
get值后

最后8个6比特的值在S盒作用下变成8个4比特的值

S(K1+E(R0)) = 0101 1100 1000 0010 1011 0101 1001 0111

又要按照P表进行重排(怎么这么多重排啊,恼!)

int P_Table[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
};

P(S(K1 xor E(R0))) = 0010 0011 0100 1010 1010 1001 1011 1011

然后到这里,F函数才算结束了…

F的结果与L0异或

L1 = R0 = 11110000 10101010 11110000 10101010
R1 = Ln-1 xor f(Rn-1,Kn) = 11101111 01001010 01100101 01000100

然后重复16轮,就可以得到一个Ln,Rn的表

L1: 11110000 10101010 11110000 10101010
R1: 11101111 01001010 01100101 01000100
L2: 11101111 01001010 01100101 01000100
R2: 11001100 00000001 01110111 00001001
L3: 11001100 00000001 01110111 00001001
R3: 10100010 01011100 00001011 11110100
L4: 10100010 01011100 00001011 11110100
R4: 01110111 00100010 00000000 01000101
L5: 01110111 00100010 00000000 01000101
R5: 10001010 01001111 10100110 00110111
L6: 10001010 01001111 10100110 00110111
R6: 11101001 01100111 11001101 01101001
L7: 11101001 01100111 11001101 01101001
R7: 00000110 01001010 10111010 00010000
L8: 00000110 01001010 10111010 00010000
R8: 11010101 01101001 01001011 10010000
L9: 11010101 01101001 01001011 10010000
R9: 00100100 01111100 11000110 01111010
L10:00100100 01111100 11000110 01111010
R10:10110111 11010101 11010111 10110010
L11:10110111 11010101 11010111 10110010
R11:11000101 01111000 00111100 01111000
L12:11000101 01111000 00111100 01111000
R12:01110101 10111101 00011000 01011000
L13:01110101 10111101 00011000 01011000
R13:00011000 11000011 00010101 01011010
L14:00011000 11000011 00010101 01011010
R14:11000010 10001100 10010110 00001101
L15:11000010 10001100 10010110 00001101
R15:01000011 01000010 00110010 00110100
L16:01000011 01000010 00110010 00110100
R16:00001010 01001100 11011001 10010101

然后将L16和R16换一下顺序,结果为R16L16

R16L16 = 00001010 01001100 11011001 10010101 01000011 01000010 00110010 00110100

然后最后根据FP表进行行末的置换

10000101 11101000 00010011 01010100 00001111 00001010 10110100 00000101

int FP_Table[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};

上述数据的十六进制形式为

85 e8 13 54 0f 0a b4 05

3DES (Triple DES)

image-1684307921656
1.密钥24个字节,相当于3个DES的密钥

2.3DES的处理流程,每次使用一个DES的密钥
DES加密–>DES解密–>DES加密
(如果第一个密钥和第二个密钥一样,就相当于啥都没干了QAQ)

AES

即Advanced Encryption Standard,高级加密标准

  1. AES算法的一些特点
    1.1 在一个分组长度内,明文的长度变化,AES的加密结果长度都一样
    1.2 密钥长度128、192、256三种,如果少1个十六进制数,会在前面补0
    1.3 分组长度只有128一种
    1.4 密文长度与填充后的明文长度有关,一般是16字节的倍数

  2. Rijndael算法也叫AES算法
    Rijndael算法本身分组长度可变,但是定为AES算法后,分组长度只有128一种

  3. DES中是针对bit进行操作,AES中是针对字节进行操作

  4. AES128 -> 10轮运算、AES192 -> 12轮运算、AES256 -> 14轮运算

加密流程

image-1684329413234

一.初始变换

1.明文state化

明文和密钥变成4*4的矩阵,而且是按顺序竖着放进矩阵里去。
image-1684328478627

2.初始轮密钥加

与种子密钥K异或(如果是CBC模式先与iv异或)

二.前9/11/13轮运算

1.SubBytes(替换S盒)

image-1684331908892

2.ShiftRows(行移位)

image-1684332142747

第一行不变
第二行循环左移1位
第三行循环左移2位
第四行循环左移3位

3.MixColumns(列混合)

image-1684342237837
特征:会存在一个4*4的数组,然后会做一个伽罗华域乘法运算(其中会用到两个常量 0x1b 和0x80,这两个值算是一个特征了)

4.AddRoundKey(轮密钥加) 即与子密钥K1~Kn-1进行异或

这个比较好懂,只是简单的异或。
image-1684342493035

三.末轮加密

1.SubBytes(替换S盒)
2.ShiftRows(行移位)
4.AddRoundKey(轮密钥加) 即与最后一个子密钥Kn进行异或
1
CTF

评论区