MD5的实现与“破解”

MD5的实现与“破解”

文章目录

  • MD5的实现与“破解”
    • 一、 概述
    • 二、 MD5简单介绍
    • 三、 MD5的实现
    • 四、 MD5的“破解”
    • 五、MD5的“破解”方法
      • 1. 暴力破解:穷举法&字典法
      • 2. 时间和空间的折中:哈希链表法&彩虹表法
        • 2.1. 哈希链表法的过程
        • 2.2. 哈希链表可能遇到的疑惑
        • 2.3. 哈希链表举例
        • 2.4. 彩虹表法的过程
        • 2.5. 区别
        • 2.6. 关于中间值的疑惑
      • 3. 差分攻击
        • 3.1. 差分攻击原理
        • 3.2. 举例说明
      • 4. 相同前缀碰撞

一、 概述

MD5(Message-Digest Algorithm 5)是一种广泛使用的密码散列函数。它于1991年由罗纳德·里维斯特设计出来,是MD4的改进版本。MD5的主要功能是提供一种将任意长度的消息转换成固定长度(128位,即16字节)的摘要的方法。这个过程通常被称为“散列”。

MD5的设计目的是为了提供一种安全的方式来存储和验证信息,比如在数据库中安全地存储密码。MD5散列是不可逆的,这意味着从产生的摘要中,无法恢复原始数据。先来看看MD5的一些简单的实际运用:

  • 登录界面:在我们的登录界面中,我们需要输入账号密码,而后台收到用户输入的账号密码之后就会与数据库中的账号密码进行校验,那么现在有个疑问,如果黑客拖走了整个数据库,那么是不是所有用户的账号密码都裸奔了呢?其实不是,有了MD5实现,用户在数据库中的账号密码都是一个个散列值,黑客拖走了数据库但是他得到的也是散列值而不是真实的密码,没有一些特殊手段他也不能进行登录。
  • 下载:我们在官网下载软件的时候一般都可以得到一个MD5值,如果我们不在官网下载而是在某某软件园下载的话,我们可以根据某某软件园下载的软件的MD5值与官网进行比较,来看看这个软件有没有被修改过。

然而,随着计算能力的增强和对MD5算法的深入分析,它在安全性方面的局限性逐渐暴露出来。特别是在2000年以后,研究人员发现了MD5的多个弱点,使得攻击者可以相对容易地生成相同MD5摘要的不同消息,这种情况被称为“碰撞”。因此,MD5不再被认为是一种安全的散列函数,尤其是在需要高安全性的场合。尽管如此,MD5由于其计算速度快和实现简单,在很多非安全性要求的应用中仍然在使用。例如,在一些老旧系统中,MD5仍用于验证数据完整性和生成文件的检验和。但在安全性要求较高的应用中,通常推荐使用更安全的散列函数,如SHA-256。

二、 MD5简单介绍

上面也说了,MD5的主要功能是提供一种将任意长度的消息转换成固定长度(128位/32个十六进制位/16字节)的摘要的方法,这个过程通常被称为“散列”,而通过这个密码散列函数的到的值,一般叫做散列值。先来看看一个MD5加密的例子:

序号原始值散列值(32个十六进制)
1Hello WorldB10A8DB164E0754105B7A99BE72E3FE5
2Hello WorldB10A8DB164E0754105B7A99BE72E3FE5
3Hello World!ED076287532E86365E841E92BFC50D8C

通过上面的表格MD5加密的例子可以得到,相同的原始值无论加密多少次,得到的散列值都是一样的,而相似度极大的原始值比如上面就多了一个感叹号,得到的散列值也是大有不同的。所以这为上面讲到的“登录界面”,“下载”的实现提供了基础。

三、 MD5的实现

MD5的实现大致可以分成3个主要部分:填充对齐,分块,多轮压缩。下面就来详细并且通俗的讲一下这个过程。首先我们知道所有文件实质上都是一个个由0/1比特位组成的,现在我们有一个文件M,它的比特位为N,即N是由一堆的01组成的东东。

  • 填充对齐:不管N是多少位,输入的消息位数被填充为512整数倍位的内容。填充的规则就是在原始消息的末尾添加一个1然后用0补充(一定要有一个1),直到只剩下64位,而剩下的64位则用来表示消息长度(以位为单位)。这时候就会有有疑问了如果原始消息长度为1000位,要是补齐的话不够65位,这样的情况也要继续补齐到1536位而不是1024位。甚至是原始消息长度刚好1024位也是这样——继续补齐!最终的内容就是一个为512整数倍的内容。大体如下所示:
原始数据(n位)填充内容1(1~512位)填充内容2(64位)
1011001…010011…000000000表示消息长度的01串
  • 分块:MD5的长度是固定了,它的作者规定了将这128个比特位分成4个部分,即32个比特位/8个十六进制数。之后,会设定4个32位(8个十六进制)整数作为初始的哈希值,通常用A、B、C、D来表示——A = 0x67452301,B = 0xEFCDAB89,C = 0x98BADCFE,D = 0x10325476。同样,填充后的比特内容也会被分为多个512位的大块。
  • 多轮压缩:进入多轮压缩这一步,从第一个512位大块开始对每一个大块上进行多轮压缩,把当前散列值(md5的四个部分)的四个部分A、B、C、D分别复制一份。压缩一共有四轮,每轮使用数据块和和md5的四个部分进行一系列包括与、或、非和循环移位的位操作。把abcd各自更新4次,四轮压缩一共把abcd更新了16次,完成4四轮压缩之后把abcd加回去当前散列值的四个部分,散列值更新。接着就是下一个大块。大体如下图所示,具体的与或非循环移位操作不必深究:

在这里插入图片描述

下面附上C++的MD5加密代码,感兴趣可以研究一下:

#include <iostream>
#include <cstring>
#include <cstdint>
#include <string>class MD5 {
public:MD5(const std::string& message) {init();update(reinterpret_cast<const uint8_t*>(message.c_str()), message.length());finalize();}const uint8_t* digest() const {return buffer;}std::string toString() const {const char hexDigits[] = "0123456789abcdef";std::string str;for (int i = 0; i < 16; ++i) {str.append(1, hexDigits[(buffer[i] >> 4) & 0x0F]);str.append(1, hexDigits[buffer[i] & 0x0F]);}return str;}private:void init() {h0 = 0x67452301;h1 = 0xEFCDAB89;h2 = 0x98BADCFE;h3 = 0x10325476;unprocessedBytes = 0;size = 0;}void update(const uint8_t* msg, size_t length) {size += length;size_t i = 0;if (unprocessedBytes > 0) {if (length + unprocessedBytes >= 64) {memcpy(&processedBytes[unprocessedBytes], msg, 64 - unprocessedBytes);processBlock(processedBytes);length -= 64 - unprocessedBytes;i += 64 - unprocessedBytes;unprocessedBytes = 0;}}for (; i + 63 < length; i += 64) {processBlock(msg + i);}if (i < length) {memcpy(processedBytes, msg + i, length - i);unprocessedBytes = length - i;}}void finalize() {uint8_t finalBlock[128];size_t finalBlockSize = unprocessedBytes;memcpy(finalBlock, processedBytes, unprocessedBytes);finalBlock[finalBlockSize++] = 0x80;if (finalBlockSize > 56) {memset(finalBlock + finalBlockSize, 0, 64 - finalBlockSize);processBlock(finalBlock);finalBlockSize = 0;}memset(finalBlock + finalBlockSize, 0, 56 - finalBlockSize);uint64_t sizeInBits = size * 8;memcpy(finalBlock + 56, &sizeInBits, 8);processBlock(finalBlock);memcpy(buffer, &h0, 4);memcpy(buffer + 4, &h1, 4);memcpy(buffer + 8, &h2, 4);memcpy(buffer + 12, &h3, 4);}void processBlock(const uint8_t* block) {uint32_t a = h0;uint32_t b = h1;uint32_t c = h2;uint32_t d = h3;uint32_t M[16];for (int i = 0; i < 16; ++i) {M[i] = (block[i * 4 + 0] << 0) |(block[i * 4 + 1] << 8) |(block[i * 4 + 2] << 16) |(block[i * 4 + 3] << 24);}// 主循环for (unsigned int i = 0; i < 64; ++i) {uint32_t F, g;if (i < 16) {F = (b & c) | ((~b) & d);g = i;}else if (i < 32) {F = (d & b) | ((~d) & c);g = (5 * i + 1) % 16;}else if (i < 48) {F = b ^ c ^ d;g = (3 * i + 5) % 16;}else {F = c ^ (b | (~d));g = (7 * i) % 16;}uint32_t D = d;d = c;c = b;b = b + leftRotate((a + F + K[i] + M[g]), S[i]);a = D;}h0 += a;h1 += b;h2 += c;h3 += d;}static inline uint32_t leftRotate(uint32_t x, uint32_t c) {return (x << c) | (x >> (32 - c));}uint32_t h0, h1, h2, h3;uint8_t buffer[16];uint8_t processedBytes[64];size_t unprocessedBytes;uint64_t size;static constexpr uint32_t K[64] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391};static constexpr uint32_t S[64] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};
};int main() {std::string message;std::cout << "Enter message: ";std::getline(std::cin, message);MD5 md5(message);std::cout << "MD5 digest: " << md5.toString() << std::endl;return 0;
}

四、 MD5的“破解”

我们已经知道了它的加密过程吧,现在我们来破解它。首先我们先明确一点,我们需要的是什么,是原文还是寻找能产生相同散列值的内容。MD5事实上是一个散列函数而不是一个加密函数,他不是一个可逆的过程,即不能通过散列值得出原始内容,就像你不能由一个128位的散列值恢复成一部2个G的电影,这是违背信息论的。同样前面也讲过多轮压缩的过程中也会进行与或非等操作,比如:1(原文)|1=1,你能通过后两个1来确认前面的是1还是0吗?这显然是不能的。那这样我们攻击个屁!

仔细想想,破解它一定要得到原始内容吗?我们好像忽略了一个东西,那就是消息内容和散列值是一一对应的吗?显然不是,原始内容是无穷无尽的,而散列值只有那128位,这可以得出一个散列值对应的也是无穷个原始内容,而这个原始内容我们很难找到罢了。去寻找两个能产生一样散列值的不同原始内容,叫做“碰撞”。

对于MD5的破解,大体上可以分成3类,原像攻击,第二原像攻击,碰撞攻击:

  • 原像攻击:原像攻击是由MD5散列值推出算出原始数据,这好比于暴力破解,但是事实上实现却是很困难的,对于一个100位的数,它的组合有2的100次方中情况,何况是1000位甚至更多呢?所以原像攻击在目前并没有成功攻击的案例。
  • 第二原像攻击:第二原像攻击是给定了一个MD5值和原始数据,需要找到一个与给定输入具有相同散列值的不同输入。第二原像攻击的目标是针对一个特定的、已知的消息,找到另一个不同的消息,使得两者产生相同的哈希值。
  • 碰撞攻击:碰撞攻击是先并没有MD5值和原始数据,攻击者要做的就是要找到两个不同的消息,这两个消息被MD5散列函数处理之后产生相同的哈希值,关键就是这两个消息都是由攻击者选择的,而不是基于一个已知的特定消息。

五、MD5的“破解”方法

1. 暴力破解:穷举法&字典法

穷举法和字典法都是利用计算机资源没有太多头绪尝试碰撞已知的MD5码。对于穷举法,就是不断尝试各种字符的排列组合,看哪一个组合可以对的上,这个方法的缺点就是太耗费时间,一个8位数的字母和数字组合的密码的组合有两百万亿种;对于字典法,就是把已知的常见的密码和对应的散列值存在一起,这种方法相对于穷举法更加理性一些,缺点就是耗费空间。

2. 时间和空间的折中:哈希链表法&彩虹表法

2.1. 哈希链表法的过程
  1. 生成链

    • 起始点:选择一系列可能的原始值(如密码)作为链的起始点。
    • 交替应用:对每个起始点先应用哈希函数(如MD5),再应用还原函数,重复这个过程多次,形成一条哈希链。
  2. 存储链端点

    • 只存储每条链的起始点和终点,不存储中间过程的值。
  3. 破解过程

    • 当你有一个目标哈希值需要破解时,使用还原函数对它进行处理,并检查是否与已存储链的末端匹配。
    • 如果匹配,沿着对应链条使用相同的哈希和还原函数回溯,直到找到产生目标哈希值的原始值。
2.2. 哈希链表可能遇到的疑惑
  1. 为什么不能直接逆向哈希值
    • 哈希函数(如MD5)是设计为单向且不可逆的。这意味着不能直接从哈希值推导出确切的原始值。
  2. 还原函数的作用和限制
    • 还原函数不是逆哈希函数。它只是从哈希值生成可能的原始值,而这个值可能不是实际产生哈希的真实数据,也就是再哈希一次不能得到原来的值。
    • 还原函数的输出是基于算法的任意决定,并不保证能覆盖所有可能的原始值。
  3. 为什么需要回溯
    • 由于还原函数并不能保证找到正确的原始值,所以需要通过回溯过程来验证每个可能的原始值,看它是否真的能产生目标哈希值。
    • 这种方法考虑到了哈希碰撞的情况,即不同的原始值可能产生相同的哈希值。
2.3. 哈希链表举例

H(X):生成信息摘要的哈希函数,比如MD5,SHA256。

R(X):从信息摘要种转化为另一个字符串的还原函数,其中R(X)的定义域是H(X)的值域,R(X)的值域是H(X)的定义域,需要注意的就是这两个并非反函数关系。

通过交替运算H和R若干次,可以形成一个链条,假设原文是aaaaaa,哈希长度位32位,那么哈希链表就是这样子:
在这里插入图片描述

同时我们只需要将首段和尾端存入哈希表里面。那接下来就来演示一下获得原文的过程:
给定信息摘要:920ECF10,R(920ECF10)=kiebgt,查询哈希表可以找到首段是aaaaaa,因此摘要920ECF10极有可能在这个链表里面。接下来从aaaaaa开始,重新交替运算R(X)H(X),看看摘要值时候是其中一次H(X)的结果,如果是的话,前面一个节点就是可能的原文。

2.4. 彩虹表法的过程

彩虹表是哈希链表法的一种改进。它通过引入“彩虹”效果来减少链之间的重叠和冲突。彩虹表的关键特点包括:

  1. 不同的还原函数:在构建彩虹表的每一步中,使用不同的还原函数。这意味着即使两条链在某一点具有相同的中间哈希值,由于接下来使用的还原函数不同,它们也会分叉成不同的路径。
  2. 优化的存储与搜索:这种方法减少了链之间的重叠和碰撞,使得彩虹表在存储和搜索时更为高效。
2.5. 区别
  1. 处理碰撞和重叠:彩虹表通过使用多种还原函数来减少链之间的碰撞和重叠,而传统的哈希链表法在这方面效率较低。
  2. 存储和效率:彩虹表通常比传统的哈希链表更有效率,因为它们减少了重复计算,并优化了搜索过程。
  3. 实现复杂性:彩虹表的实现相对于传统的哈希链表更复杂,因为它需要管理和应用多种还原函数。
2.6. 关于中间值的疑惑

在彩虹表中,一条哈希链从一个特定的起始点(原始密码候选)开始,通过交替应用哈希函数和还原函数,形成了一系列的中间值。这条链实际上在“探索”一系列可能的密码。

不同的潜在密码:每次应用还原函数时,都可能产生一个不同的潜在密码。因此,一条链实际上代表了从起始点出发,通过多次变换能够到达的多个不同的潜在密码。而这个潜在密码我来梳理一下,这里用彩虹表来解释,在彩虹表中,一个明文A应用一次哈希函数得到一个散列值A,然后散列值应用一次还原函数得到明文B,明文B应用哈希函数得到散列值B,同理得到明文C,散列值C…假如里面存在潜在相同的散列值,那么可以得到前一个的还原函数是有效的,这个还原函数恰巧将上一个散列值给还原成我们想要的明文,这个明文又被哈希函数转化为那个相同的散列值。

3. 差分攻击

利用差分攻击破解MD5是在2004年我国山东大学的王小云教授及其团队发现的,他们找到了快速发现大量MD5碰撞的方法,即找到两个原始消息使他们的MD5散列值相同,并于2005年发表,他们的研究基于模块化差分,大体思路是先找到局部碰撞,然后分析差分如何传播,找到差分路径,再利用消息修改技术最后得到能产生碰撞的消息对。

差分攻击是一种密码分析技术,它被用来破解加密算法,包括哈希函数如MD5。然而,需要注意的是,差分攻击主要用于对称加密算法,而对于哈希函数,特别是像MD5这样的,通常采用的是其他类型的攻击方法,如碰撞攻击。不过,为了解释差分攻击的原理,我会首先描述它在对称加密算法中的应用,然后讨论它在哈希函数中的应用情景。

3.1. 差分攻击原理

在对称加密算法中

  1. 基本原理

    • 差分攻击依赖于观察当输入改变时输出如何变化的模式。攻击者会对加密算法的输入进行小的、有控制的改变,并观察输出的差异。
    • 目的是找出输入和输出之间的相关性,从而推断出加密密钥或算法的一部分内部操作。
  2. 步骤

    • 选择一对明文,它们之间有已知的差异。
    • 加密这两个明文,观察密文之间的差异。
    • 通过分析这些差异,尝试推断加密密钥或算法的某些特性。

在哈希函数中

  1. 碰撞攻击

    • 在哈希函数(如MD5)中,差分攻击通常是用于寻找碰撞的一种方法。这意味着寻找两个不同的输入,它们产生相同的哈希值。
    • 攻击者会尝试微小改变输入,并分析如何影响哈希值的改变,从而找到两个哈希值相同的不同输入。
  2. MD5的脆弱性

    • MD5由于设计上的缺陷,对这类攻击特别敏感。已经有多种方法展示了如何找到MD5的碰撞,即两个不同的输入产生相同的哈希值。
3.2. 举例说明

差分攻击

在这里插入图片描述

4. 相同前缀碰撞

在这里插入图片描述

李德刚, 杨阳, 曾光. 基于选择前缀攻击的哈希函数多文件格式碰撞[J]. 密码学报, 202X, X(X): 1–16. [DOI: 10.13868/j.cnki.jcr.000659]

相同前缀碰撞方法,该方法利用两组消息块序列,通过精心设计,使得它们经过MD5处理后能够产生相同的中间哈希值(IHV)。在此基础上,可以在这些块之后附加任意长度相同的后缀而不影响碰撞结果。这个过程包括:

  1. 选择消息块:从两个不同的消息块对 {B0, B1} 和 {B′0, B′1} 开始,这些块在内容上略有差异。

  2. 构建碰撞:通过对这些消息块的处理,确保它们在经历MD5哈希过程后,即使有不同的输入块,也能得到相同的中间哈希值(IHV)。

  3. 附加后缀:在经过处理的块后面附加任意相同的后缀(S),产生的完整消息为 {P||Sr||B0||B1||S} 和 {P||Sr||B′0||B′1||S},其中 P 是前缀,Sr 是填充部分,保证整个消息长度符合MD5处理的要求。

  4. 结果:最终,这两个完整的消息将产生相同的MD5哈希值,即使它们在 {B0, B1} 与 {B′0, B′1} 部分有所不同。

这个方法也应用到了差分原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/239344.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WPF组合控件TreeView+DataGrid之DataGrid封装-粉丝专栏

wpf的功能非常强大&#xff0c;很多控件都是原生的&#xff0c;但是要使用TreeViewDataGrid的组合&#xff0c;就需要我们自己去封装实现。 我们需要的效果如图所示&#xff1a; 这2个图都是第三方控件自带的&#xff0c;并且都是收费使用。 现在我们就用原生的控件进行封装一…

k8s中Helm工具实践

k8s中Helm工具实践 1&#xff09;安装redis-cluster 先搭建一个NFS的SC&#xff08;只需要SC&#xff0c;不需要pvc&#xff09;&#xff0c;具体步骤此文档不再提供&#xff0c;请参考前面相关章节。 下载redis-cluster的chart包 helm pull bitnami/redis-cluster --untar…

到底需要会那些技能?才算一个5年经验合格的软件测试工程师

一&#xff1a;经历讲解 微软外包自动化测试两年&#xff0c;而后转入互联网公司做移动端自动化测试一年&#xff0c;经历了入行时的迷茫&#xff0c;而后的笃定&#xff0c;转入移动后对自身定位和价值的怀疑&#xff0c;继而对自动化测试的重新认识&#xff0c;职场三年&…

【ICCV2023】MMVP:基于运动矩阵的视频预测

目录 导读 本文方法 步骤1&#xff1a;空间特征提取 步骤2&#xff1a;运动矩阵的构造和预测 步骤3&#xff1a;未来帧的合成和解码 实验 实验结果 消融实验 结论 论文链接&#xff1a;https://openaccess.thecvf.com/content/ICCV2023/html/Zhong_MMVP_Motion-Matrix…

【Java】spring

一、spring spring是一个很大的生态圈&#xff0c;里面有很多技术。 其中最基础的是spring framework&#xff0c;主要的技术 是springboot以及springcloud。 1、spring framework spring framework是spring生态圈中最基础的项目&#xff0c;是其他项目的基础。 1.1、核心…

Http---HTTP 请求报文

1. HTTP 请求报文介绍 HTTP最常见的请求报文有两种: GET 方式的请求报文POST 方式的请求报文 说明: GET: 获取web服务器数据POST: 向web服务器提交数据 2. HTTP GET 请求报文分析 HTTP GET 请求报文效果图: GET 请求报文说明: ---- 请求行 ---- GET / HTTP/1.1 # GET请…

华为鸿蒙开发适合哪些人学习?

随着鸿蒙系统的崛起&#xff0c;越来越多的人开始关注鸿蒙开发&#xff0c;并希望成为鸿蒙开发者。然而&#xff0c;鸿蒙开发并不适合所有人&#xff0c;那么哪些人最适合学习鸿蒙开发呢&#xff1f;本文将为您总结鸿蒙开发适合的人群。 一、具备编程基础的人 学习鸿蒙开发需要…

账号多、用户咨询量大无法及时回复?「互动管理」助力高效经营!

随着互联网行业不断向纵深发展&#xff0c;内容形态与营销场景也更加多元化。越来越多的品牌跑步入场&#xff0c;深耕社媒营销&#xff0c;建立多平台营销矩阵&#xff0c;借助社媒平台的全域态势助力品牌增长。 据云略《2023品牌新媒体矩阵营销洞察报告》显示&#xff0c;目前…

多任务数据采集

进程&#xff1a;操作系统中资源分配的基本单位 线程&#xff1a;使用进程资源处理具体任务 一个进程中可以有多个线程&#xff1a;进程相当于一个公司&#xff0c;线程是公司里面的员工。 一 多线程 多线程都是关于功能的并发执行。而异步编程是关于函数之间的非阻塞执行&…

数据结构和算法-红黑树(定义 性质 查找 插入 删除)

文章目录 红黑树的定义和性质为什么要发明红黑树&#xff1f;红黑树怎么考总览红黑树的定义实例&#xff1a;一颗红黑树练习&#xff1a;是否符合红黑树的要求一种可能的出题思路补充概念&#xff1a;节点黑高 红黑树的性质 红黑树的查找红黑树的插入实例小结与黑高相关的理论 …

【Java】网络编程-TCP回显服务器代码编写

前面我们讲了基于UDP的网络编程 UDP回显服务器 UDP字典服务器 下面我们来讲基于TCP的回显服务编写 1、服务器 import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; impo…

opencv入门到精通——OpenCV4.1.2之性能衡量与优化方法

目录 目标 使用OpenCV衡量性能 OpenCV中的默认优化 在IPython中衡量性能 更多IPython魔术命令 性能优化技术 目标 在图像处理中&#xff0c;由于每秒要处理大量操作&#xff0c;因此必须使代码不仅提供正确的解决方案&#xff0c;而且还必须以最快的方式提供。因此&#…

1270. 数列区间最大值(线段树求区间最大)

题目&#xff1a; 1270. 数列区间最大值 - AcWing题库 输入样例&#xff1a; 10 2 3 2 4 5 6 8 1 2 9 7 1 4 3 8输出样例&#xff1a; 5 8 代码&#xff1a; #include <cstdio> #include <iostream> #include <climits>//INT_MIN头文件using namespace s…

【经典LeetCode算法题目专栏分类】【第8期】滑动窗口:最小覆盖子串、字符串排列、找所有字母异位词、 最长无重复子串

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

2023年终总结 | 四平八稳

年度回顾 年度关键词&#xff1a;四平八稳 百科解读&#xff1a;形容物体放置的平稳或言行稳当&#xff1b;也指做事但求无过&#xff0c;不思进取。 我在写这篇文章就在思考用什么关键词来描述我过去这一年&#xff0c;似乎这一年没有特别让我特别惊喜的事情&#xff0c;无论是…

【实训项目】消费账单记录小程序

1.项目说明 背景&#xff1a; 着网络技术的不断发展&#xff0c;人们的消费观念在不断变化&#xff0c;消费手段也变得错综复杂。很多人都在困扰&#xff0c;钱到底花在了那里。 目的&#xff1a; 为了解决很多人的钱花在哪了的困扰&#xff0c;我们组决定制作一个消费账单…

【原理图PCB专题】原理图图纸锁定/解锁与PCB文件加密方式

在工作中我们会遇到需要冻结原理图进行评审和加密图纸防止被他人盗用的需求。那么在OrCAD Capture中如何对图纸进行锁定与解锁,如何在Allegro中对PCB工程进行加密呢? 原理图锁定与解锁 打开原理图,在图纸中单击右键,选择lock/unlock就可以进行锁定与解锁。 锁定时图纸图…

xml 可扩展标记语言

我是南城余&#xff01;阿里云开发者平台专家博士证书获得者&#xff01; 欢迎关注我的博客&#xff01;一同成长&#xff01; 一名从事运维开发的worker&#xff0c;记录分享学习。 专注于AI&#xff0c;运维开发&#xff0c;windows Linux 系统领域的分享&#xff01; 本…

【CF闯关练习】—— 900分段

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;cf闯关练习 &#x1f48c;其他专栏&#xff1a; &#x1f534;每日一题 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓…

java定义三套场景接口方案

一、背景 在前后端分离开发的背景下&#xff0c;后端java开发人员现在只需要编写接口接口。特别是使用微服务开发的接口。resful风格接口。那么一般后端接口被调用有下面三种场景。一、不需要用户登录的接口调用&#xff0c;第二、后端管理系统接口调用&#xff08;需要账号密…