深入理解 MD5 消息摘要算法和在密码存储中的应用及安全隐患
MD5 算法相信很多开发人员都听说过, 一个最常见的使用到它的地方就是密码的存储.
当然, 很多人会说, 这个算法已经不太安全了, 确实如果你想更安全的保存密码, 则应该考虑其它更安全的算法, 不过这不属于此次讨论的主题.
什么是 MD5
MD5 是一种算法, MD5 中的 MD 代表 Message Digest, 也即信息摘要.
至于数字 5, 则因它是从更早的 MD4 算法改进而来, 因此得名 MD5.
所以 MD5 即是信息摘要算法第五版.
什么是信息摘要算法
那什么又是信息摘要算法呢? 它本质上就是一个哈希函数(hash function).
又叫散列函数.
那什么又是哈希函数呢? 它是指这样一种函数: 它能把任意大小的数据映射(map)为一个固定大小的值.
A hash function is any function that can be used to map data of arbitrary size to fixed-size values.
哈希函数所返回的这个值称为哈希值(hash value), 又称为哈希码(hash codes), 或直接简称为哈希(hash).
具体例子
单纯地这样去讲会比较抽象, 因此这里引入具体例子来说明, 以 Java 为例, 可以这样去计算 MD5:
public void rawMd5() throws NoSuchAlgorithmException {byte[] bytes = "hello".getBytes(StandardCharsets.UTF_8);MessageDigest md = MessageDigest.getInstance("MD5");byte[] md5 = md.digest(bytes);
}
注: 以上代码计算了"hello"这个字符串对应的 utf-8 字节数组的 md5 的值.
另注: 因为摘要计算的输入是一个字节数组, 如果要计算字符串的摘要值, 则要转成某种编码的字节数组, 为保持一致, 应始终显式使用同一种编码, 比如 utf-8.
从以上代码中也不难看出, 一个 MD5 函数的输入和输出都是字节数组 byte[].
而代码中不能直接体现的一点则是: 输入可以是任意大小的字节数组, 输出则是固定大小的字节数组.
对于 MD5 算法而言, 这个输出值是一个固定大小为 16 字节的数组, 然后因为每个字节(byte)有 8 个位(bit), 所以最终的输出值是一个 16 × 8 = 128 位的二进制数. MD5 的值就是一个 128 位的二进制大整数.
比如下面就是一个具体的 MD5 的值, 以原始的 128 位二进制形式表示:
10001000100100011001000111110000100011111000000111010010110010101100010111101010000110011011110000111011111101111101100110111110
这个 MD5 值实际是对我的网站域名 xiaogd.net 作摘要的结果.
这个值的二进制形式实在是长得不要不要的, 所以一般会转换为十六进制形式, 共 16 组具体为: 88 91 91 f0 8f 81 d2 ca c5 ea 19 bc 3b f7 d9 be. 依然还是很长, 但比二进制好多了.
随便说一句, IPv6 的地址也是 128 bit 的, 所以也是像这般变态的长, 写成 16 进制也还是很长, 压根记不住…
最后通常还会去掉空格写成一个紧凑的 32 个字符的字符串的形式: 889191f08f81d2cac5ea19bc3bf7d9be, 也即是我们最常见到的 MD5 值的形式.
但请不要误解, MD5 的值并不是一个字符串, 更不是什么字母都能出现在里边的.
一个完整的代码示例如下, 使用 apache commons-codec 工具包的 Hex 工具类将字节数组转为 16 进制字符串形式:
package net.xiaogd.demo.basic;import org.apache.commons.codec.binary.Hex;
import org.junit.Assert;
import org.junit.Test;import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;public class Md5Test {@Testpublic void calcMd5() {Assert.assertEquals("889191f08f81d2cac5ea19bc3bf7d9be", md5("xiaogd.net"));}public static String md5(String input) {try {byte[] bytes = input.getBytes(StandardCharsets.UTF_8);MessageDigest md = MessageDigest.getInstance("MD5");byte[] hash = md.digest(bytes);// use apache commons-codec util jarreturn Hex.encodeHexString(hash);} catch (NoSuchAlgorithmException e) {throw new RuntimeException("md5 not support!", e);}}
}
所以如果你直接存储这个整数值的字节数组, 你只需要 16 字节的空间, 但如果你存储转为 16 进制表示的字符串, 因为最终结果是 32 个字符, 那么就至少需要 32 字节(ASCII) 空间了.
在数据库存储时, 通常会存为固定长度的 char(32), 编码选 ASCII, 因为所有字符都是 ASCII 字符, 而 ASCII 字符每个只需一字节空间去保存, 如果用其它字符集, 需要的空间可能会翻倍, 甚至三倍, 四倍.
MD5 算法的特性
其实从上面的结果中, 我们已知道了 MD5 算法的一个特性, 那就是它的结果是一个固定 16 字节数组的值.
也就是说不管你的输入是多长, 输出的结果都是那么长. 你输入是一个字节, 输出也是 16 字节, 你输入是 100 个字节, 输出也还是 16 字节.
因为 MD5 算法经常用在密码保存方面, 有些人就认为它是一种加密算法, 那么这种理解其实是有问题的. MD5 的所谓"加密"效果显然与一般的加密-解密算法中的加密不是一回事.
MD5 是做摘要, 而不是加密, 如果它是加密, 之后还能解密的那种算法, 一个很自然的问题就是, 它输出是固定的, 这就带来一个问题, 就像前面说的, 输入 100 个字节, 输出还是 16 字节, 假如我的密码就是一个很变态的 100 个字符长度的, 经过你的 MD5 所谓"加密"后, 成了一个 32 字符的值, 字符变少了, 信息肯定发生了丢失, 你怎么可能从更少的 32 字符解密回去 100 个字符呢? 显然是不可能办到的.
另外一个显然的问题则是, 加解密算法是要求密钥的(或是一般意义上的密码), 然后在加密以及解密的过程, 密钥是要作为参数参与方法调用的, 而我们看前述 MD5 值生成的代码, 并没有涉及密钥.
而以上讨论又自然引出了 MD5 的又一特性, MD5 的所谓"加密", 实际上是 “one-way hash”, 也即是单向的不可逆的 hash 过程, 也即是不管你的输入字节数是比输出字节数大还是小, 你都不可能从输出值去反推到输入值.
这是 MD5 作为一种 密码学哈希函数(CHF: Cryptographic Hash Function) 所必须具备的一个特性.
不严谨的说法就是它是无法"解密"的, 但前面说了, 既然它都不是"加密", 也没有密钥, 也自然就无所谓解密了.
当然, 说到这里, 有人可能又要争辩说, MD5 是可以通过暴力方式解密的, 有所谓的 彩虹表(rainbow table) 可以倒推出原始的密码.
嗯, 这么说也确实是事实, 但具体解析这是怎么回事, 包括使彩虹表成为可能的, 则与 MD5 算法的另一个特性有很大关系: 那就是它是一种 确定性(deterministic) 的算法.
所谓确定性, 就是对于同样的输入, 总是产生同样的输出.
来看一个具体的代码示例:
@Test
public void recalcMd5() {Assert.assertEquals("889191f08f81d2cac5ea19bc3bf7d9be", md5("xiaogd.net"));Assert.assertEquals("889191f08f81d2cac5ea19bc3bf7d9be", md5("xiaogd.net"));
}
注: md5 工具方法见前述代码示例, 此处略
可以看到两次对 xiaogd.net 这个字符串进行 MD5 的结果都是一样的. 你不会碰到说, 同一个字符串第一次这个结果, 下一次又是另一个结果, 这样就乱套了.
MD5 保证不会发生这样的情况, 而这个确定性的特性也使得用 MD5 来存储密码成为可能.
具体而言整个流程是这样去实现的, 就比如说我准备注册用户 xgd, 然后用我的域名 xiaogd.net 作为对应的密码吧, 那么在第一次用户注册的时候, 就把用户名 xgd 和根据密码 xiaogd.net 算出的 MD5 的值 889191f08f81d2cac5ea19bc3bf7d9be 存起来:
xgd --> 889191f08f81d2cac5ea19bc3bf7d9be
而密码 xiaogd.net 本身则丢弃了, 不保存在数据库中.
这样的方案还是有问题的, 但这里的目的不是要讨论怎么安全的存储密码, 而是探讨 MD5 存储密码的原理, 因而采用了一个简单的有缺陷的方案.
后续, 当用户再次登录时, 自然, 用户还是传过来 xgd 这个用户名和 xiaogd.net 这个密码, 而数据库里并没有这个密码, 怎么去验证呢?
答案也很简单, 就是再次对用户输入的密码进行 MD5 计算, 然后与数据库保存的 MD5 值进行比较. 那么根据算法的确定性的特征, 如果用户输入的还是同样的密码, MD5 后的值自然也还是同样的 889191f08f81d2cac5ea19bc3bf7d9be, 这样就能与数据库先前保存的值匹配上了, 也就可以判定允许用户登录了;
而用户再次传过来的密码值在完成 MD5 运算后依然会被丢弃而不会存起来.
安全的做法必然是这样去要求的, 那就是永远都不要明码保存用户的密码, 另一方面, 这也导致了"找回密码"功能并不会真正让你找回密码, 只能让你重新设置密码, 然后覆盖掉原有的哈希值.
反之, 假如用户输入了错误的密码, 那么经 MD5 计算就会产生不一样的值:
事实上, 哪怕只有一点点的不同, 结果也会剧烈的变化, 有点像是 蝴蝶效应(Butterfly Effect) 或是 雪崩效应(Avalanche Effect) 那样, 这也可以算是 MD5 算法的一个特性.
比如缺了点的 xiaogdnet, 可谓字面意义上的有一"点"不同, MD5 的结果就会变成这样: dc0c594ffbc99b8a8d5a6fa022738ad6, 对比原来的 889191f08f81d2cac5ea19bc3bf7d9be, 结果可谓天差地别.
也就无法与数据库保存的值匹配上, 这种情况下登录就被拒绝了.
当然, 你可能好奇, 有没有可能输入了一个错误的密码, 却产生了相同的 MD5 值呢? 这实际就是所谓的 MD5 的 碰撞(Collision) 问题了, 我们留待后面再分析. 简单讲就是理论上是可能的, 这种可能性甚至有无数种, 但实际上, 想产生一个碰撞却并不容易.
综上, 此种密码存储及验证方案, 在安全性上有两个优势:
- 没有存储用户的原始的明文密码, 所以即便是内鬼也不可能知道用户的密码;
- MD5 算法是不可逆的, 即便内鬼要捣乱或者数据库泄露了, 也无法倒推出用户的密码.
既然如此, 所谓的暴力破解及彩虹表又是怎么回事呢? 这就多少涉及到人性了.
安全界早就有这种共识: 那就是系统安全最薄弱的环节往往就是人本身.
在其它很多方面, 人的问题也常常出现, 见此处的一个调侃: PEBKAC
首先, 倒推确实是不可能, 但根据 MD5 算法的确定性, 却可以进行正推.
所谓正推, 就好比你知道 xiaogd.net 经 MD5 会生成 889191f08f81d2cac5ea19bc3bf7d9be, 那么当你拿到 889191f08f81d2cac5ea19bc3bf7d9be, 你反过来就知道了原始的密码是 xiaogd.net.
咋一看时, 密码的组合是无穷无尽的, 这使得正推存在很大的困难, 可问题也恰恰出在这里, 很多用户经常使用很简单的弱密码, 比如只使用 6 位的数字, 则所有的可能不过是 100 万种而已.
那么假如此时我想去破解, 我就先把 000000 ~ 999999 的一百万个 MD5 值全部先算出来, 然后存到一个数据库的表里:
md5(‘000000’) --> 670b14728ad9902aecba32e22fa4f6bd;
md5(‘000001’) --> 04fc711301f3c784d66955d98d399afb;
…
…
md5(‘999998’) --> 755af25720023b2f852105910b125ecc;
md5(‘999999’) --> 52c69e3a57331081823331c4e69d3f2e;
此时, 假如某个泄露的用户数据库里有条记录的 md5 值为 755af25720023b2f852105910b125ecc, 我不知道密码, 但我反过来一查我的表, 发现这个 MD5 值正好与 md5(‘999998’) 的值相同, 那么显然这个用户的密码就是 999998 了.
理论上讲, 因为存在碰撞的可能, 用户的密码也可能是另一个, 但从登录的原理上看, 用 999998 也是可以登录的, 产生碰撞的两个或多个均能通过登录校验.
而这样的一个表, 即是所谓的 彩虹表(rainbow table) 了, 如果你有足够的资源, 对于位数有限的密码组合, 是完全可能穷举完的, 这种穷举式的正向 暴力破解(brute-force attack) 是很难防止的.
当然, 从以上叙述中也不难明白, 所谓破解, 不是真的破解, 另外, 它只能破解常见的以及较短的弱密码. 如果你使用一个位数很长, 又包含各种大写小字母, 各种特殊符号的强密码, 那么一般来说, 即便有彩虹表也无法奈你何, 毕竟要穷举如此之多的记录是不可能的.
为抵御这种彩虹表攻击, 可以在系统中透明地为用户的密码额外拼接上一个随机的字符串值, 即所谓的 盐值(salt), 然后再计算密码加盐后的 md5 值并保存, 这个过程称为 加盐.
验证的过程也是类似的, 同样是把再次传过来的密码加上盐值去计算 md5, 并与数据库保存的值去验证.
举个具体例子, 先生成一个随机的字符串, 也即盐值, 比如"e3f$8%0"
为叙述方便, 这里用了一个较短的随机值, 实际中可以考虑更长的随机值.
如果张三此时用了一个简单的密码 666666, 拼接后计算的是 666666e3f$8%0 的 md5 值, 那么这个串在那些暴力破解的彩虹表里就不一定有了.
李四用了一个简单的密码 888888, 拼接后计算的是 888888e3f$8%0 的 md5 值, 同样的这个串在那些暴力破解的彩虹表里也不一定有.
更加安全的做法是为每个用户都生成专属的随机串, 然后把这个盐值存在对应用户记录中, 也即存储"用户名 | 随机盐值 | 密码+随机盐值的 md5 值".
举个例子, 王五用了 123456 作为密码, 赵六也用了 123456 作为密码, 为王五生成他专门的随机盐值 a&58#93, 为赵六也生成他专属的随机盐值 b78!967, 虽然他们都用了相同的密码, 但拼上不同的盐值后, 计算到的 md5 自然也不同了.
对于王五, 算的是 123456a&58#93 的 md5 值, 对于赵六, 算的是 123456b78!967 的 md5 值, 数据库里存储的记录是这样的:
用户名 | 随机盐值 | 密码+随机盐值的 md5 值
王五 | a&58#93 | eed2402dbd1605cb1fa8fe5a270a1849
赵六 | b78!967 | fcbb4aa14c17bd8970e664e23c0fd87c
这样一个数据库即便被泄露了, 攻击者也很难倒推出密码, 他也无从知道谁谁谁是否使用了相同的密码.
注意, 实践中也有人采取二次 md5 的做法, 比如对于 123456 这个密码, 存储的是 md5(md5(‘123456’)), 这种方式其实是不安全的!!
攻击者也完全可以对那些简单的密码进行二次 md5 计算, 这个计算量不过翻了一倍而已, 完全可以再构建一个这样的表.
md5(‘123456’) = e10adc3949ba59abbe56e057f20f883e;
md5(‘e10adc3949ba59abbe56e057f20f883e’) = 14e1b600b1fd579f47433b88e8d85291;
如果攻击者发现一条记录值是 14e1b600b1fd579f47433b88e8d85291, 则第一次查表他得到了 ‘e10adc3949ba59abbe56e057f20f883e’, 问题是谁会去用如此复杂的密码呢? 于是他猜测也许用了多次的 md5, 于是他用 e10adc3949ba59abbe56e057f20f883e 再次去反查, 就得到了最终的密码 123456.
最后说说所谓的碰撞问题. 事实上, 从固定 16 字节的输出结果来看, 而另一方面输入的可能性则是无限的, 那么这就从原理上决定了碰撞是无可避免的.
吾生也有涯,而知也无涯。以有涯随无涯,殆已! --<<庄子·养生主>>
有一个所谓的抽屉原理, 举个简单的例子来说, 你有 10 个苹果, 要放到 9 个抽屉里, 然后我不需要知道你到底是怎么放的, 我都可以断言, 至少有一个抽屉里面至少有两个苹果.
为啥我可以这么断言呢? 很简单, 即便前 9 个苹果你放得非常均匀, 9 个抽屉每个都恰好放一个, 你手里还是会剩下一个无处可去, 而每个抽屉此时都放了有苹果, 因此最后无论你把这仅剩的苹果放在哪个抽屉里, 那那个抽屉里就有两个苹果, 因此就得出了前述的结论: 至少有一个抽屉里面至少有两个苹果. 萝卜太多, 坑位不够, 还能咋地?
如果放得不均匀, 有些抽屉里最后可能还不止两个苹果, 三个, 四个乃至更多都有可能.
那么对于 MD5 来说, 原理是类似的, 因为输出是固定的 128 比特, 也就限制了所有可能的值是 2^128(2 的 128 次方) 种, 也就是值的范围是从 0 ~ 2^128-1, 撑死了就这么多不同种输出.
注: 其实这个值相当大了, 它约等于 3.4×10^38, 340万亿亿亿亿.
尽管这个空间是非常非常的大, 但输入的可能性更大, 是无穷的, 就好比你有一个相当大数目的抽屉, 可我却有无穷多个苹果, 我怎么都能把你的抽屉给塞满了.
一个很简单的方式就是, 我就从 md5(‘0’), md5(‘1’), md5(‘2’)… 一直算到 md5(‘2^128’)(注: 展开后的值, 这里为简化才写成指数形式), 那么我就有了 2^128+1 种不同输入, 而输出受固定位的影响最多也只有 2^128 种不同结果, 那么根据抽屉原理, 里面就至少发生了一次碰撞, 也即必然有两个不同的输入值, 最后产生了相同的输出值.
当然了, 实际上要产生一个碰撞则是很困难的, 一方面是这个空间非常大, 另一方面则是哈希函数的特性了, 还记得它也叫 散列函数 吗? 可以理解为它就是尽量把值分得散散的.
可以这么去认为, 对于两个不同的字符串进行各自进行 md5 的值, 就相当于在这个空间随机返回两个数. 哪怕就是在 0~100 间随机返回两个数, 相同的概率也不高, 何况如此之大的一个范围呢.
尽管碰撞是如此之难, 但算法毕竟不是完美的, 还是有人根据 md5 算法的一些缺陷构建出了一些碰撞的例子.
下面是一个具体碰撞的例子(注意有两处加粗的部分是不同的, 另注: 是它们所代表的字节数组而不是字符串, 这是两个十六进制的大数):
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2
4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2
但它们都会生成这个相同的 md5 值: 008ee33a9d58b51cfeb425b0959121c9.
以下为测试的相关代码:
@Test
public void md5Collision() throws Exception {byte[] b1 = Hex.decodeHex("4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2");byte[] b2 = Hex.decodeHex("4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2");MessageDigest md = MessageDigest.getInstance("MD5");byte[] h1 = md.digest(b1);byte[] h2 = md.digest(b2);String h1Str = Hex.encodeHexString(h1);String h2Str = Hex.encodeHexString(h2);//System.out.println(h1Str);//System.out.println(h2Str);Assert.assertEquals(h1Str, h2Str);
}
注意: 是直接取 16 进制字符串代表的字节数组, 即 Hex.decodeHex 中的做法, 而不是取字符串的 string.getBytes, 直接转换与 string.getBytes 得到的字节数组是不一样的.
关于 md5 的介绍就到这里.
相关文章:
深入理解 MD5 消息摘要算法和在密码存储中的应用及安全隐患
MD5 算法相信很多开发人员都听说过, 一个最常见的使用到它的地方就是密码的存储. 当然, 很多人会说, 这个算法已经不太安全了, 确实如果你想更安全的保存密码, 则应该考虑其它更安全的算法, 不过这不属于此次讨论的主题. 什么是 MD5 MD5 是一种算法, MD5 中的 MD 代表 Message…...

python网络爬虫指南二:多线程网络爬虫、动态内容爬取(待续)
文章目录 一、多线程网络爬虫1.1 线程的基础内容、GIL1.2 创建线程的两种方式1.3 threading.Thread类1.4 线程常用方法和锁机制1.5 生产者-消费者模式1.5.1 生产者-消费者模式简介1.5.2 Condition 类协调线程 1.6 线程中的安全队列1.6 多线程爬取王者荣耀壁纸1.6.1 网页分析1.6…...
华为AirEgine9700S AC配置示例
Vlan97为管理Vlan <AirEgine9700S>dis cu Software Version V200R021C00SPC100 #sysname AirEgine9700S #http timeout 60http secure-server ssl-policy default_policyhttp secure-server server-source -i allhttp server enable #set np rss hash-mode 5-tuple # md…...
VUE3基础
一、vue-router v4.x 介绍 | Vue Router 1、安装 yarn add vue-routernext next代表最新的版本 2、路由配置 在src目录下,新建router/index.ts,具体配置如下 import {RouteRecordRaw,createRouter,createWebHashHistory} from vue-router const r…...

Qt应用开发(基础篇)——日历 QCalendarWidget
一、前言 QCalendarWidget类继承于QWidget,是Qt设计用来让用户更直观的选择日期的窗口部件。 时间微调输入框 QCalendarWidget根据年份和月份初始化,程序员也通过提供公共函数去改变他们,默认日期为当前的系统时间,用户通过鼠标和…...

Python学习笔记:正则表达式、逻辑运算符、lamda、二叉树遍历规则、类的判断
1.正则表达式如何写? 序号实例说明1.匹配任何字符(除换行符以外)2\d等效于[0-9],匹配数字3\D等效于[^0-9],匹配非数字4\s等效于[\t\r\n\f],匹配空格字符5\S等效于[^\t\r\n\f],匹配非空格字符6\w等效于[A-Za-z0-9]&…...

【滑动窗口】leetcode1004:最大连续1的个数
一.题目描述 最大连续1的个数 这道题要我们找最大连续1的个数,看到“连续”二字,我们要想到滑动窗口的方法。滑动窗口的研究对象是一个连续的区间,这个区间需要满足某个条件。那么本题要找的是怎样的区间呢?是一个通过翻转0后得到…...

力扣:73. 矩阵置零(Python3)
题目: 给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 来源:力扣(LeetCode) 链接:力扣(LeetCode)官网 - 全球极客挚…...
VB|基础语法 变量定义 函数定义 循环语句 IF判断语句等
文章目录 变量定义函数定义控制台输入输出switch case语句IF语句FOR循环语句不等于逻辑运算符 变量定义 int Dim 变量名 As Int32 0 string Dim 变量名 As String "" bool Dim 变量名 As Boolean False 枚举 Dim 变量名 As 枚举名 数组 Dim array(256) As String…...
Github 博客搭建
Github 博客搭建 准备工作 准备一个 github 账号;建立 github 仓库,仓库名为 username.github.io,同时设置仓库为 public;clone 仓库,写入一个 index.html 文件,推送到仓库(许多网上的教程会有…...
模型预测笔记(三):通过交叉验证网格搜索机器学习的最优参数
文章目录 网络搜索介绍步骤参数代码实现 网络搜索 介绍 网格搜索(Grid Search)是一种超参数优化方法,用于选择最佳的模型超参数组合。在机器学习中,超参数是在训练模型之前设置的参数,无法通过模型学习得到。网格搜索…...

创建型模式-建造者模式
使用多个简单的对象一步一步构建成一个复杂的对象 主要解决:主要解决在软件系统中,有时候面临着"一个复杂对象"的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部…...

Rust常用加密算法
哈希运算(以Sha256为例) main.rs: use crypto::digest::Digest;use crypto::sha2::Sha256;fn main() { let input "dashen"; let mut sha Sha256::new(); sha.input_str(input); println!("{}", sha.result_str());} Cargo.toml: [package]n…...

[管理与领导-55]:IT基层管理者 - 扩展技能 - 1 - 时间管理 -2- 自律与自身作则,管理者管好自己时间的五步法
前言: 管理好自己的时间,不仅仅是理念,也是方法和流程。 步骤1:理清各种待办事项 当提到工作事项时,这通常指的是要完成或处理的工作任务或事务。这些事项可以包括以下内容: 任务分配:根据工作…...
电子商务员考试题库及答案(中级)--判断题
电子商务员题库 一、判断题 1.EDI就是按照商定的协议,将商业文件分类,并通过计算机网络,在贸易伙伴的计算机网络系统之间进行数据交换和自动处理。〔〕 2.相互通信的EDI的用户必须使用相同类型的计算机。〔 〕 3.EDI采用共同…...

(WAF)Web应用程序防火墙介绍
(WAF)Web应用程序防火墙介绍 1. WAF概述 Web应用程序防火墙(WAF)是一种关键的网络安全解决方案,用于保护Web应用程序免受各种网络攻击和威胁。随着互联网的不断发展,Web应用程序变得越来越复杂&#x…...
SpringMVC拦截器常见应用场景
在Spring MVC中,拦截器是通过实现HandlerInterceptor接口来定义的。该接口包含了三个方法: preHandle:在请求到达处理器之前执行,可以进行一些预处理操作。如果返回false,则请求将被拦截,不再继续执行后续的…...

爬虫:绕过5秒盾Cloudflare和DDoS-GUARD
本文章仅供技术研究参考,勿做它用! 5秒盾的特点 <title>Just a moment...</title> 返回的页面中不是目标数据,而是包含上面的代码:Just a moment... 或者第一次打开网页的时候: 这几个特征就是被Cloud…...
数据仓库环境下的超市进销存系统结构
传统的进销存系统建立的以单一数据库为中心的数据组织模式,已经无 法满足决策分析对数据库系统的要求,而数据仓库技术的出现和发展,为上述问题 的解决提供了强有力的工具和手段。数据仓库是一种对多个分布式的、异构的数据 库提供统一查询…...

leetcode:2011. 执行操作后的变量值(python3解法)
难度:简单 存在一种仅支持 4 种操作和 1 个变量 X 的编程语言: X 和 X 使变量 X 的值 加 1--X 和 X-- 使变量 X 的值 减 1 最初,X 的值是 0 给你一个字符串数组 operations ,这是由操作组成的一个列表,返回执行所有操作…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...

【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程
STM32F1 本教程使用零知标准板(STM32F103RBT6)通过I2C驱动ICM20948九轴传感器,实现姿态解算,并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化,适合嵌入式及物联网开发者。在基础驱动上新增…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...