打工人日报#20251231

打工人日报#20251231

MD5

MD5(Message - Digest Algorithm 5)是由美国密码学家罗纳德・李维斯特(Ronald Linn Rivest)设计,于 1992 年公开,用以取代 MD4 算法的一种哈希函数。它在信息安全领域有着广泛应用,虽然如今因其安全性问题逐渐被弃用,但了解其原理和机制仍具有重要意义。

MD5 的基本原理

输入与输出:MD5 接受任意长度的数据作为输入,经过一系列复杂运算后,生成一个 128 位(16 字节)的哈希值作为输出。这个哈希值通常以 32 位十六进制数字的形式表示,例如:5d41402abc4b2a76b9719d911017c592。无论输入数据的长度、格式如何,输出的 MD5 哈希值长度始终固定为 128 位。

运算过程:MD5 算法的运算过程主要分为以下几个步骤:

数据填充:首先对输入数据进行填充,使其长度在对 512 取模后余数为 448。填充的方法是先在数据后面添加一个 1,然后再添加若干个 0,直到满足上述长度要求。例如,如果输入数据长度为 400 位,那么需要添加 1 + 47 = 48 位,使其总长度达到 448 位。

附加长度:在填充后的数据后面附加一个 64 位的二进制数,该数表示原始数据(未填充前)的长度。这一步是为了让接收方能够准确还原原始数据的长度信息。

初始化变量:MD5 算法使用四个 32 位的寄存器(A、B、C、D),并将它们初始化为固定的数值:

A = 0x67452301

B = 0xefcdab89

C = 0x98badcfe

D = 0x10325476

分块处理与循环运算:将填充和附加长度后的整个数据分成 512 位(64 字节)的块,对每个块进行 4 轮运算,每轮运算包含 16 步操作。在每一步操作中,通过一系列的位运算(如与、或、异或等)和非线性函数,对寄存器 A、B、C、D 进行更新。这里的非线性函数主要有四个:F (X,Y,Z)=(X&Y)|((~X)&Z)、G (X,Y,Z)=(X&Z)|(Y&(~Z))、H (X,Y,Z)=XYZ、I (X,Y,Z)=Y^(X|(~Z))。每一步操作都会根据当前步骤的索引和非线性函数,结合当前处理的数据块中的子块以及寄存器的值进行计算,从而更新寄存器的值。经过 4 轮共 64 步操作后,一个数据块处理完毕。

结果合并:对所有 512 位的数据块都进行上述运算后,将最终得到的寄存器 A、B、C、D 的值依次连接起来,就得到了 128 位的 MD5 哈希值。

MD5 的应用场景

数据完整性验证:在数据传输或存储过程中,发送方可以计算数据的 MD5 值并一同传输给接收方。接收方收到数据后,重新计算数据的 MD5 值,并与发送方提供的 MD5 值进行比较。如果两者相同,则说明数据在传输或存储过程中没有被篡改,保证了数据的完整性。例如,在下载软件安装包时,软件官方网站通常会提供该安装包的 MD5 值。用户下载完成后,可以使用相关工具计算下载文件的 MD5 值,并与官方提供的 MD5 值对比,以确认下载的文件是否完整、未被篡改。

文件标识:由于不同文件的内容几乎不可能产生相同的 MD5 值,因此可以将 MD5 值作为文件的唯一标识。在一些文件共享系统或数据库中,通过计算文件的 MD5 值来判断两个文件是否相同,即使文件名不同,只要 MD5 值相同,就认为是同一个文件。这样可以用于避免重复存储相同文件,节省存储空间。

密码存储:在早期的系统中,常将用户密码经过 MD5 加密后存储在数据库中。当用户登录时,系统将用户输入的密码进行 MD5 加密,然后与数据库中存储的加密密码进行比对。如果一致,则允许用户登录。然而,由于 MD5 的安全性问题,这种方式现在已不再安全,因为通过彩虹表等技术可以快速破解常见密码的 MD5 值。

MD5 的安全性问题

碰撞问题:理论上,MD5 算法可能出现不同的输入数据产生相同哈希值的情况,这被称为碰撞。虽然找到这样的碰撞在计算上曾经被认为是困难的,但随着计算能力的提升,已经有方法可以相对容易地构造出 MD5 碰撞。2004 年,中国密码学家王小云教授在国际密码学会议上展示了能够快速找到 MD5 碰撞的方法,这使得 MD5 在需要高度安全性的场景中不再可靠。例如,攻击者可以构造两个不同的可执行文件,使其具有相同的 MD5 值,从而绕过基于 MD5 的文件完整性检查机制。

暴力破解与彩虹表攻击:由于 MD5 的输出长度固定为 128 位,理论上存在一定数量的可能哈希值。通过暴力破解,即尝试所有可能的输入数据来匹配给定的 MD5 哈希值,在计算能力足够强大时是可行的。此外,彩虹表攻击是一种更高效的破解方式,攻击者预先计算大量常见密码及其对应的 MD5 哈希值,并存储在彩虹表中。在破解时,直接在彩虹表中查找目标哈希值对应的密码。这使得使用 MD5 存储密码变得极其不安全,因为常见密码很容易被快速破解。

CRC

CRC 即循环冗余校验(Cyclic Redundancy Check),是一种在数据通信和存储领域广泛应用的检错技术。它通过对传输或存储的数据进行特定的数学运算,生成一个固定长度的校验码(CRC 值)。

CRC 运算基于模 2 运算规则,发送端将待传输数据视为一个二进制多项式,与一个预设的生成多项式进行模 2 除法运算,得到的余数即为 CRC 值。这个 CRC 值附加在原始数据之后一同传输。

接收端收到数据后,用同样的生成多项式对包括 CRC 值在内的整体数据再次进行模 2 除法。如果余数为零,通常认为数据在传输过程中没有出错;若余数不为零,则表明数据出现错误,接收端可要求发送端重新传输数据。

CRC 具有检测能力强、计算速度快的特点,能够有效检测出数据在传输过程中因噪声干扰等导致的突发错误和部分随机错误。不过,它主要用于检错而非纠错,且对于一些针对性的恶意篡改可能无法有效抵御。在串口通信、网络协议(如以太网)、存储设备(如硬盘)等场景中,CRC 被广泛用于保障数据的完整性。

举例

使用 C 语言实现串口数据收发并对数据进行 MD5 加密传输的示例,使用了 OpenSSL 库来计算 MD5 值,

发送端代码

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>

#define SERIAL_PORT "/dev/ttyS0" // 根据实际串口设备路径修改
#define BAUD_RATE B9600

// 计算MD5值
void calculate_md5(const char *data, unsigned char *md5_hash) {
    MD5_CTX md5Context;
    MD5_Init(&md5Context);
    MD5_Update(&md5Context, data, strlen(data));
    MD5_Final(md5_hash, &md5Context);
}

// 配置串口
int configure_serial_port(int fd) {
    struct termios options;
    tcgetattr(fd, &options);
    cfsetispeed(&options, BAUD_RATE);
    cfsetospeed(&options, BAUD_RATE);
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;
    tcsetattr(fd, TCSANOW, &options);
    return 0;
}

int main() {
    int fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("无法打开串口");
        return 1;
    }

    configure_serial_port(fd);

    const char *header = "\x0xHG";
    const char *content = "abcd"; // 四字节内容示例
    unsigned char md5_hash[MD5_DIGEST_LENGTH];
    calculate_md5(content, md5_hash);

    char md5_str[MD5_DIGEST_LENGTH * 2 + 1];
    for(int i = 0; i < MD5_DIGEST_LENGTH; i++) {
        sprintf(&md5_str[i*2], "%02x", (unsigned int)md5_hash[i]);
    }

    // 简单的CRC校验(示例,实际应用需更完善算法)
    unsigned char crc = 0;
    for(int i = 0; i < strlen(content); i++) {
        crc ^= content[i];
    }

    const char *footer = "\x0xGH";

    char send_buffer[1024];
    snprintf(send_buffer, sizeof(send_buffer), "%s%s%02hhx%s", header, content, crc, footer);

    ssize_t bytes_written = write(fd, send_buffer, strlen(send_buffer));
    if (bytes_written == -1) {
        perror("发送数据失败");
    } else {
        printf("发送了 %zd 字节数据\n", bytes_written);
    }

    close(fd);
    return 0;
}

接收端代码

bash 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/md5.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>

#define SERIAL_PORT "/dev/ttyS0" // 根据实际串口设备路径修改
#define BAUD_RATE B9600

// 计算MD5值
void calculate_md5(const char *data, unsigned char *md5_hash) {
    MD5_CTX md5Context;
    MD5_Init(&md5Context);
    MD5_Update(&md5Context, data, strlen(data));
    MD5_Final(md5_hash, &md5Context);
}

// 配置串口
int configure_serial_port(int fd) {
    struct termios options;
    tcgetattr(fd, &options);
    cfsetispeed(&options, BAUD_RATE);
    cfsetospeed(&options, BAUD_RATE);
    options.c_cflag |= (CLOCAL | CREAD);
    options.c_cflag &= ~PARENB;
    options.c_cflag &= ~CSTOPB;
    options.c_cflag &= ~CSIZE;
    options.c_cflag |= CS8;
    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    options.c_oflag &= ~OPOST;
    tcsetattr(fd, TCSANOW, &options);
    return 0;
}

int main() {
    int fd = open(SERIAL_PORT, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1) {
        perror("无法打开串口");
        return 1;
    }

    configure_serial_port(fd);

    char receive_buffer[1024];
    ssize_t bytes_read = read(fd, receive_buffer, sizeof(receive_buffer) - 1);
    if (bytes_read == -1) {
        perror("读取数据失败");
    } else {
        receive_buffer[bytes_read] = '\0';
        printf("接收到 %zd 字节数据: %s\n", bytes_read, receive_buffer);

        const char *header = "\x0xHG";
        const char *footer = "\x0xGH";

        if (strncmp(receive_buffer, header, strlen(header)) != 0 ||
            strncmp(receive_buffer + bytes_read - strlen(footer), footer, strlen(footer)) != 0) {
            printf("数据头或数据尾错误\n");
            close(fd);
            return 1;
        }

        char content[5];
        sscanf(receive_buffer + strlen(header), "%4s", content);

        unsigned char received_md5_hash[MD5_DIGEST_LENGTH];
        calculate_md5(content, received_md5_hash);

        char received_md5_str[MD5_DIGEST_LENGTH * 2 + 1];
        for(int i = 0; i < MD5_DIGEST_LENGTH; i++) {
            sprintf(&received_md5_str[i*2], "%02x", (unsigned int)received_md5_hash[i]);
        }

        // 验证CRC校验
        unsigned char received_crc;
        sscanf(receive_buffer + strlen(header) + 4, "%02hhx", &received_crc);
        unsigned char calculated_crc = 0;
        for(int i = 0; i < strlen(content); i++) {
            calculated_crc ^= content[i];
        }

        if (calculated_crc != received_crc) {
            printf("CRC校验失败\n");
        } else {
            printf("CRC校验成功\n");
        }
    }

    close(fd);
    return 0;
}

MD5 计算:使用 OpenSSL 库的MD5函数计算数据的 MD5 值。

串口配置:通过configure_serial_port函数设置串口的波特率、数据位、停止位等参数。

发送端:构建包含头、内容、CRC 校验位和结束符的数据缓冲区,并通过串口发送。

接收端:从串口读取数据,验证数据头和数据尾,提取内容并计算 MD5 值进行验证,同时验证 CRC 校验位。

在数据传输过程中,为防止数据被截取后被轻易解读,可以是哪些方法加密呢?

对称加密算法

AES(高级加密标准)

原理:AES 是一种对称加密算法,它使用相同的密钥进行加密和解密。它支持 128 位、192 位和 256 位的密钥长度,密钥越长,安全性越高。AES 通过一系列的字节替换、行移位、列混淆和轮密钥加等操作对数据进行加密。

实现:在 C 语言中,可以使用开源的加密库如 OpenSSL 来实现 AES 加密。

bash 复制代码
#include <openssl/aes.h>
#include <stdio.h>
#include <string.h>

void encrypt(const unsigned char *plaintext, int plaintext_len, const unsigned char *key, unsigned char *ciphertext) {
    AES_KEY aes_key;
    AES_set_encrypt_key(key, 256, &aes_key);
    AES_cbc_encrypt(plaintext, ciphertext, plaintext_len, &aes_key, NULL, AES_ENCRYPT);
}

void decrypt(const unsigned char *ciphertext, int ciphertext_len, const unsigned char *key, unsigned char *decryptedtext) {
    AES_KEY aes_key;
    AES_set_decrypt_key(key, 256, &aes_key);
    AES_cbc_encrypt(ciphertext, decryptedtext, ciphertext_len, &aes_key, NULL, AES_DECRYPT);
}

int main() {
    const unsigned char key[32] = "This is a 256 bit key";
    const unsigned char plaintext[] = "Hello, World!";
    unsigned char ciphertext[128];
    unsigned char decryptedtext[128];
    int plaintext_len = strlen(plaintext);

    encrypt(plaintext, plaintext_len, key, ciphertext);
    decrypt(ciphertext, plaintext_len, key, decryptedtext);

    printf("Plaintext: %s\n", plaintext);
    printf("Ciphertext: ");
    for (int i = 0; i < plaintext_len; i++) {
        printf("%02hhx", ciphertext[i]);
    }
    printf("\nDecryptedtext: %s\n", decryptedtext);

    return 0;
}

DES(数据加密标准)

原理:DES 也是对称加密算法,它使用 56 位密钥对 64 位的数据块进行加密。DES 通过初始置换、16 轮的 Feistel 结构运算以及最终置换来实现加密。不过,由于其密钥长度较短,在现代计算能力下已相对容易被破解,通常不建议在高安全性要求场景中单独使用。

实现:同样可以使用 OpenSSL 库来实现 DES 加密,代码结构与 AES 类似,但使用的是 DES 相关函数,如DES_set_key和DES_ecb_encrypt等。

非对称加密算法

RSA

原理:RSA 基于数论中的大整数分解难题。它使用一对密钥,即公钥和私钥。公钥用于加密数据,私钥用于解密数据。加密过程是将明文通过公钥进行数学运算得到密文,只有持有对应的私钥才能将密文还原为明文。

实现:在 C 语言中使用 OpenSSL 库实现 RSA 加密示例

bash 复制代码
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <stdio.h>
#include <string.h>

void handleErrors() {
    ERR_print_errors_fp(stderr);
    abort();
}

int main() {
    RSA *rsa = NULL;
    FILE *fp = NULL;
    unsigned char plaintext[] = "Hello, RSA!";
    unsigned char ciphertext[256];
    unsigned char decryptedtext[256];
    int flen, len;

    fp = fopen("public_key.pem", "rb");
    if (!fp) {
        perror("无法打开公钥文件");
        return 1;
    }
    rsa = PEM_read_RSA_PUBKEY(fp, &rsa, NULL, NULL);
    fclose(fp);
    if (!rsa) {
        handleErrors();
    }

    flen = strlen(plaintext);
    len = RSA_public_encrypt(flen, plaintext, ciphertext, rsa, RSA_PKCS1_PADDING);
    if (len == -1) {
        handleErrors();
    }

    fp = fopen("private_key.pem", "rb");
    if (!fp) {
        perror("无法打开私钥文件");
        return 1;
    }
    rsa = PEM_read_RSAPrivateKey(fp, &rsa, NULL, NULL);
    fclose(fp);
    if (!rsa) {
        handleErrors();
    }

    len = RSA_private_decrypt(len, ciphertext, decryptedtext, rsa, RSA_PKCS1_PADDING);
    if (len == -1) {
        handleErrors();
    }

    decryptedtext[len] = '\0';
    printf("Plaintext: %s\n", plaintext);
    printf("Ciphertext: ");
    for (int i = 0; i < len; i++) {
        printf("%02hhx", ciphertext[i]);
    }
    printf("\nDecryptedtext: %s\n", decryptedtext);

    RSA_free(rsa);
    return 0;
}
相关推荐
孙严Pay2 小时前
分享三种不同的支付体验,各自有着不同的特点与适用场景。
笔记·科技·计算机网络·其他·微信
YJlio2 小时前
VolumeID 学习笔记(13.10):卷序列号修改与资产标识管理实战
windows·笔记·学习
weixin_440730502 小时前
java数组整理笔记
java·开发语言·笔记
小龙2 小时前
【学习笔记】多标签交叉熵损失的原理
笔记·学习·多标签交叉熵损失
强子感冒了5 小时前
Java学习笔记:String、StringBuilder与StringBuffer
java·开发语言·笔记·学习
不会学习?6 小时前
大二元旦,2025最后一天
经验分享·笔记
NULL指向我7 小时前
STM32F407VET6学习笔记14:Bootloader程序笔记
笔记·stm32·学习
伶星377 小时前
obsidian 日记按年月存放
笔记
日更嵌入式的打工仔7 小时前
Ehercat代码解析中文摘录<1>
网络·笔记·ethercat