盐噪声和胡椒噪声的区别

A brief note - this article is about the theory of how to crack hashed passwords. Understanding how cybercriminals execute attacks is extremely important for understanding how to secure systems against those types of attacks.

简要说明-本文是有关如何破解哈希密码的理论。 了解网络罪犯如何执行攻击对于了解如何保护系统免受这些类型的攻击极为重要。

But attempting to hack a system you do not own is likely illegal in your jurisdiction (plus hacking your own systems may [and often does] violate any warranty for that product).

但是,尝试对您不拥有的系统进行黑客入侵在您的管辖范围内可能是非法的(加上对您自己的系统进行黑客攻击可能[并且经常]违反该产品的任何保证)。

This article assumes some level of knowledge of hashing functions and basic password cracking techniques - if you don't understand those topics, check out these articles.

本文假设您对散列函数和基本的密码破解技术有所了解-如果您不理解这些主题,请查阅这些 文章 。

So, you've obtained a set of hashed passwords. Brute forcing the hash will take a very long time. How can you speed up this process?

因此,您已经获得了一组哈希密码。 暴力破解哈希将花费很长时间。 您如何加快这一过程?

等等,我以为哈希函数是单向的! 您如何破解哈希函数? (Wait, I thought hash functions were one-way! How do you crack hash functions? )

Unfortunately, the hashing functions which are used for hashing passwords aren't always as secure as generally approved hash functions. For example, the hashing function used for old Windows devices is known as LM Hash, which is so weak that it can be cracked in a few seconds.

不幸的是,用于哈希密码的哈希函数并不总是像一般认可的哈希函数那样安全。 例如,用于旧Windows设备的哈希函数称为LM哈希,它非常弱,可以在几秒钟内被破解。

Also, you don't need to reverse engineer the hash. Instead, you can use a pre-computed set of plaintext passwords and the corresponding hash value (<password>, <hash>). This tells a hacker what plaintext value produces a specific hash.

另外,您不需要对哈希进行反向工程。 而是可以使用一组预先计算的纯文本密码和相应的哈希值(<password>,<hash>)。 这告诉黑客什么纯文本值会产生特定的哈希。

With this you'll know what plaintext value produces the hash you're looking for. When you enter a password the computer will hash this value and compare it to the stored value (where it will match) and you'll be able to authenticate. Thus, you don't actually need to guess someone's password, just a value which will create the same hash.

有了它,您将知道什么纯文本值会生成您要查找的哈希。 输入密码后,计算机将对该值进行哈希处理并将其与存储的值(匹配的值)进行比较,然后您便可以进行身份​​验证。 因此,您实际上不需要猜测某个人的密码,只需猜测一个将创建相同哈希值的值即可。

This is called a collision. Essentially, as a hash can take data of any length or content, there are unlimited possibilities for data which can be hashed.

这称为碰撞。 本质上,由于散列可以获取任何长度或内容的数据,因此可以散列数据的可能性是无限的。

Since a hash converts this text into a fixed length content (for example, 32 characters), there are a finite number of combinations for a hash. It is a very very large number of possibilities, but not an infinite one.

由于哈希将文本转换为固定长度的内容(例如32个字符),因此哈希的组合数量有限。 这是非常众多的可能性,但不是无限的。

Eventually two different sets of data will yield the same hash value.

最终,两组不同的数据将产生相同的哈希值。

Precomputed tables are very helpful in achieving this, as they save significant time and computing power. Using a pre-computed set of hashes to look up a password hash is called a 'lookup-table attack'. These tables are used by system administrators to test the strength of their users' passwords, and are often available online or for purchase. However, they can also be used by nefarious hackers.

预先计算的表对于实现此目标非常有帮助,因为它们可以节省大量时间和计算能力。 使用一组预先计算的哈希表来查找密码哈希称为“查找表攻击”。 系统管理员使用这些表来测试其用户密码的强度,并且通常可以在线购买或购买。 但是,邪恶的黑客也可以使用它们。

If a password is insecure (let's say someone uses a password 5 characters long), it can be relatively easily cracked. For example, a password of 5 lowercase characters can only be used to create 11,881,376 different passwords (26^5).

如果密码不安全(例如,某人使用5个字符长的密码),则可以相对容易地破解它。 例如,5个小写字符的密码只能用于创建11,881,376个不同的密码(26 ^ 5)。

For a hash of this password, even if the hash is cryptographically secure (uses an appropriate algorithm), it would still be very easy to compute all possible passwords and their corresponding hashes. Lookup tables work very well for these types of password hashes.

对于此密码的散列,即使该散列在密码上是安全的(使用适当的算法),也将非常容易计算所有可能的密码及其对应的哈希。 查找表对于这些类型的密码哈希非常有效。

However, as passwords increase in length, the storage (and therefore storage cost) you need for every possible password and the corresponding hash grows exponentially.

但是,随着密码长度的增加,每个可能的密码所需的存储空间(以及因此的存储成本)和相应的哈希值呈指数增长。

For example if the password you're trying to crack is 8 characters long but uses numbers (10 digits), lowercase letters (26), uppercase letters (26), and some special characters (10), the number of possible passwords jumps to 722,204,136,308,736 - which is A LOT of storage space, when you realize each is hashed with a hashing function like SHA-256.

例如,如果您尝试破解的密码长8个字符,但使用数字(10位数字),小写字母(26),大写字母(26)和某些特殊字符(10),则可能的密码数跳至722,204,136,308,736-这是很多存储空间,当您意识到每个对象都使用SHA-256之类的哈希函数进行哈希处理时。

Rainbow tables address this issue by offering reduced storage needs, but they take more time to compute the potential passwords. At the most basic level, these are essentially pre-computed lookup tables which enable you to quickly find the plaintext which matches the hash you have. If the hash and plaintext are contained in the table you have - similar to dictionary attacks - you're only looking to see if the password is contained in the table you have. If it isn't, you won't be able to crack the password. You can find these online for free or for purchase.

Rainbow表通过减少存储需求来解决此问题,但是它们花费更多的时间来计算潜在的密码。 在最基本的级别上,这些本质上是预先计算的查找表,使您可以快速查找与您的哈希匹配的纯文本。 如果哈希表和明文包含在您的表中-类似于字典攻击-您只是在查看密码是否包含在您的表中。 如果不是,您将无法破解密码。 您可以免费在线购买或购买这些产品。

Check out this article for a tutorial on creating your own rainbow tables.

请查看本文 ,以获取有关创建自己的彩虹表的教程。

我还是很感兴趣 彩虹桌如何运作? (I'm still interested. How do rainbow tables work? )

If you want to skip the detailed explanation of how these work, feel free to scroll down to the 'How to protect against these attacks' section.

如果您想跳过有关它们如何工作的详细说明,请随时向下滚动至“如何防范这些攻击”部分。

In order to save yourself from hashing and storing each possible plaintext until you find the hash you need (like a lookup table), you hash each plaintext and store the result in a table to look up later without having to regenerate them. This takes longer, but saves memory.

为了使自己免于散列和存储每个可能的纯文本,直到找到所需的哈希(如查找表)为止,您对每个纯文本进行哈希处理并将结果存储在表中,以便以后查找而不必重新生成它们。 这需要更长的时间,但可以节省内存。

To generate the table, you create 'chains' of hashes and plaintext using a hashing function and a reduction function. A reduction function just creates plaintext from a hash (it doesn't reverse engineer the hash, but rather creates different plaintext from the hash). It is also a one-way function.

要生成表,您可以使用哈希函数和归约函数创建哈希和明文的“链”。 归约函数仅从哈希创建纯文本(它不会对哈希进行反向工程,而是从哈希创建不同的纯文本)。 它也是单向功能。

Thus in order to compute the table, you use one of your hashes, h1, in your reduction function, R(), in order to create the plaintext p1.

因此,为了计算表,您可以在归约函数R()中使用哈希之一h1来创建纯文本p1。

R(h1) = p1.

R(h1)= p1。

Then you use the hash function H() with p1 to create a new hash.

然后,将哈希函数H()与p1一起使用以创建新的哈希。

H(p1) = h2.

H(p1)= h2。

Using our example from before:

使用之前的示例:

If the set of plaintext is [abcdefghijklmnopqrstuvwxyz]{5} (we're looking for a rainbow table of all passwords composed of lowercase letters of length 5) and we're using MD5 (a hashing algorithm):

如果明文集为[abcdefghijklmnopqrstuvwxyz] {5}(我们正在寻找由长度为5的小写字母组成的所有密码的彩虹表),则我们使用的是MD5(哈希算法):

A hash might be ab56b4d92b40713acc5af89985d4b786 (h1). Now, we apply the reduction function, which could be as simple taking the last 5 letters in the hash.

哈希可能是ab56b4d92b40713acc5af89985d4b786(h1)。 现在,我们应用归约函数,该函数可以很简单地获取哈希中的最后5个字母。

R(ab56b4d92b40713acc5af89985d4b786) = cafdb

R(ab56b4d92b40713acc5af89985d4b786)= cafdb

H(cafdb) = 81a516edabf924cd0f727d329e855b1f

H(cafdb)= 81a516edabf924cd0f727d329e855b1f

他们为什么叫彩虹桌? (Why are they called rainbow tables?)

Each column uses a different reduction function. So if each column were colored, it would be a very long, skinny rainbow.

每列使用不同的归约函数。 因此,如果每列都是彩色的,那将是一条很长的细彩虹。

Using different reduction functions reduces the number of chain merges (collisions) which happened frequently with hash-chains, the predecessor to rainbow tables. This essentially means that if you keep using the same reduction function, there's a chance you'll end up with two different chains which converge to the same plaintext. Using different reduction functions reduces the chance of this happening, though it isn't impossible.

使用不同的归约函数可减少哈希表(彩虹表的前身)经常发生的链合并(冲突)的次数。 从本质上讲,这意味着如果您继续使用相同的归约函数,则有可能最终得到两条不同的链,它们会收敛到相同的明文。 尽管并非不可能,但使用不同的归约函数可以减少这种情况的发生。

太好了,您如何创建一条链? (Great, how do you create a chain?)

In order to create a chain, you're using your reduction function and hashing function (both one way) to create a 'chain' of hashes and plaintext. Each of these 'chains' would continue for k steps, and when the chain ends, will store only the first plaintext and the last hash in the chain.

为了创建链,您使用归约函数和哈希函数(两种方式)来创建散列和纯文本的“链”。 这些“链”中的每一个都将连续k个步骤,并且当链结束时,将仅存储链中的第一个纯文本和最后一个哈希。

So, a sample chain looks like this:

因此,示例链如下所示:

p1 -> h1 = H(p1) -> R1(h1) = p2 -> H(p2) = h2 -> R2(h2) = p3 -> H(p3) = h3

p1-> h1 = H(p1)-> R1(h1)= p2-> H(p2)= h2-> R2(h2)= p3-> H(p3)= h3

Each reduction function is different (represented by R1, R2, R3, etc.) A sample table of chains (each row is a chain of length 5) looks like the following. Note that this is populated with fake data just to give you an example - the hashing function isn't a hash function you would find used to hash passwords.

每个归约函数都是不同的(用R1,R2,R3等表示)。链的样本表(每行是长度为5的链)如下所示。 请注意,这里填充了虚假数据,仅举一个例子-哈希函数不是您会发现用来哈希密码的哈希函数。

The reduction functions, R1 and R2 are defined as follows – R1 takes the first 3 digits of the hash, and R2 takes the last 2 letters of the hash:

约简函数R1和R2的定义如下-R1接受哈希的前3位数字,R2接受哈希的后2个字母:

p1 -> h1 = H(p1) -> R1(h1) = p2 -> H(p2) = h2 -> R2(h2) = p3 -> H(p3) = h3

p1-> h1 = H(p1)-> R1(h1)= p2-> H(p2)= h2-> R2(h2)= p3-> H(p3)= h3

2  -—>  abdu2934   -—>  293  -—>  83kdnif8  -—>  if  -—>  ike83jd3

2--> abdu2934--> 293--> 83kdnif8-->如果--> ike83jd3

15  -—>  dks2ne94  -—>   294  -—>  ld932nd9  -—>  ld  -—>  ldie938d

15-> dks2ne94-> 294-> ld932nd9-> ld-> ldie938d

20  -—>  ld93md8d  -—>  938  -—>  lxked93k  -—>  lx  -—>  93mdkg8d

20-> ld93md8d-> 938-> lxked93k-> lx-> 93mdkg8d

In a rainbow table, only the first starting point and the endpoint are saved to save on storage, like this:

在Rainbow表中,仅保存第一个起点和终点以保存在存储器中,如下所示:

starting point (plaintext) endpoint, after k steps through the chain (hash)

在k遍历链(哈希)之后的起点(纯文本)端点

p1  -—>   h1k

p1-> h1k

p2  -—>   h2k

p2-> h2k

p3  -—>          h3k

p3-> h3k

Then when you have a hash (h) where you don't know the plaintext (?), you'll compare it to the chains.

然后,当您有一个不知道明文(?)的哈希(h)时,会将其与链进行比较。

  1. First, you'll check if the hash is in the list of final hashes (h1k, h2k, etc.). If so, you can move to step 3.首先,您将检查哈希是否在最终哈希(h1k,h2k等)列表中。 如果是这样,则可以转到步骤3。
  2. If not, you can reduce the hash to different plaintext (R1) and then hash that plaintext (using the hash function and next reduction function above) and compare it to the list of final hashes (h1k, h2k, h3k, etc.). When it matches one of the final hashes, that chain will likely contain the original hash.如果不是,则可以将散列简化为不同的明文(R1),然后对该散文进行散列(使用上面的散列函数和下一个归约函数),并将其与最终散列表(h1k,h2k,h3k等)进行比较。 当它与最终哈希之一匹配时,该链可能会包含原始哈希。
  3. In order to find the original hash in the chain, take that chain's starting plaintext (so if it matches h1k, start with p1) and apply the hashing and reduction functions to move along the chain until you reach the known hash and its corresponding plaintext. This way you can move through the hashes in the chain without having them take up storage space on your machine.为了找到链中的原始哈希,请采用该链的起始明文(因此,如果匹配h1k,则从p1开始),并应用哈希和归约函数沿链移动,直到到达已知的哈希及其对应的明文为止。 这样,您就可以遍历链中的哈希,而不会占用机器上的存储空间。

While you can't be sure that the chains will contain the hash you need, the more chains you've generated (or are referencing) the more certain you can be. Unfortunately, each chain is time-intensive to generate, and increasing the number of chains increases the time you need.

虽然不能确定链中是否包含所需的哈希,但是生成(或引用)的链越多,确定性就越高。 不幸的是,每个链条的生成都非常耗时,并且增加链条数会增加您所需的时间。

您如何防御这些类型的攻击? (How do you defend against these types of attacks?)

First, a layered defense of all systems. If you can prevent compromise of your systems via other methods (so the attacker can't get a copy of your hashed passwords), the attacker won't be able to crack them.

首先,对所有系统进行分层防御。 如果您可以通过其他方法防止系统受到损害(攻击者无法获得哈希密码的副本),则攻击者将无法破解它们。

You can also use salting, which adds a random value to the password before encrypting it. That means that the precomputed value you've found (which matches the hash) won't work. The encrypted text is not based solely on the unencrypted text. Because the salt is different for each password, each needs to be cracked individually.

您也可以使用盐析,在加密之前会在密码中添加随机值。 这意味着您找到的预计算值(与哈希匹配)将不起作用。 加密的文本不仅仅基于未加密的文本。 由于每个密码的符号不同,因此每个密码都需要单独破解。

Salting is now included in most major hash types as an option. While Windows doesn't currently use salting, they can encrypt stored hashes if you use the 'SYSKEY' tool.

现在,盐分包含在大多数主要的哈希类型中,作为一种选择。 尽管Windows当前不使用盐分,但如果您使用“ SYSKEY”工具,它们可以加密存储的哈希。

You can also use 'rounds', or hashing a password multiple times. Using rounds (particularly if the number of rounds is randomly chosen for each user), makes the hacker's job harder. This is most effective when combined with salting.

您还可以使用“回合”,或多次对密码进行哈希处理。 使用回合(特别是如果为每个用户随机选择回合数),会使黑客的工作更加困难。 与盐腌结合使用时最有效。

Unfortunately, a hacker who has the hashed passwords will have also have access to the number of rounds used and the salt used (because in order to get that list they've probably compromised . The salt and number of rounds used is stored with the password hash, meaning that if the attacker has one, they also have the other. However, they won't be able to use precomputed rainbow tables available online, and will have to compute their own tables (which is extremely time consuming).

不幸的是,拥有哈希密码的黑客也可以访问已使用的轮数和使用的盐(因为为了获得他们可能已经入侵的列表。使用的盐和轮数与密码一起存储哈希,这意味着如果攻击者拥有一个,那么他们也将拥有另一个,但是,他们将无法使用在线提供的预先计算的彩虹表,而必须计算自己的表(这非常耗时)。

One other method designed to increase the difficulty of cracking the password is to use a pepper. Pepper is similar to salt, but while a salt is not secret (it's stored with the hashed password), pepper is stored separately (for example, in a configuration file) in order to prevent a hacker from accessing it. This means the pepper is secret, and its effectiveness depends on this.

旨在增加破解密码难度的另一种方法是使用胡椒。 Pepper与盐类似,但是盐不是秘密的(使用哈希密码存储),但是胡椒被单独存储(例如,在配置文件中),以防止黑客访问它。 这意味着胡椒是秘密的,其有效性取决于此。

Pepper needs to be different for each application it is used for, and should be long enough to be secure. At least 112 bits is recommended by the National Institute of Standards and Technology.

Pepper需要针对每种应用程序而有所不同,并且应足够长以确保安全。 美国国家标准技术研究院建议至少使用112位。

While using a pepper can be effective in some cases, there are some downsides. First, no current algorithm supports peppers, which means practically, this is impossible to implement at scale. That is, unless you're creating your own algorithms. Listen to Bruce Schneier. Don't do that.

虽然在某些情况下使用胡椒粉可能有效,但也有一些缺点。 首先,当前没有算法支持Pepper,这实际上意味着不可能大规模实现。 也就是说,除非您要创建自己的算法。 听Bruce Schneier的话 。 不要那样做

For a longer article on the problems with peppers, check out this thread.

有关胡椒问题的更长篇文章,请查看此主题。

Finally, use strong (at least 12 character), complex passwords, and implement strong password policies across all systems. This can include forcing users to create strong passwords, testing their strength regularly, using password managers on an enterprise level, enforcing use of 2FA, and so on.

最后,使用强密码(至少12个字符),复杂的密码,并在所有系统上实施强密码策略。 这可以包括强制用户创建强密码,定期测试其强度,在企业级别上使用密码管理器,强制使用2FA等。

Confused about what makes a strong password?

对什么使强密码感到困惑?

被黑客入侵似乎很容易。 我应该担心吗? (It seems really easy to get hacked. Should I be concerned? )

The most important thing to remember about hacking is that no one wants to do more work than they have to do. For example, calculating rainbow tables is a lot of work. If there's an easier way to get your password, that's probably what a nefarious actor will try first (like phishing!).

关于黑客,要记住的最重要的事情是,没有人愿意做比他们需要做的更多的工作。 例如,计算彩虹表是很多工作。 如果有一种更简单的方式来获取密码,那可能是一个邪恶的演员首先尝试的方式(例如网络钓鱼!)。

That means that enabling basic cyber security best practices is probably the easiest way to prevent getting hacked. In fact, Microsoft recently reported that just enabling 2FA will end up blocking 99.9% of automated attacks.

这意味着启用基本的网络安全最佳实践可能是防止被黑客入侵的最简单方法。 实际上,微软最近报告说 ,仅启用2FA最终将阻止99.9%的自动攻击。

骇客入侵! (Happy hacking!)

Additional Reading:

补充阅读:

More details on hash chains

哈希链的​​更多详细信息

Another explanation of rainbow tables

彩虹表的另一种解释

A list of rainbow tables online

在线彩虹表列表

翻译自: https://www.freecodecamp.org/news/why-a-little-salt-can-be-great-for-your-passwords/

盐噪声和胡椒噪声的区别

盐噪声和胡椒噪声的区别_为什么加一点盐对您的密码很有用(但不包括胡椒粉!)相关推荐

  1. c语言给图片添加椒盐噪声,图像添加高斯噪声、胡椒噪声、盐噪声和椒盐噪声...

    1.盐椒噪声 图像加入椒盐噪声开始,椒盐噪声其实就是使图像的一些随机的像素为黑色(0)或者白色(255): 盐噪声又称白噪声,在图像中添加一些随机的白色像素点(255):胡椒噪声是在图像中添加一些随机 ...

  2. 数字图像处理——高斯噪声和椒盐噪声区别

    高斯噪声和椒盐噪声 以Lina图像为例 两者在视觉上的区别就是,高斯噪声是和原来图像像素值相关的噪声,而椒盐噪声则是很多的黑白像素点. 高斯噪声 高斯噪声是指它的概率密度函数服从高斯分布(即正态分布) ...

  3. 高斯噪声/白噪声/高斯白噪声的区别

    这几个概念的区别和联系:(转自:研学论坛 ) 白噪声,就是说功率谱为一常数:也就是说,其协方差函数在delay=0时不为0,在delay不等于0时值为零: 换句话说,样本点互不相关.(条件:零均值.) ...

  4. 浅析“高斯白噪声”,“泊松噪声”,“椒盐噪声”的区别

    from:https://www.jianshu.com/p/67f909f3d0ce 在图像处理的过程中,一般情况下都进行图像增强,图像增强主要包括"空域增强"和"频域 ...

  5. python添加高斯噪声_使用Python-OpenCV向图片添加噪声(高斯噪声、椒盐噪声)

    在matlab中,存在执行直接得函数来添加高斯噪声和椒盐噪声.Python-OpenCV中虽然不存在直接得函数,但是很容易使用相关的函数来实现. 代码: import numpy as np impo ...

  6. 像素 噪声 matlab,椒盐噪声去噪_matlab 椒盐噪声_椒盐噪声图片

    椒盐噪声去噪 [摘要] [摘 要]本文对中值滤波算法进行了改进,提出了一种基于噪声点检测的椒盐噪声去除方法.椒盐噪声去噪对分布在噪声范围内的点进行噪声点检测,对确定为噪声点的像素点进行中值滤波,其他像 ...

  7. win10有源信号分辨率怎么调_实例分析丨信号链中放大器噪声对总噪声有多少贡献?...

    当ADC的模拟输入被驱动至额定满量程输入电压时,ADC提供最佳性能.但在许多应用中,最大可用信号与额定电压不同,可能需要调整.用于满足这一要求的器件之一是可变增益放大器(VGA).了解VGA如何影响A ...

  8. 23.代码简单实现模拟噪声(图像噪声/一、二阶矩/功率谱密度/at函数/rand函数)-- OpenCV从零开始到图像(人脸 + 物体)识别系列

    本文作者:小嗷 微信公众号:aoxiaoji 吹比QQ群:736854977 简书链接:https://www.jianshu.com/u/45da1fbce7d0 本文你会找到以下问题的答案: 图像 ...

  9. 【图像处理】——Python实现图像加噪(随机噪声、椒盐噪声、高斯噪声等)

    目录 1.随机噪声 2.椒盐噪声 3.高斯噪声 补充:numpy.clip函数 4.其他噪声 1.随机噪声 随机噪声就是通过随机函数在图像上随机地添加噪声点 随机函数random模块参考:https: ...

最新文章

  1. [Poi2010]Antisymmetry
  2. 解决使用pip安装lxml包报错问题Could not find function xmlCheckVersion in library libxml2. Is libxml2 installed?
  3. [Trie] Luogu P2992 [USACO08DEC]秘密消息Secret Message
  4. TabLayout实现顶部导航栏(1)
  5. 大学python搜题app_2021年中国大学MOOC的APP用Python玩转数据答案搜题公众号
  6. 解决 windows10和ubuntu16.04双系统下时间不对的问题
  7. net core框架介绍
  8. POJ 2236 Wireless Network
  9. python--------进程与线程
  10. 剑指offer 面试题17. 打印从1到最大的n位数
  11. Hexo NexT主题添加点击爱心效果
  12. php调用其他文件数组的值,PHP递归调用数组值并用其执行指定函数
  13. 状态空间模型与传递函数的转换关系+例题
  14. QQ等级计算方法及图标
  15. 简述人工智能的研究目标
  16. html网页背景图片 菜鸟教程,CSS3 背景
  17. play 1.2.7
  18. OSChina 周三乱弹 ——猫和铲屎官总要疯一个
  19. BZOJ 4544: 椭圆上的整点
  20. java4android网易云,Android仿网易云音乐播放界面

热门文章

  1. 线程间通信之eventfd
  2. 荣耀鸿蒙价格,荣耀40S秀肌肉,120Hz+双5000万+鸿蒙系统,售价很感人
  3. JAVA的值传递问题
  4. 如何修改浏览器的默认滚动条样式
  5. 《SQL Server 2008从入门到精通》--20180716
  6. 感想篇:4)越来越精简的机械设计
  7. 使用lt;jsp:includegt;,不想写死URL,动态生成URL的解决的方法
  8. opencv中的Mat类型
  9. 一个可以支持多版本的MediaPlayer的控件做法(支持MediaPlayer6,7,8,9,10的播放)
  10. PyCharm调试错误