当前位置: 首页 > news >正文

深入理解 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) 问题了, 我们留待后面再分析. 简单讲就是理论上是可能的, 这种可能性甚至有无数种, 但实际上, 想产生一个碰撞却并不容易.

综上, 此种密码存储及验证方案, 在安全性上有两个优势:

  1. 没有存储用户的原始的明文密码, 所以即便是内鬼也不可能知道用户的密码;
  2. 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目录下&#xff0c;新建router/index.ts&#xff0c;具体配置如下 import {RouteRecordRaw,createRouter,createWebHashHistory} from vue-router const r…...

Qt应用开发(基础篇)——日历 QCalendarWidget

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

Python学习笔记:正则表达式、逻辑运算符、lamda、二叉树遍历规则、类的判断

1.正则表达式如何写&#xff1f; 序号实例说明1.匹配任何字符(除换行符以外)2\d等效于[0-9]&#xff0c;匹配数字3\D等效于[^0-9]&#xff0c;匹配非数字4\s等效于[\t\r\n\f]&#xff0c;匹配空格字符5\S等效于[^\t\r\n\f]&#xff0c;匹配非空格字符6\w等效于[A-Za-z0-9]&…...

【滑动窗口】leetcode1004:最大连续1的个数

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

力扣:73. 矩阵置零(Python3)

题目&#xff1a; 给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚…...

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 账号&#xff1b;建立 github 仓库&#xff0c;仓库名为 username.github.io&#xff0c;同时设置仓库为 public&#xff1b;clone 仓库&#xff0c;写入一个 index.html 文件&#xff0c;推送到仓库&#xff08;许多网上的教程会有…...

模型预测笔记(三):通过交叉验证网格搜索机器学习的最优参数

文章目录 网络搜索介绍步骤参数代码实现 网络搜索 介绍 网格搜索&#xff08;Grid Search&#xff09;是一种超参数优化方法&#xff0c;用于选择最佳的模型超参数组合。在机器学习中&#xff0c;超参数是在训练模型之前设置的参数&#xff0c;无法通过模型学习得到。网格搜索…...

创建型模式-建造者模式

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

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- 自律与自身作则,管理者管好自己时间的五步法

前言&#xff1a; 管理好自己的时间&#xff0c;不仅仅是理念&#xff0c;也是方法和流程。 步骤1&#xff1a;理清各种待办事项 当提到工作事项时&#xff0c;这通常指的是要完成或处理的工作任务或事务。这些事项可以包括以下内容&#xff1a; 任务分配&#xff1a;根据工作…...

电子商务员考试题库及答案(中级)--判断题

电子商务员题库 一、判断题 1&#xff0e;EDI就是按照商定的协议&#xff0c;将商业文件分类&#xff0c;并通过计算机网络&#xff0c;在贸易伙伴的计算机网络系统之间进行数据交换和自动处理。〔〕 2.相互通信的EDI的用户必须使用相同类型的计算机。〔 〕 3.EDI采用共同…...

(WAF)Web应用程序防火墙介绍

&#xff08;WAF&#xff09;Web应用程序防火墙介绍 1. WAF概述 ​ Web应用程序防火墙&#xff08;WAF&#xff09;是一种关键的网络安全解决方案&#xff0c;用于保护Web应用程序免受各种网络攻击和威胁。随着互联网的不断发展&#xff0c;Web应用程序变得越来越复杂&#x…...

SpringMVC拦截器常见应用场景

在Spring MVC中&#xff0c;拦截器是通过实现HandlerInterceptor接口来定义的。该接口包含了三个方法&#xff1a; preHandle&#xff1a;在请求到达处理器之前执行&#xff0c;可以进行一些预处理操作。如果返回false&#xff0c;则请求将被拦截&#xff0c;不再继续执行后续的…...

爬虫:绕过5秒盾Cloudflare和DDoS-GUARD

本文章仅供技术研究参考&#xff0c;勿做它用&#xff01; 5秒盾的特点 <title>Just a moment...</title> 返回的页面中不是目标数据&#xff0c;而是包含上面的代码&#xff1a;Just a moment... 或者第一次打开网页的时候&#xff1a; 这几个特征就是被Cloud…...

数据仓库环境下的超市进销存系统结构

传统的进销存系统建立的以单一数据库为中心的数据组织模式&#xff0c;已经无 法满足决策分析对数据库系统的要求&#xff0c;而数据仓库技术的出现和发展&#xff0c;为上述问题 的解决提供了强有力的工具和手段。数据仓库是一种对多个分布式的、异构的数据 库提供统一查询…...

leetcode:2011. 执行操作后的变量值(python3解法)

难度&#xff1a;简单 存在一种仅支持 4 种操作和 1 个变量 X 的编程语言&#xff1a; X 和 X 使变量 X 的值 加 1--X 和 X-- 使变量 X 的值 减 1 最初&#xff0c;X 的值是 0 给你一个字符串数组 operations &#xff0c;这是由操作组成的一个列表&#xff0c;返回执行所有操作…...

ubuntu下mysql

安装&#xff1a; sudo apt update sudo apt install my_sql 安装客户端&#xff1a; sudo apt-get install mysql-client sudo apt-get install libmysqlclient-dev 启动服务 启动方式之一&#xff1a; sudo service mysql start 检查服务器状态方式之一&#xff1a;sudo …...

大模型从入门到应用——LangChain:链(Chains)-[链与索引:检索式问答]

分类目录&#xff1a;《大模型从入门到应用》总目录 下面这个示例展示了如何在索引上进行问答&#xff1a; from langchain.embeddings.openai import OpenAIEmbeddings from langchain.vectorstores import Chroma from langchain.text_splitter import CharacterTextSplitte…...

【LeetCode-中等题】142. 环形链表 II

文章目录 题目方法一&#xff1a;哈希表set去重方法二&#xff1a;快慢指针 题目 方法一&#xff1a;哈希表set去重 思路&#xff1a;我们遍历链表中的每个节点&#xff0c;并将它记录下来&#xff1b;一旦遇到了此前遍历过的节点&#xff0c;就可以判定链表中存在环。借助哈希…...

Android TV开发之VerticalGridView

Android TV应用开发和手机应用开发是一样的&#xff0c;只是多了焦点控制&#xff0c;即选中变色。 androidx.leanback.widget.VerticalGridView 继承 BaseGridView &#xff0c; BaseGridView 继承 RecyclerView 。 所以 VerticalGridView 就是 RecyclerView &#xff0c;使…...

SpringBoot+Vue项目添加腾讯云人脸识别

一、引言 人脸识别是一种基于人脸特征进行身份认证和识别的技术。它使用计算机视觉和模式识别的方法&#xff0c;通过分析图像或视频中的人脸特征&#xff0c;例如脸部轮廓、眼睛、鼻子、嘴巴等&#xff0c;来验证一个人的身份或识别出他们是谁。 人脸识别可以应用在多个领域…...

什么是IPv4?什么又是IPv6?

IPv4网络IPv4地址 IPv6网络IPv6地址 路由总结感谢 &#x1f496; hello大家好&#x1f60a; IPv4网络 IPv4&#xff08;Internet Protocol Version 4&#xff09;是当今互联网上使用的主要网络协议。 IPv4地址 IPv4 地址有32位&#xff0c;通常使用点号分隔的四个十进制八位…...

飞腾FT-2000/4、D2000 log报错指导(3)

在爱好者群中遇见了很多的固件问题,这里总结记录了大家的交流内容和调试心得。主要是飞腾桌面CPU FT-2000/4 D2000相关的,包含uboot和UEFI。希望对大家调试有所帮助。 这个专题会持续更新,凑够一些就发。 23 在s3 唤醒时报错如下 check suspend ,Platform exception report…...

基于安卓的考研助手系统app 微信小程序

&#xff0c;设计并开发实用、方便的应用程序具有重要的意义和良好的市场前景。HBuilder技术作为当前最流行的操作平台&#xff0c;自然也存在着大量的应用服务需求。 本课题研究的是基于HBuilder技术平台的安卓的考研助手APP&#xff0c;开发这款安卓的考研助手APP主要是为了…...

Leetcode:238. 除自身以外数组的乘积【题解超详细】

纯C语言实现&#xff08;小白也能看明白&#xff09; 题目 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数…...

基于单片机的智能数字电子秤proteus仿真设计

一、系统方案 1、当电子称开机时&#xff0c;单片机会进入一系列初始化&#xff0c;进入1602显示模式设定&#xff0c;如开关显示、光标有无设置、光标闪烁设置&#xff0c;定时器初始化&#xff0c;进入定时器模式&#xff0c;如初始值赋值。之后液晶会显示Welcome To Use Ele…...

桂林网站seo/广安百度推广代理商

sudo chown xn caffe/ -R 其中xn为你的用户名&#xff0c;caffe为你的需要解锁的文件。 虽然sudo chmod -R 777 也可以解锁&#xff0c;但是可能会对你的文件夹造成损伤。...

推荐手机网站建设/点击排名软件哪个好

2019独角兽企业重金招聘Python工程师标准>>> Apache/lighttpd: 相当于一个request proxy&#xff0c;根据配置&#xff0c;把不同的请求转发给不同的server处理&#xff0c;例如静态的文件请求自己处理&#xff0c;这个时候它就像一个web server&#xff0c;对于fas…...

山东日照今日疫情/搜索引擎优化排名技巧

1. 基本概念 光功率计&#xff08;OPM&#xff09;&#xff1a;是用于测量光信号中的功率的装置&#xff0c;它通常指用于测试光的平均功率(Average Power)dB&#xff1a;表示相对功率 P(dB) 10 log(P/Pref)dBm&#xff1a;表示绝对功率 P(dBm) 10 log (P/1mW) 0dBm&…...

网站开发需要先学数据库么/网络营销在哪里学比较靠谱

1、curl仍然是最好的HTTP库&#xff0c;没有之一。 可以解决任何复杂的应用场景中的HTTP 请求2. 文件流式的HTTP请求比较适合处理简单的HTTP POST/GET请求&#xff0c;但不适用于复杂的HTTP请求3. PECL_HTTP扩展写代码更加简洁&#xff0c;省事&#xff0c; 但成熟度不好&#…...

开江建设局网站/seo综合查询怎么关闭

htons(), ntohl(), ntohs()&#xff0c;htons() 函数&#xff1a; 转载自&#xff1a;https://blog.csdn.net/myyllove/article/details/83380209 atoi()和itoa()函数 转载自&#xff1a;https://www.cnblogs.com/ralap7/p/9171613.html...

中国最火的网站/免费网络推广渠道

#基于ip设置 server{ listen 80; server_name 192.168.116.129; location /{     root /usr/etc/ngin/html/ip;     index index.html;   } } #基于域名 server{   listen 80;   server_name z.com;   location /{     root z.com;     index index.ht…...