<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>随笔 &#8211; 寂静花园</title>
	<atom:link href="https://www.addesp.com/archives/category/%E9%9A%8F%E7%AC%94/feed" rel="self" type="application/rss+xml" />
	<link>https://www.addesp.com</link>
	<description>寂静中交织着热情</description>
	<lastBuildDate>Mon, 13 Apr 2026 23:39:43 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1.2</generator>
	<item>
		<title>如何让 AI Agent 和隐私数据共处？</title>
		<link>https://www.addesp.com/archives/6299</link>
					<comments>https://www.addesp.com/archives/6299#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 13 Apr 2026 23:23:21 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=6299</guid>

					<description><![CDATA[一觉醒来，Hermes 突然就火了，我看各种文章里都在猛夸 Hermes 有多厉害，便拿来研究了一下。 我是一 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">一觉醒来，<a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a> 突然就火了，我看各种文章里都在猛夸 <a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a> 有多厉害，便拿来研究了一下。</p>



<p class="wp-block-paragraph">我是一个比较注重个人隐私的人，对于将隐私数据交给 AI Agent 这种天然具有随机性工具是感到不适的，但由于它确实可以解决一些问题，所以我也只能凑活着用，并开发 <a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 来尽我所能来隔离一些敏感数据。</p>



<p class="wp-block-paragraph">正因如此，我对 <a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a> 的研究主要集中在了数据安全方面，比如它有没有可能泄漏我的 Slack 聊天记录。答案其实也很显然，如果 <a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a> 从网络上读取到了一些恶意提示词，它是可以把我的 Slack 聊天记录打包发出去的。</p>



<h1 class="wp-block-heading">问题出在哪里？</h1>



<p class="wp-block-paragraph">自从 <a href="https://github.com/openclaw/openclaw" target="_blank" rel="noreferrer noopener nofollow">OpenClaw</a> 出来后，似乎每天都有各种 Claw 出现在新闻里，<a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a> 是这个趋势了又一波高潮。但让我感到失望的是，似乎所有的 Claw 都不能让我放心地把隐私数据接入到里面，每次使用的时候我只能选择性忽视隐私泄漏风险，并祈祷就算泄漏了也不要让我知道。</p>



<p class="wp-block-paragraph">目前所有的 Claw 都同时具备这三种属性：</p>



<ul class="wp-block-list">
<li>可以读取敏感数据</li>



<li>接受并执行任意来源的指令</li>



<li>能够进行网络通信</li>
</ul>



<p class="wp-block-paragraph">这三种属性结合起来解锁的功能是让我捏着鼻子用的唯一原因，但也是让我感到不适的罪魁祸首。</p>



<p class="wp-block-paragraph">从防范数据泄漏的角度来说，只要我们能够去掉任意一个属性，就可以解决泄漏问题，但其实我们最想拿掉的是“接受并执行任意来源的指令”，或者说“仅执行来自我的指令”。</p>



<p class="wp-block-paragraph">但这个第二种属性结构性的，其根源是 Transformer 从根本上只是将当前上下文的所有数据堆在进行概率运算，并生成概率性的结果。因此，无论是你的指令，还是网页上抓取的数据，都有可能被当作指令执行。</p>



<p class="wp-block-paragraph">我们当然可以去掉别的属性，比如给 Agent 断网，或者不让 Agent 接触敏感数据，但这就失去的 Agent 的意义了。</p>



<p class="wp-block-paragraph">因此，问题的根源是根植于 Transformer 的根本属性，也就不是能够完美解决的问题。</p>



<h1 class="wp-block-heading">有什么不完美的解决方案么？</h1>



<p class="wp-block-paragraph">既然无法完美解决，那么总是有缓解措施的对吧。</p>



<ul class="wp-block-list">
<li>提示词防护：其实就是在给 Agent 的提示词中强调 ”不要泄漏敏感数据“ 和 ”不要运行这个命令“ 等，可以显著提高攻击者进行提示词注入的难度。</li>



<li>基于正则匹配的防护：对 Agent 的输入和输入进行正则匹配，如果发现特定格式的数据（比如身份证号）就进行屏蔽或者终止执行。</li>



<li>容器化：将 Agent 运行在 docker 容器中甚至虚拟机中，来避免恶意代码读取敏感数据或者造成实际损失。</li>
</ul>



<p class="wp-block-paragraph">更常见的方案则是将这几类组合起来，组成纵深防御体系，每一层都可以被绕过，但是绕过的难度也随着纵深的增加而增加。</p>



<p class="wp-block-paragraph">不过其实这些方案都是显而易见的绕过方式，提示词防护我可以拐弯抹角地注入，正则匹配可以用 base64 绕过，容器化不能方式 Agent 将已经存在于容器或上下文内的敏感数据通过网络泄漏出去。</p>



<p class="wp-block-paragraph">我将这些称之为软性约束，特点是它依然有可能 Transformer 的随机性而被绕过。</p>



<h1 class="wp-block-heading">有没有硬性约束呢？</h1>



<p class="wp-block-paragraph">硬性约束的特点它依然可能被绕过，但是永远不会因为 Transformer 的随机性被绕过。</p>



<p class="wp-block-paragraph"><a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 是一种典型的硬性约束的实现，它允许 Agent 使用一个仅仅在本地生效的 OpenAI API Key 来访问真实的 OpenAI 服务，由于 Agent 能访问的数据中只有一个仅在本地生效的 API Key，那么这个 Key 就算被 Agent 泄漏也是无害的。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img fetchpriority="high" decoding="async" width="1024" height="441" src="https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot-1024x441.png" alt="" class="wp-image-6302" srcset="https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot-1024x441.png 1024w, https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot-300x129.png 300w, https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot-768x331.png 768w, https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot-1536x661.png 1536w, https://www.addesp.com/wp-content/uploads/2026/04/1776122227-screenshot.png 1961w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<p class="wp-block-paragraph">绕过 <a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 的方法是 Agent 读取 ClawShell 的配置文件并获取真实的 OpenAI API Key。不过操作系统的权限系统并不依赖 Transformer 来运行，所以只要 ClawShell 的配置文件有正确的读取权限，这种约束就是可靠的。</p>



<h1 class="wp-block-heading"><a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 可以保护 Slack 聊天记录么？</h1>



<p class="wp-block-paragraph"><a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 可以使用类似的方式来保护 Slack 的 API Key 不被泄漏，但是无法阻止聊天记录泄漏。因为聊天记录本身会被加载进入 Agent 的上下文中，这就让提示词注入有了可乘之机。</p>



<p class="wp-block-paragraph">这意味着虽然攻击者无法获取 Slack 的 API Key，但是依然可以通过恶意的提示词来让 Agent 将你的聊天记录泄漏出去。</p>



<h1 class="wp-block-heading">解法呢？</h1>



<p class="wp-block-paragraph">让我们再次回到 Transformer 的内在属性 — ”接受并执行任意来源的指令“，这其实和冯·诺伊曼架构的内存有些相似。冯·诺伊曼架构的内存并不区分指令和数据，这既解锁了强大功能比如 JVM 和 V8 的 JIT 编译器来大幅度提升解释型语言的执行性能，也是诸多远程代码执行漏洞的根源。</p>



<p class="wp-block-paragraph">人们的解法是一系列的硬性约束：</p>



<ul class="wp-block-list">
<li>给内存区域设置权限，确保数据区域的代码无法被执行。</li>



<li>操作系统限制任意修改内存区域权限的能力，确保只有特权程序可以执行风险操作。</li>



<li>JVM 和 V8 等 JIT 编译器团队长期的努力，修复了大量针对 JIT 的攻击漏洞。</li>
</ul>



<p class="wp-block-paragraph">人们对冯·诺伊曼架构的解法并不完美，但是够用，并构建了现在的互联网时代。我相信 Transformer，或者说 Agent 也需要一套不完美但是够用的解法。</p>



<p class="wp-block-paragraph">让我们回到 Slack 聊天数据泄漏的话题上来，既然隔离 API Key 的方式并不保险，那么我们可以做些什么呢？</p>



<ul class="wp-block-list">
<li>禁止 Agent 访问某些危险网站。</li>



<li>只允许 Agent 读取和我的直接聊天记录，对其余的聊天记录进行屏蔽。</li>



<li>甚至只允许 Agent 和 Slack 服务器通信。</li>
</ul>



<p class="wp-block-paragraph">现在，我用起来会感觉舒服一些了。这个方案并不完美，恶意网站名单可能会过期，限制聊天记录的读取范围会一定程度上限制 Agent 的能力，但是对我来说够用了。</p>



<h1 class="wp-block-heading"><a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 可以做这些么？</h1>



<p class="wp-block-paragraph">理论上可以，但实操困难重重。</p>



<p class="wp-block-paragraph">上面的解法其实就是细粒度的权限控制，但这对 <a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 来说其实很难。</p>



<p class="wp-block-paragraph">首先，由于几乎所有的 Claw 都有执行 Shell 命令的权限，这基本等于有执行任意代码的权限，这注定了进行网络通信拦截必须要在操作系统层面甚至外部基础设施上做工作，而不是简单地修改 Claw 们的源码，或者扫描一下 Skills 的内容。</p>



<p class="wp-block-paragraph">其次，<a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 的设计宗旨是 local-only，这让其部署十分简单，但也决定了其很难进行复杂的基于外部基础设施的防护。</p>



<p class="wp-block-paragraph">最后，Agent 的最大的价值之一就是它可以自由灵活地访问各类数据，限制这个就是限制 Agent 的价值，这里面需要小心的权衡。不过这些细粒度权限控制是很大的工程投入，<a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 作为一个开源项目并没有足够的工程资源来进行如此多的权限适配。</p>



<h1 class="wp-block-heading">Agent 时代不需要新技术</h1>



<p class="wp-block-paragraph">无论是细粒度的权限控制，还是网络防火墙技术，它们都不是新东西，而是经历实战检验，有大量经验可以参考的技术。但是想要充分理由这些现有的技术，我们就必须对 Agent 运行的环境有着深度的控制，这样我们才可以在外围搭建一套可靠的基础设施。</p>



<p class="wp-block-paragraph">所以这里的关键是要掌握 Agent 的运行环境，如果 Agent 只是运行在个人电脑上，那么 <a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 也许已经接近极限了，但是如果运行在一个专门为 Agent 定制的运行环境中，我们就可以通过外部的基础设施来在保证一定程度灵活性的情况下，来为数据泄漏建立的一系列硬性约束边界。</p>



<p class="wp-block-paragraph">其实，人们只是需要将现有的技术组合起来而已，仅此而已。</p>



<p class="wp-block-paragraph">最终，我还是想宣传一下我的 <a href="https://github.com/clawshell/clawshell" target="_blank" rel="noreferrer noopener nofollow">ClawShell</a> 项目，它虽然不能防止 Slack 聊天记录泄漏，但是对于 AI API Key 的保护还是足够的，并且也进行了敏感邮件过滤。最近我也让它支持了 <a href="https://github.com/NousResearch/hermes-agent" target="_blank" rel="noreferrer noopener nofollow">Hermes</a>，欢迎大家用用。</p>


<p>[ghcard path=&#8221;clawshell/clawshell&#8221;][/ghcard]</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/6299/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>证书透明机制 — 防范证书签发机构作恶</title>
		<link>https://www.addesp.com/archives/5638</link>
					<comments>https://www.addesp.com/archives/5638#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 01 May 2023 06:36:26 +0000</pubDate>
				<category><![CDATA[科普]]></category>
		<category><![CDATA[随笔]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=5638</guid>

					<description><![CDATA[原来就是你小子乱签证书？]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph"><a href="https://baike.baidu.com/item/公钥基础设施/10881894" target="_blank" rel="noopener nofollow" title="公钥基础设施（PKI）">公钥基础设施（PKI）</a>是当今互联网世界的安全基石，它可以帮助我们确认通信双方的身份，防止数据被窃听或篡改。</p>



<p class="wp-block-paragraph">简单回忆一下经典的互联网身份验证过程，浏览器向服务器请求网站的 HTTPS 证书，验证证书所代表的网站是否就是被访问的网站，如果不是则停止通信，以避免<a href="https://baike.baidu.com/item/中间人攻击/1739730" target="_blank" rel="noopener nofollow" title="">中间人攻击</a>。</p>



<p class="wp-block-paragraph">浏览器不仅要确定证书所代表的网站是否正确，也要确定证书本身是否是伪造的。所以浏览器会存储一些受信任的证书签发机构（CA），只要是他们签发的证书都被认为是可信的。</p>



<p class="wp-block-paragraph">不过 CA 并不总是可信的：</p>



<ul class="wp-block-list">
<li>CA 可能会出于某些原因签发用于攻击证书。</li>



<li>CA 被黑客控制，签发了用于攻击的证书。</li>



<li>CA 因为操作失误/系统故障等原因签发了错误的证书。</li>
</ul>



<p class="wp-block-paragraph">比如说如果我拿到了一个网站的证书，但这个网站并不属于我，我就可以窃听，篡改这个网站的通信。</p>



<p class="wp-block-paragraph">证书透明机制（<a href="https://certificate.transparency.dev/">Certificate Transparency</a>）即 <strong>CT</strong> 机制可以缓解这个问题。</p>



<h2 class="wp-block-heading">何为透明？</h2>



<p class="wp-block-paragraph">从上文来看，如果某个 CA 签发了不合规的证书，只要我们及时吊销它，就不会有被攻击的风险。所以问题的关键就是如何及时发现不合规的证书。</p>



<p class="wp-block-paragraph">证书透明机制维护了一个<strong>向所有人公开的日志数据库</strong>，所有加入到 CT 机制的 CA 签发的每一个证书都会被记录到这个数据库中。所以所谓透明就是把 CA 的签发所有证书都公开，这样一旦 CA 乱搞很快就会被发现。</p>



<h2 class="wp-block-heading">CT 机制下 CA 如何签发一张证书？</h2>



<ol class="wp-block-list">
<li>网站所有者向 CA 请求签发一张属于自己的证书。</li>



<li>CA 机构验证申请者的身份，并首先生成一张 “<strong>预证书”</strong>（precertificate），并将这张证书提交给 CT 数据库。下文将解释什么是预证书，以及为什么需要它。</li>



<li>CT 数据库接收到预证书后，立即将 “<strong>证书签名时间戳</strong>”（signed certificate timestamp）返回给 CA，并承诺预证书会在某个时间点之后被写入到 CT 数据库。这个时间戳会被 CT 签名，确保不给伪造。</li>



<li>CA 将这个时间戳写入证书中，签名，成为最终的证书。</li>



<li>将最终的证书返回给网站所有者。</li>
</ol>



<p class="wp-block-paragraph">我们首先来解释一下 “<strong>预证书</strong>”。</p>



<p class="wp-block-paragraph">CT 数据库需要知道证书的全部内容才能将其写入到数据库，CA 机构需要 “<strong>证书签名时间戳</strong>” 才能生成最终的证书。但是生成最终证书需要 CT 提供 “<strong>证书签名时间戳</strong>”，CT 又需要知道证书的全部内容才返回这个时间戳，</p>



<p class="wp-block-paragraph">“预证书” 可以解决这个问题，”预证书” 包含了最终证书的所有信息（除了 “证书签名时间戳”），同，时它被标记为不可用，不会被浏览器识别为有效的证书。</p>



<p class="wp-block-paragraph">由于 “预证书” 包含了最终证书几乎所有的信息，所以存储这张证书依然可以建立与最终证书对应关系。所以 CT 可以放心地将 “证书签名时间戳“ 返回给 CA，并由 CA 签发最终的证书。</p>



<p class="wp-block-paragraph">回到证书签发流程，我们会发现 CT 并不关心一张证书的签发是否合规，它只是如实地记录每一个被签发证书，以及签发这个证书 CA。</p>



<h2 class="wp-block-heading">CT 机制如何保证安全？</h2>



<p class="wp-block-paragraph">为了及时发现不合规的证书，就需要有人监视这个数据库的变动。</p>



<p class="wp-block-paragraph">利益相关方比如 Google，会监控数据库的变动，如果它发现有自己以外的 CA 签发了 Google 的证书，那么 Google 可以立即将这个发现公之于众，然后在自己的浏览器（Chrome）中吊销这个证书，鉴于 Chrome 的市场份额，这可以很大程度地保持安全。</p>



<p class="wp-block-paragraph">同时许多浏览器（Chrome，Safari 等）会强制证书加入 CT 机制，这些浏览器会验证 “<strong>证书签名时间戳</strong>” 是否来自受信任的 CT 数据库，如果不是则中断通信。</p>



<p class="wp-block-paragraph">你可能会疑惑为什么浏览器不直接去 CT 数据库查询对应的证书，一部分原因是这种做法等同于向 CT 数据库发送自己的浏览器历史记录。</p>



<h2 class="wp-block-heading">为什么 CA 和浏览器要参与到 CT 机制？</h2>



<p class="wp-block-paragraph">CT 机制是增强安全措施，如果 CA 拒绝加入会被公众怀疑你的证书是否合规，浏览器拒绝加入则会让用户怀疑使用你的浏览器是否安全。</p>



<p class="wp-block-paragraph">Google 是这项机制的重要推动者，如果 CA 不加入 CT，那么你签发的证书很可能不被市场份额极高的 Google Chrome 所信任。</p>



<h2 class="wp-block-heading">谁来确保 CT 不会作恶？</h2>



<p class="wp-block-paragraph">整个证书签发和写入数据库的流程都由密码学提供保证，任何人都不可能篡改已经写入数据库的信息。所以 CT 作恶的唯一方式就是照常签发 “证书签名时间戳”，但是不将证书写入数据库以避免来自外部的审查。</p>



<p class="wp-block-paragraph">所以要成为浏览器信任的 CT，需要满足一些条件，随便举几个例子。</p>



<ul class="wp-block-list">
<li>CT 能够在指定时间内将证书信息写入数据库。</li>



<li>CT 服务的停机时间不能超出某个时间范围。</li>



<li>······</li>
</ul>



<p class="wp-block-paragraph">每个浏览器的审核标准也不同，必须满足标准才会被浏览器信任。比如你可以<a href="https://github.com/GoogleChrome/CertificateTransparency" target="_blank" rel="noopener" title="查看 Google Chrome 的审核标准">查看 Google Chrome 的审核标准</a>。</p>



<h2 class="wp-block-heading">CT 和 CA 依然有机会作恶</h2>



<p class="wp-block-paragraph">是的，CT 机制并不能解决这个问题，但是它让这个问题<strong>变好了</strong>。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list">
<li><a href="https://certificate.transparency.dev" target="_blank" rel="noopener" title="">https://certificate.transparency.dev</a></li>



<li><a href="https://www.thesslstore.com/blog/ssl-precertificates/" target="_blank" rel="noopener" title="What are SSL Precertificates?">What are SSL Precertificates?</a></li>
</ul>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/5638/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>为什么需要 “API 网关”？</title>
		<link>https://www.addesp.com/archives/5603</link>
					<comments>https://www.addesp.com/archives/5603#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 27 Jan 2022 15:14:33 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=5603</guid>

					<description><![CDATA[简单聊聊 API 网关。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="问题背景">问题背景</h2>



<p class="wp-block-paragraph">假如我们开发一个短视频服务，我们可以将服务简单地划分为下列几个部分。</p>



<ul class="wp-block-list"><li>视频服务：视频的上传和下载。</li><li>社交服务：关注、私信和评论等传统的社交服务。</li><li>商品服务：周边商品和增值服务，比如会员服务。</li><li>评价服务：点赞视频、收藏视频、点赞评论等。</li></ul>



<p class="wp-block-paragraph">我们不可能只让用户再网页上刷视频，要支持很多个平台，简单地可以分为下面两个。</p>



<ul class="wp-block-list"><li>WEB 服务（网页）</li><li>APP 服务（手机上的软件）</li></ul>



<p class="wp-block-paragraph">如果我们要部署这四类服务，肯定不能将其部署在同一个机器或者机房里，原因如下。</p>



<ul class="wp-block-list"><li>单个机器的能提供的服务量有限，大量的用户使用时无法及时处理。</li><li>一旦机房出现意外，所有的服务都会失效，容错性太低。</li></ul>



<p class="wp-block-paragraph">所以我们就把这四种服务部署在物理位置距离适当的四个机房中。真实情况要比这个复杂得多，比如我可能会在许多个机房中部署同一个服务，防止某个机房出现意外时对应的服务失效，但这对本文来说不重要。</p>



<p class="wp-block-paragraph">现在我们部署好了全部的服务，可以让 APP 和网页调用我们服务来让用户刷视频了。</p>



<figure class="wp-block-image size-full"><img decoding="async" width="601" height="411" src="https://www.addesp.com/wp-content/uploads/2022/01/0.webp" alt="" class="wp-image-5611"/></figure>



<p class="wp-block-paragraph">看起来是不是没什么问题？以 WEB 端为例，存在下列问题。</p>



<ul class="wp-block-list"><li>需要调用四类完全不同的服务，而且通常它们的调用方式无法保证一致，加大了开发难度。WEB 开发者需要写四套调用代码，而服务的开发者需要为两个平台提供两套接口。前后端代码的耦合性不可避免地提高，提升维护难度。</li><li>用户的一次操作可能多调用多个服务，意味着发起多次网络请求，如果这些请求还必须按照确定的顺序执行，响应时间会很长，降低用户体验。</li></ul>



<p class="wp-block-paragraph">实践中，服务的划分可能更多，对应的调用方式会更加复杂，必须做出改变。</p>



<h2 class="wp-block-heading" id="如何解决">如何解决？</h2>



<p class="wp-block-paragraph">上面的例子中，我们发现出现问题的原因是我们总是不可避免地需要同时用到多种服务，那么有什么解决办法呢？</p>



<p class="wp-block-paragraph">很容易想到如果把这四类服务合并为一类服务是不是就可以了？其实这回引入更多的问题。</p>



<ul class="wp-block-list"><li>不同的服务对硬件的需求不同，例如视频服务不需要很高的硬件配置，但带宽已经要足够；社交和评价服务通常涉及到复杂的数据查询，需要强大的计算和 I/O 性能；商品服务需要极高可靠性，其服务器的部署方式可能都比较特殊，同时对性能也有较高的要求。</li><li>软件层面就是代码的耦合问题，简单来说就是大幅度提高开发和维护成本。</li></ul>



<p class="wp-block-paragraph">既然合并服务不可行，那么把服务的调用合并起来怎么样呢？听起来不错，这就是 API 网关的作用了。</p>



<h2 class="wp-block-heading" id="api-网关">API 网关</h2>



<p class="wp-block-paragraph">如果你有计算机网络的理论基础，你肯定知道 “网关” 这个概念，在 OSI 模型的网络层中，网关通常指路由器，因为它可以实现局域网间的数据互通。API 网关的作用也类似，即接受客户端的请求，转发客户端的请求，转发服务端的响应给客户端。</p>



<p class="wp-block-paragraph">那么这和客户端直接请求有什么区别呢？一大区别就是 API 网关可以将不同服务的接口聚合成一个接口，客户端只要请求一次，API 网关就可以根据请求向若干个服务发起请求，等到请求完成后，一起返回给客户端。</p>



<figure class="wp-block-image size-full"><img decoding="async" width="601" height="411" src="https://www.addesp.com/wp-content/uploads/2022/01/1.webp" alt="" class="wp-image-5612"/></figure>



<p class="wp-block-paragraph">如图所示，这样做有下列优点。</p>



<ul class="wp-block-list"><li>API 网关屏蔽了不同服务之间调用差异，统一了调用方式，降低开发和维护成本。</li><li>无需发起多次网络请求，提升了响应时间，保证用户体验。</li><li>API 网关层面可以进行更多的操作，比如安全检查，前后端只需要专注于业务逻辑即可。</li></ul>



<p class="wp-block-paragraph">那么有什么缺点呢？缺点就是 API 网关一定要十分可靠，否则一旦它出现故障，所有的服务就算正常运行，客户端也无法调用它们。</p>



<p class="wp-block-paragraph">可靠既包含性能保证，也要有容灾保证。为了保证这两点，我们通常不会只部署一个节点的网关，而是部署很多个节点，这样既能保证性能，又有容灾能力。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/5603/feed</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		
			</item>
		<item>
		<title>SIMD 指令集友好的子串匹配算法</title>
		<link>https://www.addesp.com/archives/5486</link>
					<comments>https://www.addesp.com/archives/5486#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 21 Oct 2021 16:52:56 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[算法]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=5486</guid>

					<description><![CDATA[标题简单明了，这是一篇看了之后也没什么用的文章。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">标题简单明了，这是一篇看了之后也没什么用的文章。</p>



<p class="wp-block-paragraph">这是我在看 <a href="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/" target="_blank" rel="noreferrer noopener" title="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/">Building even faster interpreters in Rust</a> 时发现的，至于我为什么要看，可能是因为我也开发了一个<a href="https://github.com/ADD-SP/ngx_waf" target="_blank" rel="noreferrer noopener" title="https://github.com/ADD-SP/ngx_waf">防火墙模块</a>。</p>



<h2 class="wp-block-heading">什么是 SIMD？</h2>



<p class="wp-block-paragraph">SIMD（<strong>S</strong>ingle <strong>I</strong>nstruction <strong>M</strong>ultiple <strong>D</strong>ata）即单指令流多数据流，翻译成人话就是用一条指令操作多组数据。举个例子就是通常一条指令只能完成一对寄存器间的一次加法，现在我们可以用一条指令完成多对寄存器间的多次加法。</p>



<h2 class="wp-block-heading">常规子串匹配算法的缺点</h2>



<p class="wp-block-paragraph">常规的子串匹配算法基本是下列流程。</p>



<pre class="wp-block-code language-pseudocode"><code>k = needle.length
for i = 0 to haystack.length - k - 1 {
    if haystack&#91;i] == needle&#91;0] {
        for j = 1 to k - 1 {
            if haystack&#91;i + j] != needle&#91;j] {
                break
            }
        }
        if j == k {
            return true
        }
    }
}
return false</code></pre>



<p class="wp-block-paragraph">原理很简单，就是一个一个字符地匹配，但是有下列缺点。</p>



<ul class="wp-block-list"><li>对于现代处理器来说，对 8 bit、16 bit、32 bit 甚至 64 bit 进行一次运算的耗时是一样的。如果还这样逐字节比较就浪费了硬件资源。</li><li>逐字节比较也要逐次访问内存，浪费 CPU 周期。</li><li>错误的分支预测则会浪费更多的 CPU 周期。</li><li>这类代码难以乱序执行。</li></ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>寄存器：可以认为是变量。</p><cite><a href="https://baike.baidu.com/item/%E6%B2%83%E5%85%B9%E5%9F%BA%E7%A1%95%E5%BE%B7/50646842" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/%E6%B2%83%E5%85%B9%E5%9F%BA%E7%A1%95%E5%BE%B7/50646842">沃·兹基硕德</a></cite></blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>* There is no difference in comparing one, two, four or 8 bytes on a 64-bit CPU. When a processor supports SIMD instructions, then comparing vectors (it means 16, 32 or even 64 bytes) is as cheap as comparing a single byte.</p><p>* Thus comparing short sequences of chars can be faster than fancy algorithms which avoids such comparison.</p><p>* Looking up in a table costs one memory fetch, so at least a L1 cache round (~3 cycles). Reading char-by-char also cost as much cycles.</p><p>* Mispredicted jumps cost several cycles of penalty (~10-20 cycles).</p><p>* There is a short chain of dependencies: read char, compare it, conditionally jump, which make hard to utilize out-of-order execution capabilities present in a CPU.</p><cite><a href="http://0x80.pl/articles/simd-strfind.html#performance-results-update" target="_blank" rel="noreferrer noopener" title="http://0x80.pl/articles/simd-strfind.html#performance-results-update">SIMD-friendly algorithms for substring searching</a></cite></blockquote>



<h2 class="wp-block-heading">优化算法</h2>



<p class="wp-block-paragraph">那么如何使用 SMID 来优化算法性能呢？</p>



<p class="wp-block-paragraph">假设我们有一些 8-byte 的寄存器，我们要在字符串 &#8220;a_cat_tries&#8221; 中搜索子串 &#8220;cat&#8221;。</p>



<p class="wp-block-paragraph">首先我们将 “cat” 的第一个字节和最后一个字节填充到两个寄存器中，并尽可能地重复知道寄存器被填满。</p>



<pre class="wp-block-code language-ascii-art"><code>F    = &#91; c | c | c | c | c | c | c | c ]
L    = &#91; t | t | t | t | t | t | t | t ]</code></pre>



<p class="wp-block-paragraph">然后我们将字符串  &#8220;a_cat_tries&#8221;  加载到另外两个寄存器中，其中一个寄存器从第二个字符开始加载。</p>



<pre class="wp-block-code language-ascii-art"><code>A    = &#91; a | _ | c | a | t | _ | t | r ]
B    = &#91; c | a | t | _ | t | r | i | e ]</code></pre>



<p class="wp-block-paragraph">然后比较两组寄存器的内容，对应位置的内容相同为 1，反之则为 0。</p>



<pre class="wp-block-code language-ascii-art"><code>AF   = (A == F)
     = &#91; 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 ]

BL   = (B == L)
     = &#91; 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 ]</code></pre>



<p class="wp-block-paragraph">最后我们将两个寄存器的内容合并，即 “位与” 运算。</p>



<pre class="wp-block-code language-ascii-art"><code>mask = &#91; 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 ]</code></pre>



<p class="wp-block-paragraph">此时我们发现只有第 2 位（从零开始）是 1，这说明只有从此处开始搜索才有可能搜索到子串。着就减少了我们的搜索次数，下面是一种实现。</p>



<pre class="wp-block-code language-cpp"><code>size_t avx2_strstr_anysize(const char* s, size_t n, const char* needle, size_t k) {

    // 向寄存器中填充 needle 的第一个字节
    const __m256i first = _mm256_set1_epi8(needle&#91;0]);
    // 向寄存器中填充 needle 的最后一个字节
    const __m256i last  = _mm256_set1_epi8(needle&#91;k - 1]); 

    for (size_t i = 0; i &lt; n; i += 32) {

        // 向寄存器中填充 s 的部分内容
        const __m256i block_first = _mm256_loadu_si256(reinterpret_cast&lt;const __m256i*&gt;(s + i));

        // 向寄存器中填充 s 的部分内容，相对于上一行，本次填充的内容有所偏移
        const __m256i block_last  = _mm256_loadu_si256(reinterpret_cast&lt;const __m256i*&gt;(s + i + k - 1));
    

        // 比较两组寄存器
        const __m256i eq_first = _mm256_cmpeq_epi8(first, block_first);
        const __m256i eq_last  = _mm256_cmpeq_epi8(last, block_last);

        // 合并两个寄存器的比较结果
        uint32_t mask = _mm256_movemask_epi8(_mm256_and_si256(eq_first, eq_last));

        while (mask != 0) {

            // 找到第一个值为 1 的 bit 的下标
            const auto bitpos = bits::get_first_bit_set(mask);

            if (memcmp(s + i + bitpos + 1, needle + 1, k - 2) == 0) {
                return i + bitpos;
            }

            mask = bits::clear_leftmost_set(mask);
        }
    }

    return std::string::npos;
}</code></pre>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/" target="_blank" rel="noreferrer noopener" title="https://blog.cloudflare.com/building-even-faster-interpreters-in-rust/">Building even faster interpreters in Rust</a></li><li><a href="http://0x80.pl/articles/simd-strfind.html#algorithm" target="_blank" rel="noreferrer noopener" title="http://0x80.pl/articles/simd-strfind.html#algorithm">SIMD-friendly algorithms for substring searching</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/5486/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>谈谈偶像和追星</title>
		<link>https://www.addesp.com/archives/5089</link>
					<comments>https://www.addesp.com/archives/5089#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 26 Aug 2021 02:25:17 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=5089</guid>

					<description><![CDATA[这段时间关于偶像和追星的讨论多了起来，我也来说说我的想法吧，顺便在结尾讲个瞎编的小故事。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">这段时间关于偶像和追星的讨论多了起来，我也来说说我的想法吧，顺便在结尾讲个瞎编的小故事。</p>



<h2 class="wp-block-heading">偶像艺人要承担必要的社会责任</h2>



<p class="wp-block-paragraph">进入互联网时代之前，如果你只有好看的外表，你想要称为具有广泛影响的偶像艺人还需要过应的专业能力。当今，如果你拥有好看的外表，你可能不需要十分强大的专业能力也能称为一样红的偶像艺人，这就是互联网的作用。</p>



<p class="wp-block-paragraph">诚然，互联网有利于发挥一个人的优势。我虽然不支持一个没有过硬专业能力的人称为十分红火的偶像艺人，但我也不反对。因为帅哥美女大家都喜欢，这很正常。</p>



<p class="wp-block-paragraph">不过当你已经成为了一个偶像艺人时，你的行为就必须像一个合格的艺人，你必须要承担你的身份所应该承担的社会责任。一旦你只想享受身份带来的好处而逃避其带来的社会责任时，在笔者的眼里你已经不配吃这碗饭了。</p>



<p class="wp-block-paragraph">当红的偶像艺人很可能是许多未成年人追随的目标，他们可能会将偶像的行为视为信条，加以模仿。这就要求艺人们要规范自己的行为，不能给社会带来负面的影响。曾经有人问为什么不让曾经吸毒的艺人付出，因为这样的艺人一旦复出不仅变相降低了艺人违法犯罪的成本，更可能会未成年人带来负面影响（你看 XX 吸毒了不照样很火么）。</p>



<p class="wp-block-paragraph">少数偶像艺人可能不至于违法犯罪，但却严重违反公序良俗。比如同时拥有多个性伴侣，凭借身份对和多名粉丝发生性关系等。一件事情是否违反公序良俗好像没有一个明确的标准，不过有一个可供参考的判断方式，当你做一件事情的时候，你是否能堂堂正正地和自己的配偶、孩子和朋友说你干了这件事情。虽然这个方法并不总是正确的，但大家心里都有杆秤。</p>



<p class="wp-block-paragraph">当一个偶像艺人严重违反了公序良俗但不至于违法犯罪，牢饭吃不了，但艺人饭也不配吃了。</p>



<h2 class="wp-block-heading">偶像也要承担必要的社会责任</h2>



<p class="wp-block-paragraph">偶像似乎经常与艺人同时出现，比如歌手或者演员，但偶像也不一定是艺人，甚至不是一个人，比如是一个创作团队。有的可能因为不凡的成就成为偶像； 有的可能因为渊博的学识成为偶像；有的可能因为科普创作成为了偶像；有的可能迎合的某些潮流成为了偶像。这类事情在互联网上已经已经发生过很多次了。</p>



<p class="wp-block-paragraph">这种现象十分正常，但同样的，无论是偶像还是偶像艺人都需要规范自己的行为，承担应有的社会责任。比如知识生产类的偶像要注意实事求是。</p>



<h2 class="wp-block-heading">额外的想法</h2>



<p class="wp-block-paragraph">如果你将一个人视为偶像，你需要做的是学习偶像的优点，而不是去做一些偶像不会去做的事情。</p>



<h2 class="wp-block-heading">写在最后</h2>



<p class="wp-block-paragraph">旁白：曾经有一个勇者帮助村庄消灭的为祸多年的魔王，村民都很感激勇者。就在勇者要前往下一个村庄的时候，一个小男孩叫住了勇者。</p>



<p class="wp-block-paragraph">小男孩：我怎么才能像你一样厉害？</p>



<p class="wp-block-paragraph">勇者：很简单，每一种怪物都会被某种道具或武器所克制，这世间有着数不完的材料，总有一些材料可以制作出克制某种怪物的道具或武器。只要你愿意去调查，愿意测试每一种材料，你也可以成为勇者。</p>



<p class="wp-block-paragraph">旁白：勇者前往了下一个村庄。小男孩记住的勇者的话，并将勇者的话写到了自己的床边，常用的工具上和衣服上，村民们都觉得他会成为新的勇者为包围大家的安全。</p>



<p class="wp-block-paragraph">旁白：很久很久以后，那位小男孩已经长大成人，他熟练地挥舞着武器，消灭了一个又一个的怪物。有一天，他接到了一个委托去消灭一个新的怪物，于是勇者上路了。</p>



<p class="wp-block-paragraph">旁白：三天后，村民们发现这位勇者已经牺牲了，村民伤心的同时也在疑惑，如此强大的勇者是如何失败的。</p>



<p class="wp-block-paragraph">旁白：一段时间后，最初的勇者听到了消息赶来杀死怪物报了仇，村民们询问那位勇者是如何牺牲的。</p>



<p class="wp-block-paragraph">勇者：这个怪物有两条命，如果他像往常一样先去拜访这一带的村民，了解情况，制作克制其的武器可能就能讨伐成功了。</p>



<p class="wp-block-paragraph">旁白：最初的勇者捡起了阵亡勇者的武器，上面还刻着自己那天对他说过的话，叹了一口气便前往了下一个村庄。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/5089/feed</wfw:commentRss>
			<slash:comments>6</slash:comments>
		
		
			</item>
		<item>
		<title>应对明星重大舆情事件的四阶段战略</title>
		<link>https://www.addesp.com/archives/4908</link>
					<comments>https://www.addesp.com/archives/4908#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Wed, 21 Jul 2021 10:45:58 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=4908</guid>

					<description><![CDATA[通常这类事件的核心是双方有没有违反公序良俗和法律，而不是谁渣谁，谁欺骗谁等。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">开篇致敬经典</h2>



<figure class="wp-block-pullquote"><blockquote><p>《Yes, Prime Minister》</p><cite>The four-stage strategy<br>四阶段战略<br><br><strong>In stage one, we say nothing is going to happen.</strong><br>第一阶段，我们宣称什么事都没有发生。<br><br><strong>Stage two, we say something may be going to happen, but we should do nothing about it.</strong><br>第二阶段，我们说也许有事发生但我们不该采取行动。<br><br><strong>Stage three, we say that maybe we should do something about it, but there&#8217;s nothing we can do.</strong><br>第三阶段，说也许我们应该采取行动，但是我们什么都做不了。<br><br><strong>Stage four, we say there was something we could have done, but it&#8217;s too late now.</strong><br>第四阶段，也许当初我们能做点什么，但现在已经太迟了。</cite></blockquote></figure>



<h2 class="wp-block-heading">战略总结</h2>



<p class="wp-block-paragraph"><strong>这里的舆情特指和下半身有关的舆情。</strong></p>



<p class="wp-block-paragraph">这类舆情事件通常是一方主动对某个明星进行攻击，舆情发展中经常会出现攻守互易、矛盾激化和舆论反转等现象，双方各执一词，互相找对方的言语和相关材料的漏洞。</p>



<p class="wp-block-paragraph">不过一般来说事件的核心不是 xxx是不是渣男、xxx 被欺骗、xxx 是 o 婊、xxx 又当又立这些问题。核心就是双方到底犯法没有，犯法了就依法处理。没犯法但违反道德的，如果是明星则不配吃艺人饭，如果是另一方则接收舆论的谴责。</p>



<h2 class="wp-block-heading">四阶段战略</h2>



<h3 class="wp-block-heading">第一阶段</h3>



<p class="wp-block-paragraph">当舆情中对立的双方停留在打嘴炮的阶段我们应该视为什么都没发生，毕竟网络水太深，把握不住。</p>



<h3 class="wp-block-heading">第二阶段</h3>



<p class="wp-block-paragraph">当舆情中对立的双方放出了一些未经权威机构证明的材料（如截图和录音），说明舆情有极大概率进一步升级，此时我们应该保持理性的心态。如果难以保持理性或者预设了立场，不要参与到舆情中。</p>



<h3 class="wp-block-heading">第三阶段</h3>



<p class="wp-block-paragraph">当舆情的进展表示可能有一方或者双方存在违法犯罪的可能通常已经引起了相关方面的注意，这时可以参与到舆情中成为理性的声音，但更要等待权威机构的调查结果。</p>



<h3 class="wp-block-heading">第四阶段</h3>



<p class="wp-block-paragraph">当权威的调查结果发布后，我们应该反思在此次事件中自己有没有做错什么。如果调查结果证明了自己预设的立场则更要小心，检查自己预设的立场是否有合理的支撑。</p>



<ul class="wp-block-list"><li>如果涉事明星完全无辜则万事大吉。</li><li>如果涉事明星违反公序良俗但并未违法犯罪则明星不会受到执法机关的处罚，但也不配吃艺人这碗饭。</li><li>如果涉事明星违法甚至犯罪那么依法处理，艺人这碗饭肯定是吃不成了。</li><li>如果攻击明星的一方完全正确则万事大吉。</li><li>如果攻击明星的一方违反公序良俗但并未违法犯罪则接收舆论的谴责。</li><li>如果攻击明星的一方违法犯罪则依法处理。</li><li>如果攻击明星的一方操纵舆论，将道德问题包装为法律事件则是全员恶人。</li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/4908/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Seafile 服务端加密缺陷分析</title>
		<link>https://www.addesp.com/archives/4654</link>
					<comments>https://www.addesp.com/archives/4654#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Tue, 08 Jun 2021 16:25:56 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[Seafile]]></category>
		<category><![CDATA[已知明文攻击]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=4654</guid>

					<description><![CDATA[Seafile 的服务端加密功能存在缺陷，攻击者可以发起「已知明文攻击」。我翻了翻中文的搜索结果没有发现类似的文章，于是写下这篇文章提醒一下正在使用或者打算使用 Seafile 的用户。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph"><a href="https://www.seafile.com/en/home/" target="_blank" rel="noreferrer noopener nofollow" title="https://www.seafile.com/en/home/">Seafile</a> 是一个十分受欢迎的企业网盘，提供社区版供用户免费使用，也提供专业版供有更高需求的用户/企业使用。</p>



<p class="wp-block-paragraph">当你使用 Seafile 创建了一个加密存储库的时候这个库内的文件在服务端也会被加密，防止服务器管理员获取文件内容。</p>



<p class="wp-block-paragraph">不过这种加密存在缺陷，容易受到攻击。我翻了翻中文的搜索结果没有发现类似的文章，于是写下这篇文章提醒一下正在使用或者打算使用 Seafile 的用户。</p>



<p class="wp-block-paragraph">本文章完成时 Seafile 的最新版本为 8.0.5。</p>



<h2 class="wp-block-heading">攻击手段及后果</h2>



<p class="wp-block-paragraph">攻击者可以通过猜测某个加密库内的某个文件的内容来获取该库内的其它文件的部分或全部明文。因为目前 Seafile 并不加密一些元数据比如目录名和文件名，所以根据文件名进行猜测是有可能的。比如 python 源代码文件的开头大概率是 <code>import</code> 关键字。</p>



<h2 class="wp-block-heading">缺陷分析</h2>



<p class="wp-block-paragraph">我们<a href="https://manual.seafile.com/security/security_features/" target="_blank" rel="noreferrer noopener nofollow" title="https://manual.seafile.com/security/security_features/">打开英文版的官方文档</a>，看看 Seafile 的加密流程。</p>



<ol class="wp-block-list"><li>随机生成一个 32 Byte 的数字作为「文件密钥」。</li><li>通过用户为加密库指定的密码生成一个「派生密钥」和一个「初始化向量（IV）」，然后使用 AES-CBC 算法通过「派生密钥」和「初始化向量（IV）」加密「文件密钥」，得到被「被加密的文件密钥」。「被加密的文件密钥」会存储在服务端，解密时可以通过相同的方式计算出「派生密钥」和「初始化向量（IV）」用来解密「被加密的文件密钥」。</li><li>该加密库内的所有文件可以被「文件密钥」加密。</li></ol>



<p class="wp-block-paragraph">从上面过程中我们可以看出「派生密钥」和「初始化向量（IV）」从加密库创建时就被固定一下，并且不会变化，这实际上并不是正确的做法。因为按照 AES-CBC 的设计，不同的数据应该使用不同的且不可预测的「初始化向量（IV）」，否则就容易受到「已知明文攻击」。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>已知明文攻击：攻击者利用已知明文和对应的密文进行攻击。</p></blockquote>



<h2 class="wp-block-heading">为什么「初始化向量（IV）」不能重复使用？</h2>



<p class="wp-block-paragraph">引入「初始化向量（IV）」是为了解决同一段明文总会被加密成同一段密文这一缺陷，因为这种加密方式难以抵御统计分析，攻击者可以通过密文中某一片段的出现的频率、位置和时间等猜测对应的明文。比如某段密文总是出现双方首次通信，则这段密文对应的明文很可能是「你好」之类的问候语。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="902" height="363" src="https://www.addesp.com/wp-content/uploads/2021/06/DD92B63D7B6CE580F39DB.webp" alt="" class="wp-image-4676"/><figcaption>AES-CBC 加密过程</figcaption></figure>



<p class="wp-block-paragraph">引入「初始化向量（IV）」后就会让相同的明文产生不同的密文，但是如果「初始化向量（IV）」不变，同一段明文还是会产生相同的密文，所以算法设计时就要求「初始化向量（IV）」必须不可预测并且仅使用一次并且不可预测，简单来说就是使用随机的数据作为「初始化向量（IV）」。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://manual.seafile.com/security/security_features/" target="_blank" rel="noreferrer noopener nofollow" title="https://manual.seafile.com/security/security_features/">Security features &#8211; Seafile admin manual</a></li><li><a href="https://github.com/haiwen/seafile/issues/350" target="_blank" rel="noreferrer noopener nofollow" title="https://github.com/haiwen/seafile/issues/350">Encrypted libraries leak lots of information · Issue #350 · haiwen/seafile</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/4654/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>BOINC — 为科研事业贡献闲置的算力</title>
		<link>https://www.addesp.com/archives/4331</link>
					<comments>https://www.addesp.com/archives/4331#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Sat, 17 Apr 2021 14:10:38 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[BOINC]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=4331</guid>

					<description><![CDATA[服务器闲置？服务器性能过剩？来为科研事业做贡献吧。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">适用场景</h2>



<ul class="wp-block-list">
<li>闲置的服务器，或者服务器长期性能过剩。这种情况应该常见于个人博主，一台服务器仅仅部署一个博客通常无法充分利用服务器的计算资源。</li>



<li>其它闲置的计算机，比如淘汰掉的笔记本，只要能联网就可以。</li>
</ul>



<h2 class="wp-block-heading">客户端安装</h2>



<p class="wp-block-paragraph">BOINC 可以分为两部分，一部分是客户端，用于执行具体的计算。另一部分为管理端，可以管理多个客户端，比如指定具体的科研项目，设置 CPU 使用率上限等。</p>



<p class="wp-block-paragraph">本文将提供 Docker 部署客户端的方法。你可以直接运行下面的命令来启动客户端。</p>



<pre class="wp-block-code language-sh"><code>docker run -d \
  --name boinc \
  --net=host \
  --pid=host \
  -v /opt/appdata/boinc:/var/lib/boinc \
  -e BOINC_GUI_RPC_PASSWORD="123" \
  -e BOINC_CMD_LINE_OPTIONS="--allow_remote_gui_rpc" \
  boinc/client</code></pre>



<ul class="wp-block-list">
<li><code>/var/lib/boinc</code>：计算时所使用的数据目录，作为用户无需关心，只需要挂载到宿主机上妥善保存即可。</li>



<li><code>BOINC_GUI_RPC_PASSWORD</code>：管理端连接客户端时所使用的密码。</li>



<li><code>BOINC_CMD_LINE_OPTIONS</code>：运行客户端时的参数，<code>--allow_remote_gui_rpc</code> 表示允许管理端远程控制。</li>
</ul>



<p class="wp-block-paragraph">然后打开客户端所在机器的 TCP 31416 端口，这个端口将用于与管理端通信。</p>



<p class="wp-block-paragraph">BOINC 客户端还有针对特定平台的优化版本，更多详情见 <a href="https://hub.docker.com/r/boinc/client" target="_blank" rel="noreferrer noopener" title="https://hub.docker.com/r/boinc/client">boinc/client</a>。</p>



<h2 class="wp-block-heading">管理端安装</h2>



<p class="wp-block-paragraph">你可以在 <a href="https://boinc.berkeley.edu/download_all.php" target="_blank" rel="noreferrer noopener">https://boinc.berkeley.edu/download_all.php</a> 下载对应平台的软件。</p>



<p class="wp-block-paragraph">以 Windows 为例，安装完打开应该是这样的。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="533" height="824" src="https://www.addesp.com/wp-content/uploads/2021/04/0.webp" alt="" class="wp-image-4345"/><figcaption class="wp-element-caption">BOINC 初始界面</figcaption></figure>



<p class="wp-block-paragraph">依次点击：视图 &#8211;&gt; 高级视图。会出现下面的界面</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1920" height="1029" src="https://www.addesp.com/wp-content/uploads/2021/04/1.webp" alt="" class="wp-image-4346"/><figcaption class="wp-element-caption">BOINC 高级视图</figcaption></figure>



<p class="wp-block-paragraph">依次点击：文件 &#8211;&gt; 选择计算机。会出现下面的界面</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="495" height="143" src="https://www.addesp.com/wp-content/uploads/2021/04/2.webp" alt="" class="wp-image-4347"/><figcaption class="wp-element-caption">BOINC 客户端选择框</figcaption></figure>



<p class="wp-block-paragraph">在主机名框内输入你的客户端的 IP 地址或者域名，密码框内输入部署客户端时的密码，最后点击确定。</p>



<p class="wp-block-paragraph">如果连接成功则右下角会出现提示，如下图所示。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="351" height="63" src="https://www.addesp.com/wp-content/uploads/2021/04/3.webp" alt="" class="wp-image-4348"/><figcaption class="wp-element-caption">BOINC 成功连接客户端</figcaption></figure>



<p class="wp-block-paragraph">依次点击：工具 &#8211;&gt; 添加项目。选择一个你喜欢的项目，按照提示操作即可。</p>



<h2 class="wp-block-heading">添加项目或者使用账号管理器时卡死无响应</h2>



<p class="wp-block-paragraph">Windows 10 系统可能会出现这个问题。</p>



<p class="wp-block-paragraph">解决方法：右键点击任务栏的输入法图标 &#8211;&gt; 设置 &#8211;&gt; 常规 &#8211;&gt; 使用以前版本的微软拼音输入法。</p>



<p class="wp-block-paragraph">解决方法参考自<a href="https://tieba.baidu.com/p/6408249475" target="_blank" rel="noreferrer noopener">有关添加项目或者使用账号管理器时卡死无响应的问题【boinc吧】_百度贴吧</a>第 13 楼。</p>



<h2 class="wp-block-heading">无法连接客户端</h2>



<p class="wp-block-paragraph">此时观察 BOINC 的日志会发现这么一段。</p>



<pre class="wp-block-preformatted">GUI RPC request from non-allowed address x.y.z.q
</pre>



<p class="wp-block-paragraph">此时要将日志中提到的 IP 地址添加到 BOINC 数据目录下的 <code>remote_hosts.cfg</code> 中，重新连接就行了。</p>



<h2 class="wp-block-heading">相关网站</h2>



<ul class="wp-block-list">
<li>BOINC 官网：<a href="https://boinc.berkeley.edu/" target="_blank" rel="noreferrer noopener">https://boinc.berkeley.edu/</a>。</li>



<li>BOINC&nbsp;&#8211; 中国分布式计算总站：<a href="https://www.equn.com/wiki/BOINC">https://www.equn.com/wiki/BOINC</a>。</li>



<li>中国分布式计算论坛：<a href="http://www.equn.com/forum/">http://www.equn.com/forum/</a>。</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/4331/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>nginx TLS 配置建议（Mozilla Recommendation）</title>
		<link>https://www.addesp.com/archives/4293</link>
					<comments>https://www.addesp.com/archives/4293#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 12 Apr 2021 16:01:20 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=4293</guid>

					<description><![CDATA[本文主要按照 Mozilla 对于服务端 TLS 协议配置的建议来提供 nginx 的配置建议，按照这些建议进行配置可以让我们的站点更加安全。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">本文主要按照 Mozilla 对于服务端 TLS 协议配置的建议来提供 nginx 的配置建议，按照这些建议进行配置可以让我们的站点更加安全。</p>



<h2 class="wp-block-heading">适用于个人站点的配置</h2>



<p class="wp-block-paragraph">如果你的站点无需向后兼容（老旧的浏览器、客户端和操作系统），那么这是整个建议中最安全的配置。所以十分适合个人站点，比如个人博客。</p>



<pre class="wp-block-code language-nginx"><code>server {
    ......
    # 启用 HSTS。
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    # 仅启用 TLS 1.3 协议。
    ssl_protocols TLSv1.3;
    ssl_conf_command MinProtocol TLSv1.3;
    # 启用建议的加密套件，这些套件均支持前向安全性。
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    # 启用建议的 ECC 曲线。
    ssl_ecdh_curve  X25519:prime256v1:secp384r1;
    ......
}</code></pre>



<h2 class="wp-block-heading">适用于绝大多数站点的配置</h2>



<p class="wp-block-paragraph">如果你需要兼容过去五年内发布的应用程序（如浏览器），下面是建议的配置。</p>



<pre class="wp-block-code language-nginx"><code>server {
    ......
    # 启用 HSTS。
    add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
    # 仅启用 TLS 1.2 和 TLS 1.3 协议。
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_conf_command MinProtocol TLSv1.2;
    # 启用建议的加密套件，这些套件均支持前向安全性。
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    # 启用建议的 ECC 曲线。
    ssl_ecdh_curve  X25519:prime256v1:secp384r1;
    ......
}</code></pre>



<h2 class="wp-block-heading">升级你的 Openssl</h2>



<p class="wp-block-paragraph">请将你的 Openssl 升级到 1.1.1k 或以上的版本，之前的版本已经被爆出了两个漏洞，详见 <a href="https://www.openssl.org/news/secadv/20210325.txt" target="_blank" rel="noreferrer noopener" title="https://www.openssl.org/news/secadv/20210325.txt">OpenSSL Security Advisory [25 March 2021]</a>。一个漏洞可以进行 DoS 攻击，另一个则可以进行中间人攻击。</p>



<p class="wp-block-paragraph">你可以通过运行命令 <code>nginx -V</code> 来查看你的 nginx 编译时用的 openssl 的版本，或者运行时使用的版本。你应该能看到下面这行，请保证运行时所使用 openssl 的版本在 1.1.1k 及以上，如果没有括号里的内容请确保编译时所使用的 openssl 的版本在 1.1.1k 及以上</p>



<pre class="wp-block-preformatted">built with OpenSSL 1.1.1i  8 Dec 2020 (running with OpenSSL 1.1.1k  25 Mar 2021)</pre>



<h2 class="wp-block-heading">前向安全性</h2>



<p class="wp-block-paragraph">简单来说如果某一天你的服务器上的证书私钥泄露了，你之前的通信也无法被解密，你需要做的只是换一个证书而已。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://wiki.mozilla.org/Security/Server_Side_TLS" target="_blank" rel="noreferrer noopener" title="https://wiki.mozilla.org/Security/Server_Side_TLS">Security/Server Side TLS &#8211; MozillaWiki</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/4293/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>BGP 劫持 — 连 IP 都是假的</title>
		<link>https://www.addesp.com/archives/4225</link>
					<comments>https://www.addesp.com/archives/4225#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Fri, 09 Apr 2021 08:10:29 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=4225</guid>

					<description><![CDATA[听说你喜欢 ping ？]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">当你打不开某个网站的时候你会怎么办？</p>



<p class="wp-block-paragraph">你可能会先 ping 一下域名看一下延迟。如果延迟正常的话你大概会检查一下自己的 DNS 解析结果是否正确。如果 DNS 解析也是正确的你大概会怀疑网站本身出了问题。但是你老家的亲戚能打开你却打不开，这是怎么回事呢？</p>



<p class="wp-block-paragraph">这种情况下你可能遇到了「BGP 劫持」，它可以将你的通信劫持到指定主机上，并且 IP 地址不发生变化。</p>



<h2 class="wp-block-heading">什么是 BGP ?</h2>



<p class="wp-block-paragraph">BGP（Border Gateway Protocol），即边界网关协议，用于各个 AS（自治系统）之间互相交换路由信息。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>自治系统（AS）：可以简单地理解为一个或多个网络管理员管理的网络区域，比如一块属于某个 ISP 的网络，某个学校的网络，你自己在家里建立一个局域网也可以理解为自治系统。</p></blockquote>



<p class="wp-block-paragraph">一般来说路由器可以通过 RIP 或 OSPF 协议来互相交换路由信息，但是这通常只能用于不那么大的网络。如果网络太大，比如国与国之间，那么 RIP 和 OSPF 协议就力不从心了。</p>



<p class="wp-block-paragraph">首先就是要维护的路由信息太大，软硬件成本都不可接受。然后就是各个网络内部的情况可能不同，比如计算路线代价的时候可能各个自治系统的定义不一样，比如自治系统 A 的某条线路代价为 100 表示速度比较慢的线路，但是自治系统 B 的代价 100 则表示不可用的线路。最后就是一些特殊的需求，比如国家内部的终端在公网交换信息，理论上有两种路线，一种是走国内的线路，另一种是从国外的线路绕行到国内的线路，BGP 协议可以让国内的通信无需绕行国外，提高速度。</p>



<p class="wp-block-paragraph">下图是一个简单的 BGP 工作示意图。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1377" height="640" src="https://www.addesp.com/wp-content/uploads/2021/04/BGP-1.webp" alt="" class="wp-image-4258"/><figcaption>BGP 协议</figcaption></figure>



<h2 class="wp-block-heading">BGP 协议的安全缺陷</h2>



<p class="wp-block-paragraph">BGP 协议并不能验证 BGP 发言人所声明的信息是否为真，也就是说 BGP 发言人完全可以声明一个不属于自己的网络，然后将被声明的网络的流量引到自己的自治系统内，完成 BGP 劫持。</p>



<p class="wp-block-paragraph">下图是一个 BGP 劫持的示意图。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1390" height="1000" src="https://www.addesp.com/wp-content/uploads/2021/04/BGP劫持.webp" alt="" class="wp-image-4262"/><figcaption>BGP 劫持</figcaption></figure>



<p class="wp-block-paragraph">此时攻击者可选择丢弃掉 N3 网络所有的流量、进行窃听或者篡改。</p>



<h2 class="wp-block-heading">为什么 IP 不会变？</h2>



<p class="wp-block-paragraph">回顾 OSI 七层模型，其中第三层为网络层，负责根据 IP 转发 IP 数据报。一旦 BGP 劫持成功，路由器会错误地将发往网络 N3 的数据报发往网络 N4，但是路由器并不知道，所以从使用者的角度看，自己的 IP 地址是正常的。</p>



<h2 class="wp-block-heading">BGP 协议的改进</h2>



<p class="wp-block-paragraph"><a href="https://tools.ietf.org/html/rfc7454" target="_blank" rel="noreferrer noopener" title="https://tools.ietf.org/html/rfc7454">BGP Operations and Security — RFC7454</a> 中记录了一些最佳实践，如引入 ACL（访问控制列表），在传输层建立安全机制等，有兴趣的读者可以自行阅读。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/4225/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>OCSP—让你及时发现被吊销的数字证书的协议</title>
		<link>https://www.addesp.com/archives/3498</link>
					<comments>https://www.addesp.com/archives/3498#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Sun, 31 Jan 2021 17:07:50 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=3498</guid>

					<description><![CDATA[没有及时发现证书失效是很可怕的事，尤其是在金融交易中。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">用途</h2>



<p class="wp-block-paragraph">互联网世界中我们需要确定对方的身份，比如防止我想和小明说话，但是实际却在和黑客说话这种情况。一种比较常见的认证方式就是数字证书，比如 HTTPS 站点的 SSL 证书。关于 HTTPS 的工作流程的简单介绍，可以看一下我之前的一篇文章——<a href="https://www.addesp.com/archives/284" target="_blank" rel="noreferrer noopener">简要说明 HTTPS 是如何保证数据安全</a>。</p>



<p class="wp-block-paragraph">一般来说客户端收到对方的证书后会沿着证书链确定各级证书是否有效以决定是否信任接收到的证书。在这种验证流程中操作系统、浏览器和安全软件都可以内置一些 CA 的证书以便证书的检查。</p>



<p class="wp-block-paragraph">如果一个证书是无效的，那么它一定符合下列情况中的至少一个。</p>



<ul class="wp-block-list"><li>证书本身过期或者证书链上的某个证书过期。</li><li>证书本身被吊销或者证书链上的某个证书被吊销。</li></ul>



<p class="wp-block-paragraph">证书的有效期已经写入证书本身，无需额外的信息即可判断。但是如果一个证书出于某种原因被吊销了（比如私钥泄露），客户端此时无法根据证书所携带的信息来确定是否被吊销。OCSP 就是为了解决这个问题。</p>



<h2 class="wp-block-heading">协议概述</h2>



<p class="wp-block-paragraph">OCSP 全称 Online Certificate Status Protocol，即在线证书状态协议。它可以让客户端与服务器通信以确定某个证书的状态，即未撤销、已撤销或未知。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>笔者并未在协议文档中找到关于 OCSP 所使用的传输机制，报文结构等信息，所以 OCSP 理论上可以有多种实现。</p></blockquote>



<h2 class="wp-block-heading">通信流程</h2>



<p class="wp-block-paragraph">客户端向提供 COSP 服务的服务器发送一个请求，包含下列内容。</p>



<ul class="wp-block-list"><li>协议版本。</li><li>服务请求的类型（协议文档并未指明有哪些类型）。</li><li>打算查询状态的一个或多个证书的 ID。</li><li>可选的扩展信息。</li></ul>



<p class="wp-block-paragraph">服务器接收到请求之后首先会检查请求是否符合下列要求。</p>



<ul class="wp-block-list"><li>请求格式正确。</li><li>服务器支持请求中所指定的「服务请求的类型」。</li><li>客户端提供的信息要足以让服务器做出相应，即不能缺少必要信息。</li></ul>



<p class="wp-block-paragraph">然后服务器会通过某些方式获取证书的状态，然后响应客户端的请求，响应中包含下列内容。</p>



<ul class="wp-block-list"><li>响应内容的语法版本。</li><li>响应器的 ID。</li><li>本次响应的生成时间。</li><li>针对每一个证书的响应，包含下列内容。<ul><li>该证书的 ID。</li><li>该证书的状态，包含未撤销、已撤销和未知。</li><li>针对本证书的响应的有效期。</li><li>可选的扩展信息。</li></ul></li><li>可选的扩展信息。</li><li>使用的签名算法的 ID。</li><li>本次响应内容的哈希值的签名。</li></ul>



<p class="wp-block-paragraph">证书状态未知表示服务器不知道这个证书的状态。通常是签发被查询证书的 CA 不受信任，比如自签证书。也有可能是服务器知道的不够多，比如这个证书本身没有问题，但是服务器没有收录。当客户端得到这个回复的时候可以自行选择，比如默认不信任，或者通过其他方式查询状态等。</p>



<p class="wp-block-paragraph">证书状态为已撤销通常表示为此证书被 CA 撤销，但这并不绝对。对于类似自签证书这样的由不受信任的 CA 签发的证书，服务器也可以在响应中将其标记为已撤销状态，意为服务器打算让客户端直接拒绝这个证书，而不是像证书状态未知那样交给客户端自己去选择。</p>



<p class="wp-block-paragraph">一旦一个由不受信任的 CA 颁发的证书在本次响应中被标记为已撤销，那么本次响应必须要符合下列要求。</p>



<ul class="wp-block-list"><li>必须在本次响应中说明撤销的原因。</li><li>必须在本次响应中说明撤销的时间。</li><li>不能在本次响应中包含 CRL 的参考信息。</li></ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>CRL 是另一个用来查询证书状态的协议。</p></blockquote>



<h2 class="wp-block-heading">写在最后</h2>



<p class="wp-block-paragraph">OCSP 会拖慢证书的验证时间，尤其是在提供 OCSP 的服务器不那么可用的情况下，所以有了 OCSP stapling。它将验证工作放到了 Web 服务器，也就是说你在连接一个 HTTPS 站点的时候同时向站点对应的服务器发起 OCSP 请求，因为 Web 服务器对于访问它的客户端来说通常都是高可用的，所以这种方式一般比较快。本站也在写完本篇文章后支持了此种方式。</p>



<p class="wp-block-paragraph">本文只是粗略地说明协议的运行过程，一些细节被省略，比如如何预防重放攻击。如果有兴趣可以自行查看协议文档。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://tools.ietf.org/html/rfc6960" target="_blank" rel="noreferrer noopener">RFC 6960 &#8211; X.509 Internet Public Key Infrastructure Online Certificate Status Protocol &#8211; OCSP</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/3498/feed</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>apt 软件源格式解析</title>
		<link>https://www.addesp.com/archives/3406</link>
					<comments>https://www.addesp.com/archives/3406#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Tue, 26 Jan 2021 20:13:57 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[apt]]></category>
		<category><![CDATA[Ubuntu]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=3406</guid>

					<description><![CDATA[总之就是一篇看了也没什么用的文章，了解一下而已。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">每次安装完 Ubuntu 的第一件事情就是给 <code>apt</code> 换源，而换源过程基本就是复制粘贴，今天突然发现自己复制粘贴这么久也不知道粘贴了什么，于是了解了一下并分享出来。总之就是一篇看了也没什么用的文章，了解一下而已。</p>



<h2 class="wp-block-heading">正文</h2>



<p class="wp-block-paragraph">Linux 上有问题就先 <code>man</code> 一下，于是我 <code>man apt</code>，然后发现没有说关于 <code>source.list</code> 的格式，不过在手册的末尾最后发现有专门的文档讲解文件格式，于是 <code>man source.list</code>。</p>



<p class="wp-block-paragraph">其实 <code>apt</code> 源文件并不只有 <code>source.list</code>，也有一个叫 <code>source.sources</code>。两者的区别是格式不同。前者使用的格式称为 <code>ONE-LINE-STYLE</code>，也就是网上常见的一行一个的那种。后者使用的格式叫做 <code>DEB822-STYLE</code>，这种格式在网上应该比较少见。</p>



<h3 class="wp-block-heading">ONE-LINE-STYLE</h3>



<p class="wp-block-paragraph">这种格式是最常见的了，通常从网上复制粘贴来的就是这个格式。下面是 Ubuntu 20.04 LTS 的默认源。</p>



<pre class="wp-block-code"><code># See http://help.ubuntu.com/community/UpgradeNotes for how to upgrade to
# newer versions of the distribution.
deb http://archive.ubuntu.com/ubuntu/ focal main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ focal main restricted

## Major bug fix updates produced after the final release of the
## distribution.
deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted
# deb-src http://archive.ubuntu.com/ubuntu/ focal-updates main restricted

## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team. Also, please note that software in universe WILL NOT receive any
## review or updates from the Ubuntu security team.
deb http://archive.ubuntu.com/ubuntu/ focal universe
# deb-src http://archive.ubuntu.com/ubuntu/ focal universe
deb http://archive.ubuntu.com/ubuntu/ focal-updates universe
# deb-src http://archive.ubuntu.com/ubuntu/ focal-updates universe

## N.B. software from this repository is ENTIRELY UNSUPPORTED by the Ubuntu
## team, and may not be under a free licence. Please satisfy yourself as to
## your rights to use the software. Also, please note that software in
## multiverse WILL NOT receive any review or updates from the Ubuntu
## security team.
deb http://archive.ubuntu.com/ubuntu/ focal multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ focal multiverse
deb http://archive.ubuntu.com/ubuntu/ focal-updates multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ focal-updates multiverse

## N.B. software from this repository may not have been tested as
## extensively as that contained in the main release, although it includes
## newer versions of some applications which may provide useful features.
## Also, please note that software in backports WILL NOT receive any review
## or updates from the Ubuntu security team.
deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse
# deb-src http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse

## Uncomment the following two lines to add software from Canonical's
## 'partner' repository.
## This software is not part of Ubuntu, but is offered by Canonical and the
## respective vendors as a service to Ubuntu users.
# deb http://archive.canonical.com/ubuntu focal partner
# deb-src http://archive.canonical.com/ubuntu focal partner

deb http://security.ubuntu.com/ubuntu/ focal-security main restricted
# deb-src http://security.ubuntu.com/ubuntu/ focal-security main restricted
deb http://security.ubuntu.com/ubuntu/ focal-security universe
# deb-src http://security.ubuntu.com/ubuntu/ focal-security universe
deb http://security.ubuntu.com/ubuntu/ focal-security multiverse
# deb-src http://security.ubuntu.com/ubuntu/ focal-security multiverse</code></pre>



<p class="wp-block-paragraph">下面是此风格的基本格式。</p>



<pre class="wp-block-code"><code>deb &#91; option1=value1 option2=value2 ] uri suite &#91;component1] &#91;component2] &#91;...]
deb-src &#91; option1=value1 option2=value2 ] uri suite &#91;component1] &#91;component2] &#91;...]</code></pre>



<ul class="wp-block-list"><li>deb：表示从这里可以找到二进制程序，即包含已经编译好的程序。</li><li>deb-src：表示从这里找到源码。</li><li>option：表示额外的选项，比如 <code>arch</code> 表示体系结构，<code>amd64</code> 啥的。语法上不仅支持 <code>option=value1</code> 也支持 <code>option=value1,value2</code>  、<code>option-=value</code> 和 <code>option+=value</code>。<code>+=</code> 和 <code>-=</code> 仅支持那些可能有多个值的选项。</li><li>uri：就是源的网址，比如腾讯的 apt 源的网址就是 <a href="http://mirrors.cloud.tencent.com/ubuntu/" target="_blank" rel="noreferrer noopener">http://mirrors.cloud.tencent.com/ubuntu/</a>。</li><li>suite：代表发行版的类型如 <code>stable</code>。也可以代表发行版的代号比如 Ubutnu 20.24 LTS 的代号就是 <code>focal</code>。<a href="#参考资料"><sup>[3]</sup></a>所以说如果你用的是 Ubutnu 18.04，那么你应该写 <code>bionic</code>，因为这个版本的代号就叫 <code>bionic</code>。<a href="https://www.addesp.com/wp-admin/post.php?post=3406&amp;action=edit#%E5%8F%82%E8%80%83%E8%B5%84%E6%96%99"><sup>[3]</sup></a>不过版本代号后面可能还会跟着如 <code>-backports</code> 这样的后缀，相关信息见 <a href="https://help.ubuntu.com/community/UbuntuUpdates" target="_blank" rel="noreferrer noopener">UbuntuUpdates &#8211; Community Help Wiki</a>。</li><li>component：表示软件包的分类，不同的 Linux 发行版有不同的分类。对于 Ubuntu 来说有下面的几种。<ul><li>main：由 <a href="https://baike.baidu.com/item/Canonical" target="_blank" rel="noreferrer noopener">Canonical</a> 支持的自由和开源软件。<a href="#参考资料" rel="nofollow"><sup>[1]</sup></a></li><li>universe：由社区维护的自由和开源软件。<a href="#参考资料" title="#参考资料"><sup>[1]</sup></a></li><li>restricted：设备驱动程序。<a href="#参考资料" title="#参考资料"><sup>[1]</sup></a></li><li>multiverse：受版权或法律问题限制的软件。<a href="#参考资料" title="#参考资料"><sup>[1]</sup></a></li></ul></li></ul>



<h3 class="wp-block-heading">DEB822-STYLE</h3>



<p class="wp-block-paragraph">此格式从 <code>apt 1.1</code> 开始支持，文件必须以 <code>.sources</code> 结尾。下面是它的基本格式。</p>



<pre class="wp-block-code"><code>Types: deb deb-src
URIs: uri
Suites: suite
Components: &#91;component1] &#91;component2] &#91;...]
option1: value1
option2: value2

Types: deb deb-src
URIs: uri
Suites: suite
Components: &#91;component1] &#91;component2] &#91;...]
option1: value1
option2: value2</code></pre>



<p class="wp-block-paragraph">同一条目中的每个字段各占一行，不同的条目间用一行空行分隔。各个字段的含义与 <code>ONE-LINE-STYLE</code> 中的完全相同。你也可以通过添加一行 <code>Enabled: no</code> 来禁用某一个条目。如果一个字段有多个值则每个值之间用空格分隔。</p>



<h3 class="wp-block-heading">例子</h3>



<p class="wp-block-paragraph">下面的两种格式的源配置是等效的。</p>



<pre class="wp-block-code"><code>deb http://us.archive.ubuntu.com/ubuntu focal main restricted
deb http://security.ubuntu.com/ubuntu focal-security main restricted
deb http://us.archive.ubuntu.com/ubuntu focal-updates main restricted</code></pre>



<pre class="wp-block-code"><code>Types: deb
URIs: http://us.archive.ubuntu.com/ubuntu
Suites: focal focal-updates
Components: main restricted

Types: deb
URIs: http://security.ubuntu.com/ubuntu
Suites: focal-security
Components: main restricted</code></pre>



<h2 class="wp-block-heading" id="参考资料">参考资料</h2>



<ul class="wp-block-list"><li>[1] <a href="https://help.ubuntu.com/community/Repositories/Ubuntu" target="_blank" rel="noreferrer noopener">Repositories/Ubuntu &#8211; Community Help Wiki</a></li><li>[2] <a href="https://help.ubuntu.com/community/UbuntuUpdates" target="_blank" rel="noreferrer noopener">UbuntuUpdates &#8211; Community Help Wiki</a></li><li>[3] <a href="https://wiki.ubuntu.com/DevelopmentCodeNames" target="_blank" rel="noreferrer noopener">DevelopmentCodeNames &#8211; Ubuntu Wiki</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/3406/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>nginx 防火墙模块开发总结</title>
		<link>https://www.addesp.com/archives/2876</link>
					<comments>https://www.addesp.com/archives/2876#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 21 Dec 2020 11:01:33 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[项目分享]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[防火墙]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=2876</guid>

					<description><![CDATA[我起初并没有写这篇文章的意思，因为无论是从难度还是代码复杂度来说这个项目在我写过的项目里只能算中等，但是它却有一个特点，就是我的第一个专门开发出来供他人使用并且希望被他人使用的项目。先前的项目要不然不能公开，要不然个人属性非常强，不适合他人使用。所以这篇文章主要是以纪念为目的的。]]></description>
										<content:encoded><![CDATA[<div class='github-info-card github-info-card-full card shadow-sm' data-author='ADD-SP' data-project='ngx_waf' githubinfo-card-id='3917628351' data-getdata='frontend' data-description='' data-stars='' data-forks=''><div class='github-info-card-header'><a href='https://github.com/' ref='nofollow' target='_blank' title='Github' no-pjax><span><i class='fa fa-github'></i> GitHub</span></a></div><div class='github-info-card-body'>
			<div class='github-info-card-name-a'>
				<a href='https://github.com/ADD-SP/ngx_waf' target='_blank' no-pjax>
					<span class='github-info-card-name'>ADD-SP/ngx_waf</span>
				</a>
				</div>
			<div class='github-info-card-description'></div>
		</div><div class='github-info-card-bottom'>
				<span class='github-info-card-meta github-info-card-meta-stars'>
					<i class='fa fa-star'></i> <span class='github-info-card-stars'></span>
				</span>
				<span class='github-info-card-meta github-info-card-meta-forks'>
					<i class='fa fa-code-fork'></i> <span class='github-info-card-forks'></span>
				</span>
			</div></div>



<h2 class="wp-block-heading">为什么要写这篇文章？</h2>



<p class="wp-block-paragraph">我起初并没有写这篇文章的意思，因为无论是从难度还是代码复杂度来说这个项目在我写过的项目里只能算中等，但是它却有一个特点，就是我的第一个专门开发出来供他人使用并且希望被他人使用的项目。先前的项目要不然不能公开，要不然个人属性非常强，不适合他人使用。所以这篇文章主要是以纪念为目的的。</p>



<h2 class="wp-block-heading">为什么要开启这个项目？</h2>



<p class="wp-block-paragraph">那段时间我的站点有时会打不开，起初以为是网络问题，后来越来越严重，进入后台才发现数据库 IO 拉满了。看了看 nginx 的日志才发现站点被疯扫，于是打算做点什么。</p>



<p class="wp-block-paragraph">我先从网上搜索到了一些基础的拦截扫站的规则，直接 copy 到 <code>nginx.conf</code> 里就能用，但是这些规则都特别多，而且杂，直接丢到配置文件里会大大降低配置的可读性，所以当初就被否了。</p>



<p class="wp-block-paragraph">后来我拉黑了一些 IP，不过这也就一时起作用，过段时间换了 IP 继续来扫。补救措施是用 <code>whois</code> 找到 IP 地址快直接全部拉黑。但是这导致了一些误伤，于是这个方案也被否了。</p>



<p class="wp-block-paragraph">然后我找到了 <a href="https://github.com/loveshell/ngx_lua_waf" target="_blank" rel="noreferrer noopener">ngx_lua_waf</a>，用了一段觉得还行，但是也发现了一些缺陷，最明显的就是拉黑 IP 的时候只能拉黑单个 IP，而不能拉黑一个地址块。但是这个模块大概已经停止维护了，不能指望作者更新了。</p>



<p class="wp-block-paragraph">然后在 Github 上看了几个防火墙模块，要不然功能不全，要不然使用复杂，于是萌生了自己写模块的想法。</p>



<h2 class="wp-block-heading">为什么用 C 语言开发？</h2>



<p class="wp-block-paragraph">主要是因为这样可以不用安装 <a href="https://github.com/openresty/lua-nginx-module" target="_blank" rel="noreferrer noopener">lua_nginx_module</a>，其次是因为 C 的执行效率理论上要高于 lua，因为 lua 需要启动一个 VM。</p>



<p class="wp-block-paragraph">不过我主要考虑的是安装是否方便，性能倒不是那么看重，因为 <a href="https://github.com/openresty/lua-nginx-module" target="_blank" rel="noreferrer noopener">lua_nginx_module</a> 身经百战，性能完全可以保证。</p>



<p class="wp-block-paragraph">不过用 C 语言开发必然增加开发难度，最重要的是要自己控制内存，这类 Web 服务器上的模块一旦内存泄露、段错误啥的那麻烦就大了，不过我还是有信心不出大问题的。</p>



<h2 class="wp-block-heading">设计原则</h2>



<p class="wp-block-paragraph">只实现基础的防护，即 IP 检测、Url 检测、Get 参数检测、Cookie 检测、Post 检测、Referer 检测和 CC 防御。不会引入更复杂的功能，比如根据某个 IP 的行为进行综合判断是否拦截。</p>



<p class="wp-block-paragraph">在保证代码良好可读性的情况下提高性能。</p>



<h2 class="wp-block-heading"><a href="https://semver.org/lang/zh-CN/" target="_blank" rel="noreferrer noopener">语义化版本</a></h2>



<p class="wp-block-paragraph">说实话我之前还真没把这个当回事，但是当我打算把这个项目给别人用的时候我发现版本号似乎也能传递一些比较重要的信息，所以开发这个项目的时候我是一直遵守这个标准的。</p>



<p class="wp-block-paragraph">简单来说语义化版本将版本号分为 X.Y.Z。</p>



<ul class="wp-block-list"><li>当你做了不向下兼容的更改之后 X 要递增。</li><li>当你做了向下兼容的功能性新增的时候 Y 要递增。</li><li>当你做了向下兼容的修正时 Z 要递增。</li></ul>



<p class="wp-block-paragraph">这样版本号就可以向用户传递对应的信息，这无疑是十分有用的，最明显的就是当用户看到 X 递增的时候就会十分谨慎，因为贸然更新可能会导致现有的程序出现不兼容的情况。</p>



<h2 class="wp-block-heading">Change Log</h2>



<p class="wp-block-paragraph">作为一个打算给他人使用的项目，一个清晰的 Change Log 就是很必要的了，它可以将每次更新的内容展示出来，方便他人了解也方便自己记录。</p>



<p class="wp-block-paragraph">格式上基本遵循：<a href="https://keepachangelog.com/zh-CN/1.0.0/" target="_blank" rel="noreferrer noopener">https://keepachangelog.com/zh-CN/1.0.0/</a></p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1151" height="817" src="https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_02-51-16.webp" alt="" class="wp-image-2950"/><figcaption>效果图</figcaption></figure>



<p class="wp-block-paragraph">从效果上来看应该还算是清晰准确的 Change Log。</p>



<h2 class="wp-block-heading">开发文档</h2>



<p class="wp-block-paragraph">项目写到后期就不得不写开发文档了，首先是方便了自己，其次也是方便别人。可以从头开始写效率太低了，强烈推荐 <a href="https://www.doxygen.nl/index.html" target="_blank" rel="noreferrer noopener">doxygen</a>。这个工具可以通过代码内的注释自动生成文档，这样为源代码写注释的同时还能生成开发文档，十分方便。</p>



<p class="wp-block-paragraph">下面是对应的注释以及生成的开发文档。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="769" src="https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_03-03-19-1024x769.png" alt="" class="wp-image-2961" srcset="https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_03-03-19-1024x769.png 1024w, https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_03-03-19-300x225.png 300w, https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_03-03-19-768x577.png 768w, https://www.addesp.com/wp-content/uploads/2020/12/Snipaste_2020-12-20_03-03-19.png 1053w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption>注释内容</figcaption></figure>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1894" height="3196" src="https://www.addesp.com/wp-content/uploads/2020/12/0.webp" alt="" class="wp-image-2963"/><figcaption>开发文档</figcaption></figure>



<p class="wp-block-paragraph">开发文档简单，清晰，还能自动生成函数调用图和被调用图等一系列图。如果觉得丑还能自定义 CSS，不过对于我这个前端困难户来说就不搞了。</p>



<h2 class="wp-block-heading">性能优化</h2>



<p class="wp-block-paragraph">性能优化的主要工作就是降低请求检查花费的时间。</p>



<h3 class="wp-block-heading">缓存</h3>



<p class="wp-block-paragraph">Url 检查、Get 参数检查、Post 检查、Cookie 检查和 Referer 检查都需要执行正则匹配，暂时没发现什么合适的方法去优化，只能一个一个地去检查。时间复杂度为 $\text{O}(nm)$，$n$ 为需要测试的正则条数，$m$ 为执行正则匹配的时间。</p>



<p class="wp-block-paragraph">不过由于本模块只会在 nginx 启动时读取规则，运行时规则不会改变，所以对于同一个 URL 无论检测多少次结果都不会变，于是本模块会缓存检查的结果，每次检查前先读取缓存，如果命中则直接取出结果，反之则走流程检查。除了 IP 检查和 Post 检查以外所有的检查都使用了缓存机制。</p>



<p class="wp-block-paragraph">缓存淘汰策略为 LRU。当缓存的内存不足时会引起频繁的淘汰，增加内存碎片。于是本模块会周期性地按照 LRU 的策略淘汰掉一定比例的缓存。</p>



<h3 class="wp-block-heading">前缀树优化</h3>



<p class="wp-block-paragraph">IP 检查很有搞头。本模块使用了一种经过修改的前缀树来改进 IP 检查的性能，见下图。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="872" height="712" src="https://www.addesp.com/wp-content/uploads/2020/12/未命名绘图.webp" alt="" class="wp-image-2981"/><figcaption>本模块所使用的前缀树</figcaption></figure>



<p class="wp-block-paragraph">从图示就可以看出此结构可以方便地处理单个 IP 的检查和 IP 地址块的检查。</p>



<p class="wp-block-paragraph">前缀树的查找时间为 $\text{O}(h)$，其中 $h$ 为树的高度。对于 IPV4 来说一共有 32 位，那么树高最多 32，IPV6 对应的树高最多为 128。那么查找时间复杂度为 $\text{O}(1)$，也就是说执行 IP 检查的时间基本上不会受到 IP 黑白名单规模的影响。</p>



<h3 class="wp-block-heading">测试</h3>



<p class="wp-block-paragraph">经过比较极限的测试，QPS 降低了约 4%，详情见<a href="https://docs.addesp.com/ngx_waf/zh-cn/guide/test.html#%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95" target="_blank" rel="noreferrer noopener" title="https://docs.addesp.com/ngx_waf/zh-cn/guide/test.html#%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95">性能测试</a>。</p>



<h2 class="wp-block-heading">我上 Trending 了！</h2>



<p class="wp-block-paragraph">2021 年 4 月 8 日晚上我发现我的项目出现在了 Github Trending（C 语言） 下，截图纪念一下。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1077" height="635" src="https://www.addesp.com/wp-content/uploads/2021/04/KK7o9cR.webp" alt="" class="wp-image-4223"/></figure>



<p class="wp-block-paragraph">希望不要明天起来就没了，多撑几天吧。</p>



<p class="wp-block-paragraph">第二天晚更新：博主已经凉了，散了吧。</p>



<h2 class="wp-block-heading">验证码</h2>



<p class="wp-block-paragraph">这里的验证码特指用于人机验证的验证码，比如滑块解锁等，也就是我们通常所说的 <a href="https://baike.baidu.com/item/CAPTCHA/9630117" target="_blank" rel="noreferrer noopener nofollow" title="https://baike.baidu.com/item/CAPTCHA/9630117">CAPTCHA</a>，比较有名的就是 reCAPTCHA。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="1576" height="992" src="https://www.addesp.com/wp-content/uploads/2022/01/5f7614c93b38651b67b75add_931071.webp" alt="" class="wp-image-5554"/><figcaption>reCAPTCHA</figcaption></figure>



<p class="wp-block-paragraph">目前已经集成了 reCAPTCHA 的全部种类的验证码以及 hCAPTCHA。本来想着集成一下腾讯云的验证码，但是发现集成起来好麻烦，远没有前两种简单，就一直搁置到现在。</p>



<p class="wp-block-paragraph">验证码的集成也带来了不小的好处，比如 CC 防护不再只能简单地拉黑了。有了验证码后就可以将 CC 防护的阈值降低一些，超出时弹出验证码，通过验证则放行，反之则拉黑一段时间。同时，根据某个用户的建议，现在当触发任何的拦截时，就会对该 IP 启用验证码，当且仅当通过验证时才会放行，否则无法进行任何访问。</p>



<h2 class="wp-block-heading">集成 ModSecurity</h2>



<p class="wp-block-paragraph">ModSecurity 是一个广泛使用的开源的 WAF 引擎，并且在网络上有现成的安全规则可以套用，所以本模块也继承了 ModSecurity 的引擎，可以加载 ModSecurity 的规则。</p>



<p class="wp-block-paragraph">（ModSecurity 的内存泄漏问题官方什么时候修一下）</p>



<h2 class="wp-block-heading">后续计划</h2>



<p class="wp-block-paragraph">本项目已经完成了性能优化，后续应该不会有大的改动，不过如果有合适的需求就另说了。有了新的进展我也会更新本文。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/2876/feed</wfw:commentRss>
			<slash:comments>36</slash:comments>
		
		
			</item>
		<item>
		<title>D-H 密钥交换—即使被窃听也能安全地交换密钥</title>
		<link>https://www.addesp.com/archives/2710</link>
					<comments>https://www.addesp.com/archives/2710#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Tue, 01 Dec 2020 14:03:22 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=2710</guid>

					<description><![CDATA[是的，即使通信全程都被窃听，你依然可以和对方安全地协商出密钥来进行加密通讯。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">是的，即使通信全程都被窃听，你依然可以和对方安全地协商出密钥来进行加密通讯。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p><strong>迪菲-赫尔曼密钥交换</strong>（英语：Diffie–Hellman key exchange，缩写为D-H） 是一种<a target="_blank" href="https://baike.baidu.com/item/%E5%AE%89%E5%85%A8%E5%8D%8F%E8%AE%AE" rel="noreferrer noopener">安全协议</a>。它可以让双方在完全没有对方任何预先信息的条件下通过不安全<a target="_blank" href="https://baike.baidu.com/item/%E4%BF%A1%E9%81%93" rel="noreferrer noopener">信道</a>创建起一个<a target="_blank" href="https://baike.baidu.com/item/%E5%AF%86%E9%92%A5" rel="noreferrer noopener">密钥</a>。这个密钥可以在后续的通讯中作为<a target="_blank" href="https://baike.baidu.com/item/%E5%AF%B9%E7%A7%B0%E5%AF%86%E9%92%A5" rel="noreferrer noopener">对称密钥</a>来<a target="_blank" href="https://baike.baidu.com/item/%E5%8A%A0%E5%AF%86" rel="noreferrer noopener">加密</a>通讯内容。公钥交换的概念最早由瑞夫·墨克（Ralph C. Merkle）提出，而这个密钥交换方法，由<a target="_blank" href="https://baike.baidu.com/item/%E6%83%A0%E7%89%B9%E8%8F%B2%E5%B0%94%E5%BE%B7%C2%B7%E8%BF%AA%E8%8F%B2" rel="noreferrer noopener">惠特菲尔德·迪菲</a>（Bailey Whitfield Diffie）和<a target="_blank" href="https://baike.baidu.com/item/%E9%A9%AC%E4%B8%81%C2%B7%E8%B5%AB%E5%B0%94%E6%9B%BC" rel="noreferrer noopener">马丁·赫尔曼</a>（Martin Edward Hellman）在1976年首次发表。马丁·赫尔曼曾主张这个密钥交换方法，应被称为<strong>迪菲-赫尔曼-墨克密钥交换</strong>（英语：Diffie–Hellman–Merkle key exchange）。</p><cite><a href="https://baike.baidu.com/item/%E8%BF%AA%E8%8F%B2%EF%BC%8D%E8%B5%AB%E5%B0%94%E6%9B%BC%E5%AF%86%E9%92%A5%E4%BA%A4%E6%8D%A2" target="_blank" rel="noreferrer noopener">迪菲-赫尔曼密钥交换_百度百科</a></cite></blockquote>



<h2 class="wp-block-heading">D-H 密钥交换流程</h2>



<ol class="wp-block-list"><li>通信双方甲和乙事先约定好两个精心挑选的数，一个是质数 $p$，另一个是 $g$。这两个数是可以公开的。我们假设 $p=23,g=5$。</li><li>甲随机选择一个整数 $a$，计算 $A=g^a \mod p$，并将 $A$ 发送给乙。其中 $A$ 是可以公开的，但是 $a$ 不能泄露。我们假设 $a=6$，那么 $A=5^6 \mod 23 =8$。</li><li>乙随机选择一个整数 $b$，计算 $B=g^b \mod p$，并将 $B$ 发送给甲。其中 $B$ 是可以公开的，但是 $b$ 不能泄露。我们假设 $b=15$，那么 $B=5^{15} \mod 23 =19$。</li><li>此时双方就可以计算出共有的密码 $s$。甲通过计算 $s = B^a \mod p = 19^6 \mod 23 = 2$。乙通过计算 $s = A^b \mod p = 8^6 \mod 23 = 2$。</li><li>双方得到了相同的密码 $s$。但这个密码并不是一方主动发给另一方的，而是通过双方交换的信息计算而来的。此时可以使用 $s$ 生成对称密钥来加密通信。</li></ol>



<p class="wp-block-paragraph">根据模运算法则 $g^a \mod p = [{(g \mod p)}^a] \mod p$，同理 $g^b \mod p = [{(g \mod p)}^b] \mod p$，显然 $s={(g^a \mod p)}^b \mod p = {(g^b \mod p)}^a \mod p$。</p>



<p class="wp-block-paragraph">可以发现公开的信息只有 $p,g,A,B$，而只需要在草稿纸上试一试就感觉这些信息逆向算出$a,b$是很难的事情，貌似只能去穷举，事实也确实如此。由于计算出密码 $s$ 除了 $p,g,A,B$ 以外还需要 $a$ 或 $b$，所以这种密钥交换方式即使在被窃听的情况下也能保证安全（前提是不会受到中间人攻击）。</p>



<h2 class="wp-block-heading">离散对数问题</h2>



<p class="wp-block-paragraph">上文说到逆向计算 $a,b$ 是很难的，这其实就是著名的离散对数问题。相关的数学表述本文不会涉及，首先是因为本文的初衷是了解即可，没必要钻那么深，其次就是笔者不是搞数学的，水平有限，查了半天资料还是一知半解的，就不瞎说了。就举一个比较直观的例子吧。</p>



<p class="wp-block-paragraph">我们设 $g=3,p=10$，依次计算 $g^i \mod p$，其中  $i = 0,1,2,···,99$。</p>


<div  class='collapse-block shadow-sm collapse-block-success collapsed hide-border-left'><div class='collapse-block-title'><span class='collapse-block-title-inner'>点击查看计算结果</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'><br />
$$<br />
\begin{aligned}<br />
3^{0} \mod 10 = 1 \\<br />
3^{1} \mod 10 = 3 \\<br />
3^{2} \mod 10 = 9 \\<br />
3^{3} \mod 10 = 7 \\<br />
3^{4} \mod 10 = 1 \\<br />
3^{5} \mod 10 = 3 \\<br />
3^{6} \mod 10 = 9 \\<br />
3^{7} \mod 10 = 7 \\<br />
3^{8} \mod 10 = 1 \\<br />
3^{9} \mod 10 = 3 \\<br />
3^{10} \mod 10 = 9 \\<br />
3^{11} \mod 10 = 7 \\<br />
3^{12} \mod 10 = 1 \\<br />
3^{13} \mod 10 = 3 \\<br />
3^{14} \mod 10 = 9 \\<br />
3^{15} \mod 10 = 7 \\<br />
3^{16} \mod 10 = 1 \\<br />
3^{17} \mod 10 = 3 \\<br />
3^{18} \mod 10 = 9 \\<br />
3^{19} \mod 10 = 7 \\<br />
3^{20} \mod 10 = 1 \\<br />
3^{21} \mod 10 = 3 \\<br />
3^{22} \mod 10 = 9 \\<br />
3^{23} \mod 10 = 7 \\<br />
3^{24} \mod 10 = 1 \\<br />
3^{25} \mod 10 = 3 \\<br />
3^{26} \mod 10 = 9 \\<br />
3^{27} \mod 10 = 7 \\<br />
3^{28} \mod 10 = 1 \\<br />
3^{29} \mod 10 = 3 \\<br />
3^{30} \mod 10 = 9 \\<br />
3^{31} \mod 10 = 7 \\<br />
3^{32} \mod 10 = 1 \\<br />
3^{33} \mod 10 = 3 \\<br />
3^{34} \mod 10 = 8 \\<br />
3^{35} \mod 10 = 2 \\<br />
3^{36} \mod 10 = 6 \\<br />
3^{37} \mod 10 = 6 \\<br />
3^{38} \mod 10 = 0 \\<br />
3^{39} \mod 10 = 6 \\<br />
3^{40} \mod 10 = 8 \\<br />
3^{41} \mod 10 = 2 \\<br />
3^{42} \mod 10 = 2 \\<br />
3^{43} \mod 10 = 6 \\<br />
3^{44} \mod 10 = 4 \\<br />
3^{45} \mod 10 = 2 \\<br />
3^{46} \mod 10 = 4 \\<br />
3^{47} \mod 10 = 0 \\<br />
3^{48} \mod 10 = 8 \\<br />
3^{49} \mod 10 = 8 \\<br />
3^{50} \mod 10 = 4 \\<br />
3^{51} \mod 10 = 2 \\<br />
3^{52} \mod 10 = 4 \\<br />
3^{53} \mod 10 = 4 \\<br />
3^{54} \mod 10 = 6 \\<br />
3^{55} \mod 10 = 8 \\<br />
3^{56} \mod 10 = 0 \\<br />
3^{57} \mod 10 = 4 \\<br />
3^{58} \mod 10 = 2 \\<br />
3^{59} \mod 10 = 2 \\<br />
3^{60} \mod 10 = 0 \\<br />
3^{61} \mod 10 = 8 \\<br />
3^{62} \mod 10 = 4 \\<br />
3^{63} \mod 10 = 4 \\<br />
3^{64} \mod 10 = 0 \\<br />
3^{65} \mod 10 = 2 \\<br />
3^{66} \mod 10 = 8 \\<br />
3^{67} \mod 10 = 4 \\<br />
3^{68} \mod 10 = 6 \\<br />
3^{69} \mod 10 = 4 \\<br />
3^{70} \mod 10 = 8 \\<br />
3^{71} \mod 10 = 4 \\<br />
3^{72} \mod 10 = 0 \\<br />
3^{73} \mod 10 = 0 \\<br />
3^{74} \mod 10 = 6 \\<br />
3^{75} \mod 10 = 0 \\<br />
3^{76} \mod 10 = 0 \\<br />
3^{77} \mod 10 = 4 \\<br />
3^{78} \mod 10 = 6 \\<br />
3^{79} \mod 10 = 2 \\<br />
3^{80} \mod 10 = 4 \\<br />
3^{81} \mod 10 = 2 \\<br />
3^{82} \mod 10 = 8 \\<br />
3^{83} \mod 10 = 0 \\<br />
3^{84} \mod 10 = 6 \\<br />
3^{85} \mod 10 = 4 \\<br />
3^{86} \mod 10 = 2 \\<br />
3^{87} \mod 10 = 8 \\<br />
3^{88} \mod 10 = 4 \\<br />
3^{89} \mod 10 = 0 \\<br />
3^{90} \mod 10 = 6 \\<br />
3^{91} \mod 10 = 2 \\<br />
3^{92} \mod 10 = 6 \\<br />
3^{93} \mod 10 = 0 \\<br />
3^{94} \mod 10 = 0 \\<br />
3^{95} \mod 10 = 0 \\<br />
3^{96} \mod 10 = 2 \\<br />
3^{97} \mod 10 = 6 \\<br />
3^{98} \mod 10 = 2 \\<br />
3^{99} \mod 10 = 6 \\<br />
\end{aligned}<br />
$$<br />
</div></div>



<p class="wp-block-paragraph">那么如果我问求一个 $k$ 使得 $3^k \mod 10 = 6$。在没有快捷算法的情况下我们只能一个一个地试，此时可以视为随机取 $i$ 的值去试。可以注意到在上面列出的 100 个计算结果中 6 出现了 13 次，出现的概率是 $13\%$，这意味着我在穷举过程中每次计算都有 $13\%$ 的概率的都正确答案。</p>



<p class="wp-block-paragraph">那么当我们让$p=17$，即一个素数时，情况就变了。</p>


<div  class='collapse-block shadow-sm collapse-block-success collapsed hide-border-left'><div class='collapse-block-title'><span class='collapse-block-title-inner'>点击查看计算结果</span><i class='collapse-icon fa fa-angle-down'></i></div><div class='collapse-block-body' style='display:none;'><br />
$$<br />
\begin{aligned}<br />
&amp; 3^{0} \mod 17 = 1 \\<br />
&amp; 3^{1} \mod 17 = 3 \\<br />
&amp; 3^{2} \mod 17 = 9 \\<br />
&amp; 3^{3} \mod 17 = 10 \\<br />
&amp; 3^{4} \mod 17 = 13 \\<br />
&amp; 3^{5} \mod 17 = 5 \\<br />
&amp; 3^{6} \mod 17 = 15 \\<br />
&amp; 3^{7} \mod 17 = 11 \\<br />
&amp; 3^{8} \mod 17 = 16 \\<br />
&amp; 3^{9} \mod 17 = 14 \\<br />
&amp; 3^{10} \mod 17 = 8 \\<br />
&amp; 3^{11} \mod 17 = 7 \\<br />
&amp; 3^{12} \mod 17 = 4 \\<br />
&amp; 3^{13} \mod 17 = 12 \\<br />
&amp; 3^{14} \mod 17 = 2 \\<br />
&amp; 3^{15} \mod 17 = 6 \\<br />
&amp; 3^{16} \mod 17 = 1 \\<br />
&amp; 3^{17} \mod 17 = 3 \\<br />
&amp; 3^{18} \mod 17 = 9 \\<br />
&amp; 3^{19} \mod 17 = 10 \\<br />
&amp; 3^{20} \mod 17 = 13 \\<br />
&amp; 3^{21} \mod 17 = 5 \\<br />
&amp; 3^{22} \mod 17 = 15 \\<br />
&amp; 3^{23} \mod 17 = 11 \\<br />
&amp; 3^{24} \mod 17 = 16 \\<br />
&amp; 3^{25} \mod 17 = 14 \\<br />
&amp; 3^{26} \mod 17 = 8 \\<br />
&amp; 3^{27} \mod 17 = 7 \\<br />
&amp; 3^{28} \mod 17 = 4 \\<br />
&amp; 3^{29} \mod 17 = 12 \\<br />
&amp; 3^{30} \mod 17 = 2 \\<br />
&amp; 3^{31} \mod 17 = 6 \\<br />
&amp; 3^{32} \mod 17 = 1 \\<br />
&amp; 3^{33} \mod 17 = 3 \\<br />
&amp; 3^{34} \mod 17 = 8 \\<br />
&amp; 3^{35} \mod 17 = 15 \\<br />
&amp; 3^{36} \mod 17 = 11 \\<br />
&amp; 3^{37} \mod 17 = 1 \\<br />
&amp; 3^{38} \mod 17 = 11 \\<br />
&amp; 3^{39} \mod 17 = 0 \\<br />
&amp; 3^{40} \mod 17 = 0 \\<br />
&amp; 3^{41} \mod 17 = 8 \\<br />
&amp; 3^{42} \mod 17 = 11 \\<br />
&amp; 3^{43} \mod 17 = 16 \\<br />
&amp; 3^{44} \mod 17 = 15 \\<br />
&amp; 3^{45} \mod 17 = 11 \\<br />
&amp; 3^{46} \mod 17 = 7 \\<br />
&amp; 3^{47} \mod 17 = 6 \\<br />
&amp; 3^{48} \mod 17 = 10 \\<br />
&amp; 3^{49} \mod 17 = 12 \\<br />
&amp; 3^{50} \mod 17 = 2 \\<br />
&amp; 3^{51} \mod 17 = 6 \\<br />
&amp; 3^{52} \mod 17 = 15 \\<br />
&amp; 3^{53} \mod 17 = 2 \\<br />
&amp; 3^{54} \mod 17 = 5 \\<br />
&amp; 3^{55} \mod 17 = 15 \\<br />
&amp; 3^{56} \mod 17 = 10 \\<br />
&amp; 3^{57} \mod 17 = 14 \\<br />
&amp; 3^{58} \mod 17 = 8 \\<br />
&amp; 3^{59} \mod 17 = 14 \\<br />
&amp; 3^{60} \mod 17 = 12 \\<br />
&amp; 3^{61} \mod 17 = 10 \\<br />
&amp; 3^{62} \mod 17 = 13 \\<br />
&amp; 3^{63} \mod 17 = 13 \\<br />
&amp; 3^{64} \mod 17 = 14 \\<br />
&amp; 3^{65} \mod 17 = 10 \\<br />
&amp; 3^{66} \mod 17 = 5 \\<br />
&amp; 3^{67} \mod 17 = 15 \\<br />
&amp; 3^{68} \mod 17 = 7 \\<br />
&amp; 3^{69} \mod 17 = 5 \\<br />
&amp; 3^{70} \mod 17 = 11 \\<br />
&amp; 3^{71} \mod 17 = 16 \\<br />
&amp; 3^{72} \mod 17 = 16 \\<br />
&amp; 3^{73} \mod 17 = 14 \\<br />
&amp; 3^{74} \mod 17 = 9 \\<br />
&amp; 3^{75} \mod 17 = 12 \\<br />
&amp; 3^{76} \mod 17 = 2 \\<br />
&amp; 3^{77} \mod 17 = 7 \\<br />
&amp; 3^{78} \mod 17 = 0 \\<br />
&amp; 3^{79} \mod 17 = 16 \\<br />
&amp; 3^{80} \mod 17 = 12 \\<br />
&amp; 3^{81} \mod 17 = 2 \\<br />
&amp; 3^{82} \mod 17 = 4 \\<br />
&amp; 3^{83} \mod 17 = 16 \\<br />
&amp; 3^{84} \mod 17 = 15 \\<br />
&amp; 3^{85} \mod 17 = 7 \\<br />
&amp; 3^{86} \mod 17 = 4 \\<br />
&amp; 3^{87} \mod 17 = 10 \\<br />
&amp; 3^{88} \mod 17 = 13 \\<br />
&amp; 3^{89} \mod 17 = 14 \\<br />
&amp; 3^{90} \mod 17 = 4 \\<br />
&amp; 3^{91} \mod 17 = 16 \\<br />
&amp; 3^{92} \mod 17 = 14 \\<br />
&amp; 3^{93} \mod 17 = 6 \\<br />
&amp; 3^{94} \mod 17 = 1 \\<br />
&amp; 3^{95} \mod 17 = 3 \\<br />
&amp; 3^{96} \mod 17 = 1 \\<br />
&amp; 3^{97} \mod 17 = 3 \\<br />
&amp; 3^{98} \mod 17 = 5 \\<br />
&amp; 3^{99} \mod 17 = 15 \\<br />
\end{aligned}<br />
$$<br />
</div></div>



<p class="wp-block-paragraph">此时 6 只出现了 5 次，而且呈现出了明显的周期现象，这个周期就是 $p-1$ 即 16。这时我们穷举的时候每次计算只有 $5\%$ 的概率是正确答案了。在这种情况下我们提高了穷举的难度。</p>



<p class="wp-block-paragraph">目前的数学研究结果让我们可以精心选择一个素数 $p$ 和另一个精心选择的 $g$ 来通过这样的方式来提高穷举的难度，比如我们选择一个长度为 $1024 bit$ 的素数，那么它的周期和 $2^{1024} \approx 1.798 \times 10^{308}$ 是同一个数量级的，那么暴力穷举算法的时间复杂度就是指数级的。那么如果素数的长度是 $2048 bit$ 呢？$4096 bit$ 呢？</p>



<p class="wp-block-paragraph">上面的例子并不严谨，但可以直观地感受一下离散对数问题的难度。稍微专业点的描述一下离散对数的难度就是暂时没有多项式时间或更快的算法能够求解离散对数问题。</p>



<h2 class="wp-block-heading">身份验证</h2>



<p class="wp-block-paragraph">D-H 密钥交换没有提供身份验证的机制，所以我们需要公钥基础设施来保证不会受到中间人攻击。</p>



<h2 class="wp-block-heading">前向安全性</h2>



<p class="wp-block-paragraph">前向安全性即通讯的主密钥泄露不会导致会话密钥的泄露，它可以保证之前的通讯不会因为在未来的某一时刻主密钥泄露而被解密。</p>



<p class="wp-block-paragraph">目前常见的身份认证方式之一就是基于非对称加密的证书验证。在这种方式下为了实现身份认证服务端会使用私钥给一些必要的信息签名发送给客户端，客户端确认服务端的身份后双方协商出会话密钥，协商过程中的信息会用公钥加密，只有私钥才能解密。此时客户端可以直接将会话密钥通过公钥加密后发送给服务端，然后开始加密通讯。这在正常情况下没有任何问题。但是一旦服务端的私钥泄露，那么之前的会话密钥就会被解密，之前的通讯信息也就暴露了。</p>



<p class="wp-block-paragraph">回忆一下，D-H 密钥交换过程中 $p,g,A,B$ 是可以公开的，只有$a,b$ 是秘密的，我们假设 $a,b$ 是随机选取的，并且在通讯结束后会被销毁。如果客户端在确认了服务端身份的之后采用 D-H 密钥交换的方式，而不是直接发送公钥加密后的会话密钥，那么即使未来的某一时刻服务端私钥泄露了，也不会泄露会话密钥，也就无法解密之前的通讯信息。</p>



<p class="wp-block-paragraph">目前这种机制已经被引入 TLS 中。</p>



<h2 class="wp-block-heading">写在最后</h2>



<p class="wp-block-paragraph">搞密码的这群人是何等的怪物（褒义）。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://baike.baidu.com/item/%E8%BF%AA%E8%8F%B2%EF%BC%8D%E8%B5%AB%E5%B0%94%E6%9B%BC%E5%AF%86%E9%92%A5%E4%BA%A4%E6%8D%A2" target="_blank" rel="noreferrer noopener">迪菲-赫尔曼密钥交换_百度百科</a></li><li><a href="https://baike.baidu.com/item/%E5%89%8D%E5%90%91%E5%AE%89%E5%85%A8%E6%80%A7/6357798" target="_blank" rel="noreferrer noopener">前向安全性_百度百科</a></li><li><a href="https://tools.ietf.org/html/rfc8446" target="_blank" rel="noreferrer noopener">RFC 8446 &#8211; The Transport Layer Security (TLS) Protocol Version 1.3</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/2710/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>SOCKS5 协议工作流程</title>
		<link>https://www.addesp.com/archives/2594</link>
					<comments>https://www.addesp.com/archives/2594#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 09 Nov 2020 16:05:40 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=2594</guid>

					<description><![CDATA[简介 SOCKS5 是一个代理协议，它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色，使 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">简介</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>SOCKS5 是一个代理协议，它在使用<a target="_blank" href="https://baike.baidu.com/item/TCP%2FIP%E5%8D%8F%E8%AE%AE" rel="noreferrer noopener">TCP/IP协议</a>通讯的前端机器和服务器机器之间扮演一个中介角色，使得<a target="_blank" href="https://baike.baidu.com/item/%E5%86%85%E9%83%A8%E7%BD%91" rel="noreferrer noopener">内部网</a>中的前端机器变得能够访问Internet网中的服务器，或者使通讯更加安全。SOCKS5 服务器通过将前端发来的请求转发给真正的目标服务器， 模拟了一个前端的行为。在这里，前端和SOCKS5之间也是通过TCP/IP协议进行通讯，前端将原本要发送给真正服务器的请求发送给SOCKS5服务器，然后SOCKS5服务器将请求转发给真正的服务器。</p><cite><a href="https://baike.baidu.com/item/socks5" target="_blank" rel="noreferrer noopener">socks5_百度百科</a></cite></blockquote>



<h2 class="wp-block-heading">工作流程</h2>



<p class="wp-block-paragraph">客户端需要先与代理服务器建立 TCP 连接，SOCKS 服务的默认端口是 1080，建立成功后客户端要与代理服务器协商认证方式并完成认证，然后就可以通过代理服务器收发数据。</p>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">TCP 连接建立后，客户端会向代理服务器发送请求，包含 SOCKS 协议的版本和客户端支持的认证方式。</p>



<figure class="wp-block-table is-style-regular"><table><thead><tr><th><strong>协议版本</strong></th><th>方法数</th><th>方法编号列表</th></tr></thead><tbody><tr><td>1Byte</td><td>1Byte</td><td>长度可变</td></tr></tbody></table><figcaption>请求格式</figcaption></figure>



<ul class="wp-block-list"><li>协议版本：对于 SOCKS5 协议来说这个字段永远是 5，代表 SOCKS 的第五版协议。</li><li>方法数：表示后续的方法编号列表中有多少种客户端支持的认证方法。</li><li>方法编号列表：存储客户端支持的认证方法的编号，这些编号有些是 SOCKS 协议预先指定的，有些是 <a href="https://baike.baidu.com/item/IANA/2800158" target="_blank" rel="noreferrer noopener">IANA</a> 事后分配的，也有些是给开发者自由使用的，也有的编号代表无需认证。</li></ul>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">代理服务器应该选择一个认证方法，并发送下面格式的请求，告知客户端被选择的认证方法。</p>



<figure class="wp-block-table"><table><thead><tr><th>协议<strong>版本</strong></th><th>方法编号</th></tr></thead><tbody><tr><td>1Byte</td><td>1Byte</td></tr></tbody></table><figcaption>响应格式</figcaption></figure>



<hr class="wp-block-separator is-style-default"/>



<p class="wp-block-paragraph">完成了认证后，客户端就可以向代理服务器发送代理请求，客户端会向代理服务器发送下面格式的请求</p>



<figure class="wp-block-table"><table><thead><tr><th>协议<strong>版本</strong></th><th>指令编号</th><th>保留字段</th><th>地址类型</th><th>目标地址</th><th>目标端口号</th></tr></thead><tbody><tr><td>1Byte</td><td>1Byte</td><td>1Byte</td><td>1Byte</td><td>长度可变</td><td>2Byte</td></tr></tbody></table><figcaption>请求格式</figcaption></figure>



<ul class="wp-block-list"><li>协议版本：对于 SOCKS5 协议来说这个字段永远是 5，代表 SOCKS 的第五版协议。</li><li>指令编号：包括 <code>CONNECT</code> 指令，用于 TCP 代理。<code>BIND</code> 指令，一般用于需要客户端主动接受来自服务器连接的时候。<code>UDP ASSOCIATE</code> 指令，用于 UDP 代理。</li><li>保留字段：必须为 0。</li><li>地址类型：包括 IPV4 地址，域名和 IPV6 地址。</li><li>目标地址：要访问的目标服务器的地址或域名。</li><li>目标端口号：于目标地址对应的端口号。</li></ul>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">代理服务器执行相应的请求后会返回一个或者多个信息，格式如下。</p>



<figure class="wp-block-table"><table><thead><tr><th>协议<strong>版本</strong></th><th>结果</th><th>保留字段</th><th>地址类型</th><th>绑定地址</th><th>绑定端口号</th></tr></thead><tbody><tr><td>1Byte</td><td>1Byte</td><td>1Byte</td><td>1Byte</td><td>长度可变</td><td>2Byte</td></tr></tbody></table><figcaption>响应格式</figcaption></figure>



<ul class="wp-block-list"><li>协议版本：对于 SOCKS5 协议来说这个字段永远是 5，代表 SOCKS 的第五版协议。</li><li>结果：包含请求的结果，比如成功、常规故障、网络不可达和超时等。</li><li>保留字段：必须为 0。</li><li>地址类型：包括 IPV4 地址，域名和 IPV6 地址。</li><li>绑定地址：即请求成功后客户端需要连接的代理服务器的地址或域名（有可能就是代理服务器本身，也可能被分配到别的代理服务器），客户端之后的通信均通过地址对应的服务器。</li><li>绑定端口号：绑定地址对应的端口号。</li></ul>



<p class="wp-block-paragraph">如果代理服务器返回成功消息，则后续客户端通过绑定地址和绑定端口号与代理服务器通信，由代理服务器转发客户端的请求到目标服务器，并将目标服务器的响应转发给客户端。</p>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">如果把上面的信息化成一张简单的交互流程图就是下面这样的。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="717" height="1096" src="https://www.addesp.com/wp-content/uploads/2020/11/01.webp" alt="" class="wp-image-2623"/><figcaption>SOCKS5 通信交互流程</figcaption></figure>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://tools.ietf.org/html/rfc1928" target="_blank" rel="noreferrer noopener">RFC 1928 &#8211; SOCKS Protocol Version 5</a></li></ul>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/2594/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>机械钟、石英钟和原子钟原理简介</title>
		<link>https://www.addesp.com/archives/2252</link>
					<comments>https://www.addesp.com/archives/2252#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Wed, 14 Oct 2020 11:08:48 +0000</pubDate>
				<category><![CDATA[科普]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=2252</guid>

					<description><![CDATA[2020 年 10 月 12 日部分网友称自己的手机的时间与北京时间相差较大，慢了几分钟到十几分钟不等，目前已经恢复正常。
有趣的是，「格林尼治国际标准时间」就是在 135 年前的 10 月 13 日诞生的，而今天又是「世界标准日」。那就来简单介绍一下机械钟、石英钟和原子钟原理吧。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">2020 年 10 月 12 日部分网友称自己的手机的时间与北京时间相差较大，慢了几分钟到十几分钟不等，目前已经恢复正常。</p>



<p class="wp-block-paragraph">相关新闻：<a href="https://pcedu.pconline.com.cn/1377/13770967.html" target="_blank" rel="noreferrer noopener">部分安卓手机时间变慢 北京移动：个别基站出了问题-太平洋电脑网</a></p>



<p class="wp-block-paragraph">有趣的是，「<a href="https://baike.baidu.com/item/%E6%A0%BC%E6%9E%97%E5%B0%BC%E6%B2%BB%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4" target="_blank" rel="noreferrer noopener">格林尼治国际标准时间</a>」就是在 135 年前的 10 月 13 日诞生的，而今天又是「<a href="https://baike.baidu.com/item/%E4%B8%96%E7%95%8C%E6%A0%87%E5%87%86%E6%97%A5" target="_blank" rel="noreferrer noopener">世界标准日</a>」。那就来简单介绍一下机械钟、石英钟和原子钟原理吧。</p>



<h2 class="wp-block-heading">机械钟</h2>



<p class="wp-block-paragraph">机械钟的背后是「<a href="https://baike.baidu.com/item/%E6%91%86%E7%9A%84%E7%AD%89%E6%97%B6%E6%80%A7%E5%8E%9F%E7%90%86" target="_blank" rel="noreferrer noopener">摆的等时性原理</a>」，简单来说就是对于一个摆，它的周期与重物的质量和数量无关。</p>


<p>$$<br />
T=2 \pi \sqrt{\frac{L}{g}}，L 是摆长，g 是重力加速度。<br />
$$</p>



<p class="wp-block-paragraph">经过简单的计算可以知道当 $L=1m$ 时 $T \approx 2s$，也就是说一个摆长为 1 米的单摆它的周期大概是 2 秒。机械钟的原理就是这个。</p>



<p class="wp-block-paragraph">摆钟是机械钟的一种，其中有一个结构叫做「<a href="https://baike.baidu.com/item/%E6%93%92%E7%BA%B5%E5%99%A8" target="_blank" rel="noreferrer noopener">擒纵器</a>」，它的工作过程大概是这样的。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="283" height="307" src="https://www.addesp.com/wp-content/uploads/2020/10/0.gif" alt="" class="wp-image-2269"/><figcaption>擒纵器工作原理</figcaption></figure>



<p class="wp-block-paragraph">当重物摆动到最左端的时候擒纵器会卡住齿轮的右端，摆动到最右端的时候擒纵器会卡住齿轮的左端，无论卡住哪一端都会让齿轮停止运动、如果摆长是 1 米，那么就是大约每秒就会卡住一次齿轮，只要齿轮的转速和半径合适，就可以通过齿轮和擒纵器制作出秒针，再通过齿轮传动制作出分针和时针，然后一台摆钟的核心部分就做完了。我们听到的嘀嗒嘀嗒声就是擒纵器卡住齿轮时发出的声音。</p>



<h2 class="wp-block-heading">石英钟</h2>



<p class="wp-block-paragraph">石英晶体本身会按照自身的「<a href="https://baike.baidu.com/item/%E5%9B%BA%E6%9C%89%E9%A2%91%E7%8E%87" target="_blank" rel="noreferrer noopener">固有频率</a>」进行震动，「<a href="https://baike.baidu.com/item/%E5%9B%BA%E6%9C%89%E9%A2%91%E7%8E%87" target="_blank" rel="noreferrer noopener">固有频率</a>」只与物体的本身属性有关，所以非常稳定，但是这个震动十分微弱，难以测量，那么我们就需要将这种震动放大。</p>



<p class="wp-block-paragraph">石英钟背后的原理是「<a href="https://baike.baidu.com/item/%E5%8E%8B%E7%94%B5%E6%95%88%E5%BA%94" target="_blank" rel="noreferrer noopener">压电效应</a>」，即某些电介质在发生形变的时候其表面会产生正电荷和负电荷，形变消失后正负电荷也会消失。当对其施加电场时，它也会发生形变，电场消失形变也随之消失。石英晶体就是具有「<a href="https://baike.baidu.com/item/%E5%8E%8B%E7%94%B5%E6%95%88%E5%BA%94" target="_blank" rel="noreferrer noopener">压电效应</a>」的电介质之一。</p>



<p class="wp-block-paragraph">有了「<a href="https://baike.baidu.com/item/%E5%8E%8B%E7%94%B5%E6%95%88%E5%BA%94" target="_blank" rel="noreferrer noopener">压电效应</a>」后，我们可以对石英晶体施加交流电，当交流电的频率与石英晶体的「<a href="https://baike.baidu.com/item/%E5%9B%BA%E6%9C%89%E9%A2%91%E7%8E%87" target="_blank" rel="noreferrer noopener">固有频率</a>」相近时元件其震动就会变强，也就更容易测量。然后我们通过电路去测量这种震动，然后通过转换将其变为 1 Hz 的方波，用这种脉冲去驱动电机来驱动指针旋转，再通过传动装置制作出分针和时针，石英钟的核心部分就完成了。</p>



<p class="wp-block-paragraph">石英钟的精度差不多 270 年才差 1 秒。</p>



<h2 class="wp-block-heading">原子钟</h2>



<p class="wp-block-paragraph">原子是有能级的，通常都是在最低能级，处于最低能级的原子称为基态原子。如果一束光打到原子上，只要光的频率合适，就可以让原子跃迁到更高的能级，称为激发态原子。光消失后原子会自发地跃迁到最低能级，也就是回到基态，这个过程会释放光，这个光的能量与入射的光的能量是一样的。下文称能让铯原子跃迁激发态的光的频率为目标频率。</p>



<p class="wp-block-paragraph">我们难以精确稳定地发出目标频率的光，但是我们可以使得光的频率稳定地在目标频率的周围变化。我们就用这种光打在铯原子团上，光的频率越接近目标频率，处于激发态的铯原子就会越多。然后我们就可以通过测量激发态原子的多少来确定当前的光的频率有多接近目标频率。</p>



<p class="wp-block-paragraph">测量方式时检测激发态铯原子跃迁到基态时释放的光的强度。因为激发态的铯原子越多，回到激发态释放的光的铯原子也就越多，测量到的光强也就越大，我们会在横轴为照射频率，纵轴为光强的坐标系上看见看见峰值，这个峰值对应的光的频率就是最接近目标频率的光，下文称这个频率为 $f_0$</p>



<p class="wp-block-paragraph">由于照射的频率在不断变化，新的峰值也会不断出现，这可以让我们不断地去矫正 $f_0$，$f_0$ 也会越来越接近目标频率。</p>



<p class="wp-block-paragraph">然后我们将 $f_0$ 频率的光处理为电信号，通过电路去记录震荡次数，由于铯原子的振动频率时已知的，即 9192631770Hz，所以电路每振荡 9192631770 次就过了一秒。这种装置称为铯原子喷泉原子钟（Cesium Fountain Atomic Clock）。</p>



<p class="wp-block-paragraph">原子钟的精度十分高。2015 年 NASA 的 Deep Space Atomic Clock，可以做到 100 亿年的误差不超过 1 秒。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/2252/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>IA-32 架构的纯软件虚拟化解决方案简介</title>
		<link>https://www.addesp.com/archives/1891</link>
					<comments>https://www.addesp.com/archives/1891#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Fri, 02 Oct 2020 17:26:25 +0000</pubDate>
				<category><![CDATA[虚拟化技术]]></category>
		<category><![CDATA[虚拟化]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1891</guid>

					<description><![CDATA[IA-32 架构起初并不是为了虚拟化设计的，它的目标是在处理器上运行最多一个操作系统，所以在 Intel VT 出现之前， IA-32 架构上的虚拟化解决方案就只有纯软件虚拟化了。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">本文章属于虚拟化技术系列文章，<strong><a href="https://www.addesp.com/shuoshuo/%e3%80%8c%e8%99%9a%e6%8b%9f%e5%8c%96%e6%8a%80%e6%9c%af%e3%80%8d%e7%b3%bb%e5%88%97%e5%bc%80%e5%9d%91" target="_blank" rel="noreferrer noopener">点击查看本系列的说明</a></strong>。</p>



<p class="wp-block-paragraph">阅读本文请先阅读下面的<a href="/archives/1877" target="_blank" rel="noreferrer noopener" title="/archives/1877">虚拟化技术的术语</a>以了解相关术语。</p>



<h2 class="wp-block-heading">IA-32 架构面临的挑战</h2>



<p class="wp-block-paragraph">IA-32 架构起初并不是为了虚拟化设计的，它的目标是在处理器上运行最多一个操作系统，所以在 <a href="https://baike.baidu.com/item/Intel%20VT/2091588" target="_blank" rel="noreferrer noopener">Intel VT</a> 出现之前， IA-32 架构上的虚拟化解决方案就只有纯软件虚拟化了。</p>



<h3 class="wp-block-heading">IA-32 架构的权限等级</h3>



<p class="wp-block-paragraph">Intel 处理器提供了四种权限级别，分别为 Ring0、Ring1、Ring2 和 Ring3，权限依次降低，Ring0 拥有最高权限。所以一些特权指令只能在 Ring0 下才可以正常使用。大多数 IA-32 架构的应用运行在 Ring3。</p>



<p class="wp-block-paragraph">然而一些操作系统的组件必须运行在 Ring0，但是要实现纯软件的虚拟化就必须让 VMM 独占 Ring0。因为 VMM 要虚拟化出若干组硬件来供 GuestOS 使用，这就要保证 GuestOS 不能直接控制物理硬件。</p>



<h3 class="wp-block-heading">特权解除和陷入模拟</h3>



<p class="wp-block-paragraph">因此出现了一种技术被称为“特权解除和陷入模拟”。</p>



<p class="wp-block-paragraph">特权解除是指将 VMM 运行在 Ring0，而虚拟机中的操作系统运行在 Ring1。这样就可以保证 GuestOS 无法越过 VMM 去执行特权指令导致虚拟化失效。</p>



<p class="wp-block-paragraph">同时由于 IA-32 架构下运行特权指令的时候如果权限不足会自动陷入权限更高的 Ring 中。所以当 GuestOS 执行特权指令的时候会陷入 Ring0，此时由 VMM 接管并模拟特权指令的执行，这就叫陷入模拟。</p>



<p class="wp-block-paragraph">不幸的是整个过程没有看起来那么简单。究其根本是因为虚拟机中的软件并不是为了虚拟化而编写的。</p>



<p class="wp-block-paragraph">有的指令在非 Ring0 下运行会返回一个包含当前 Ring 值的返回值，GuestOS 会去读取这个值，一旦发现自己没有运行在 Ring0 下就会出现错误。这也就导致了这类指令的调用无法被 VMM 检测到，自然也就无法模拟了。</p>



<p class="wp-block-paragraph">另一个问题是操作系统在编写的时候并没有支持操作系统级的上下文切换，所以一些上下文可能会在很隐蔽的角落。当 VMM 切换上下文的时候这些信息就会丢失导致错误。</p>



<p class="wp-block-paragraph">并非只有这些问题，受限于篇幅问题就不再多说了，读者有兴趣可以自行查阅资料。</p>



<h2 class="wp-block-heading">纯软件解决方案</h2>



<p class="wp-block-paragraph">为了应对这些挑战，设计人员给出了两种软件层面的途径去开发 VMM，分别是半虚拟化（Paravirtualization）和二进制翻译（Binary Translation）。</p>



<h4 class="wp-block-heading">半虚拟化（Paravirtualization）</h4>



<p class="wp-block-paragraph">这种方法需要修改 GuestOS 的源代码使得其可以在特定的 VMM 下运行。比如敏感指令替换为 hypercall。hypercall 直译为超级调用，可以陷入到 VMM 中以便 VMM 去模拟指令的运行。</p>



<p class="wp-block-paragraph">这种方式的好处是基本没有性能损失。但是坏处也很明显，它只能用于开源的操作系统，比如 Linux。</p>



<h4 class="wp-block-heading">二进制翻译（Binary Translation）</h4>



<p class="wp-block-paragraph">这个方法是将 GuestOS 的二进制指令翻译为另一种类指令，通常是翻译为 vCPU 可以识别的指令。它的特殊之处在于大多数情况下我们都是将源代码或者 IR（Intermediate Representation） 翻译为二进制代码，但是二进制翻译是将二进制代码翻译为另一类代码。按照翻译的时机分类又分为静态翻译和动态翻译。</p>



<h5 class="wp-block-heading">静态翻译（Static Binary Translation）</h5>



<p class="wp-block-paragraph">即在运行 GuestOS 前一次性地将全部的代码翻译为目标指令。这样做可以最大限度地提高性能，但是这个方法实际上并不可行。</p>



<p class="wp-block-paragraph">最大的难题是冯·诺依曼体系结构下指令和数据存储在一个存储器上，并未区分指令存储器和数据存储器。也就是说对于一堆静态的二进制数据，我们无法确定它到底是代码还是数据。这也就导致静态翻译可能错误地将数据翻译为代码从而导致错误。</p>



<p class="wp-block-paragraph">还有一个问题是有些程序会在运行时修改自己，而静态翻译是一次性的，这也会导致错误。</p>



<h5 class="wp-block-heading">动态翻译（Dynamic Binary Translation）</h5>



<p class="wp-block-paragraph">动态翻译就是在运行时以一种类似按需翻译的方式去翻译 GuestOS 的二进制指令。这可以很好地解决静态翻译带来的问题。</p>



<p class="wp-block-paragraph">因为只会翻译被执行的部分，所以不会错误地将数据翻译为二进制代码。同时由于翻译并不是一次性地工作，也可以解决程序修改自身所带来的问题。</p>



<p class="wp-block-paragraph">不过实际实现起来会有很多问题，比如何时启动翻译、翻译什么和翻译多少。这也是本系列的后续任务，将会简单地介绍一些实现动态翻译中遇到的问题和解决方法。本文在这一点不做过多讨论。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://software.intel.com/sites/default/files/m/d/4/1/d/8/An_Introduction_to_Virtualization.pdf" target="_blank" rel="noreferrer noopener">An Introduction to Virtualization</a></li><li><a href="http://rts.lab.asu.edu/web_438/project_final/CSE_598_Binary_Translation_Report.pdf" target="_blank" rel="noreferrer noopener">CSE 598 Binary Translation Report</a></li><li><a href="http://www.complang.tuwien.ac.at/schani/papers/bintrans.pdf" target="_blank" rel="noreferrer noopener">Dynamic Binary Translation</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1891/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>虚拟化技术的术语</title>
		<link>https://www.addesp.com/archives/1877</link>
					<comments>https://www.addesp.com/archives/1877#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Fri, 02 Oct 2020 17:17:08 +0000</pubDate>
				<category><![CDATA[虚拟化技术]]></category>
		<category><![CDATA[虚拟化]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1877</guid>

					<description><![CDATA[介绍虚拟化技术中的一些常见术语。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">本文章属于虚拟化技术系列文章，<strong><a href="https://www.addesp.com/shuoshuo/%e3%80%8c%e8%99%9a%e6%8b%9f%e5%8c%96%e6%8a%80%e6%9c%af%e3%80%8d%e7%b3%bb%e5%88%97%e5%bc%80%e5%9d%91" target="_blank" rel="noreferrer noopener">点击查看本系列的说明</a></strong>。</p>



<h2 class="wp-block-heading">宿主机（Host Machine）</h2>



<p class="wp-block-paragraph">宿主机是运行虚拟机的物理机，是真实地存在于物理世界的机器。它包含如 CPU，内存和网卡等物理资源。</p>



<h2 class="wp-block-heading">虚拟机（Virtual Machine）</h2>



<p class="wp-block-paragraph">虚拟机是对物理机虚拟化后的结果，并由虚拟化软件运行和维护。</p>



<h2 class="wp-block-heading">客机操作系统（<strong>GuestOS</strong>: Guest Operating System）</h2>



<p class="wp-block-paragraph">指运行在虚拟机中的操作系统。</p>



<h2 class="wp-block-heading">虚拟机管理程序/虚拟机监视器（Hypervisor/VMM）</h2>



<p class="wp-block-paragraph">实际上就是管理虚拟机和实现虚拟化的程序。在比较老的资料里这两个名词可能是不同的概念，即 Hypervisor 是直接运行在硬件上的，而 VMM 是运行在软件上的，但貌似目前这两个名词已经差不多一样了，不需要去可以区分了。</p>



<h2 class="wp-block-heading">半虚拟化（Paravirtualization）</h2>



<p class="wp-block-paragraph">通过修改操作系统的源代码实现的虚拟化，比如将一些敏感指令（可能导致虚拟化出错的指令）修改为 hypercall 使 VMM 可以模拟这类执行的执行以避免错误。</p>



<h2 class="wp-block-heading">全虚拟化（Full virtualization）</h2>



<p class="wp-block-paragraph">即不修改操作系统的源代码就可以实现的虚拟化。</p>



<h2 class="wp-block-heading">虚拟 CPU（vCPU）</h2>



<p class="wp-block-paragraph">指 VMM 虚拟出来的供虚拟机使用的 CPU，并不存在于物理世界中。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://software.intel.com/sites/default/files/m/d/4/1/d/8/An_Introduction_to_Virtualization.pdf" target="_blank" rel="noreferrer noopener">An Introduction to Virtualization</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1877/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>虚拟化技术的发展史</title>
		<link>https://www.addesp.com/archives/1869</link>
					<comments>https://www.addesp.com/archives/1869#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 01 Oct 2020 16:58:30 +0000</pubDate>
				<category><![CDATA[虚拟化技术]]></category>
		<category><![CDATA[虚拟化]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1869</guid>

					<description><![CDATA[在我们了解如今成熟稳定的虚拟化技术之前，有必要去了解一下它的发展史。因为虚拟化并不是一项全新的技术，而是基于之前的努力。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">本文章属于虚拟化技术系列文章，<strong><a href="https://www.addesp.com/shuoshuo/%e3%80%8c%e8%99%9a%e6%8b%9f%e5%8c%96%e6%8a%80%e6%9c%af%e3%80%8d%e7%b3%bb%e5%88%97%e5%bc%80%e5%9d%91" target="_blank" rel="noreferrer noopener">点击查看本系列的说明</a></strong>。</p>



<p class="wp-block-paragraph">在我们了解如今成熟稳定的虚拟化技术之前，有必要去了解一下它的发展史。因为虚拟化并不是一项全新的技术，而是基于之前的努力。</p>



<h2 class="wp-block-heading">1950年 &#8211; 1990年</h2>



<p class="wp-block-paragraph">虚拟存储器的概念可以追溯到 20 世纪 50 年代末，当时曼彻斯特大学的一个小组在 Atlas 系统（一种晶体管计算机）中引入了自动换页功能，其原理是将分页作为一种在内存层次结构之间存储和传输数据的方法。虽然这个原理之前就有，但Atlas是第一个将这一过程自动化，从而提供了第一个虚拟存储器的工作原型。</p>



<p class="wp-block-paragraph">虚拟机这个词可以追溯到20世纪60年代，最早的虚拟机系统来自 IBM。1967 年前后，IBM 推出了型号为 67 的 System/360，它是第一个带有虚拟存储器的系统。这个系统中有一个概念名为处理器虚拟化指令集，这个概念也在也在后续的型号中得到完善。起初这个系统被称为 CP-67，之后就演化成了虚拟机系统。虚拟机允许在一台处理机上运行多个操作系统。本质上来说虚拟机和硬件通过合作来启动多个操作系统，让它们可以受保护地访问完整的指令集。</p>



<p class="wp-block-paragraph">20 世纪 60 年代中期，IBM 还率先推出了 M44/44X 项目，探索新兴的分时概念。系统架构的核心是一组虚拟机，每个用户可以使用其中的一个。宿主机是 IBM 7044（简称 M44），每个虚拟机是 7044 的实验镜像（简称44X）。这项工作最终促成了虚拟机/<a href="https://baike.baidu.com/item/%E5%88%86%E6%97%B6%E7%B3%BB%E7%BB%9F" target="_blank" rel="noreferrer noopener">分时系统</a>，包括 IBM 著名的 VM/370。</p>



<p class="wp-block-paragraph">硬件虚拟化的概念也是在这个时期出现的，VMM 可以在一个隔离和保护的环境中运行虚拟机。因为 VMM 对运行在虚拟机中的软件来说是透明的，软件认为它独占控制权。随着时间的推移，这一概念得到了完善，最终 VMM 只会消耗较少的性能和资源。</p>



<p class="wp-block-paragraph">20 世纪 70 年代中期，虚拟化技术已经在各个操作系统被用户所接受。在这几十年中，虚拟化技术解决了许多重要问题，例如应用还在大规模操作系统中虚拟存储器使得应用程序的编程变得简单，因为程序员不需要像以前那么关心内存不足的问题。</p>



<p class="wp-block-paragraph">虚拟出来资源是很便宜的，但是真实的物理资源却很贵，虚拟资源可以让我们按需分配这些昂贵的物理资源以获取最大的利益。比如现在厂商可以将多余的算力虚拟化成一个个的云服务器出售给客户。</p>



<p class="wp-block-paragraph">虽然在 20 世纪 60 年代和 70 年代，硬件级虚拟机在研究和商业市场上都很流行，但在 20 世纪 80 年代和 90 年代，它们基本上消失了。因为低成本的微型计算机和 PC 进入市场导致了虚拟化的需求在总体上有所下降。</p>



<p class="wp-block-paragraph">顺便一提，另一种类型的虚拟机在历史上仍有一席之地，典型的代表就是 <a href="https://baike.baidu.com/item/JVM" target="_blank" rel="noreferrer noopener">JVM</a> 和 微软的 <a href="https://baike.baidu.com/item/%E5%85%AC%E5%85%B1%E8%AF%AD%E8%A8%80%E8%BF%90%E8%A1%8C%E5%BA%93/2882128" target="_blank" rel="noreferrer noopener">CLR</a>。这一类型的虚拟机不是本文所要介绍的虚拟机，它们并不虚拟化出一个硬件平台，而是为编程语言提供可移植性。所以请不要混淆概念。</p>



<figure class="wp-block-image size-large is-resized is-style-default"><img loading="lazy" decoding="async" src="https://www.addesp.com/wp-content/uploads/2020/10/2.webp" alt="" class="wp-image-1887" width="466" height="334"/><figcaption>Runtime Virtual Machines</figcaption></figure>



<h2 class="wp-block-heading">虚拟化的重新兴起</h2>



<p class="wp-block-paragraph">20 世纪 90 年代，企业部署的服务器数量越来越多，但是服务器资源的利用效率却越来越低。</p>



<p class="wp-block-paragraph">一个服务器仅仅部署一个应用会大量浪费服务器的硬件资源。一个服务器部署多个应用则会遇到更为严重的问题，比如其中一个应用崩溃了可能会带着整个服务器崩溃，那么这台服务器上所有的应用都会停止。又比如某个应用更新后需要重启服务器，但是这台服务器上还有别的应用，所以其它应用也被迫重启。</p>



<p class="wp-block-paragraph">服务器数量的激增带来了部署、更新和支持方面的挑战，以及安全和灾难恢复方面的问题。</p>



<p class="wp-block-paragraph">虚拟化技术很好地解决这一问题。可以在一台宿主机上虚拟出多个虚拟机，每个虚拟机仅部署一个应用。由于每个虚拟机时互相隔离的，所以崩溃一个其它的依然可以正常运行，上面的问题也就迎刃而解了。</p>



<p class="wp-block-paragraph">虽然虚拟化技术重新兴起，但是开发用于虚拟化的操作系统和软件是一件十分复杂的事情。<a href="https://baike.baidu.com/item/Intel%20VT/2091588" target="_blank" rel="noreferrer noopener">Intel VT</a> 的出现消除或大大减少了其中的一些复杂因素。Intel 开始与 VMM 的开发者合作，在 Intel 处理器和芯片组中实现硬件辅助虚拟化，并推动相关规范的制定以促进虚拟化在未来的发展。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://software.intel.com/sites/default/files/m/d/4/1/d/8/An_Introduction_to_Virtualization.pdf" target="_blank" rel="noreferrer noopener">An Introduction to Virtualization</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1869/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>校园网保证一机一号和检测共享上网的原理</title>
		<link>https://www.addesp.com/archives/1769</link>
					<comments>https://www.addesp.com/archives/1769#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 01 Oct 2020 07:02:08 +0000</pubDate>
				<category><![CDATA[科普]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1769</guid>

					<description><![CDATA[出于利益等原因，许多校园网采取一机一号的管理方式。即一个宽带账号只能由一台规定的设备使用。同时检测共享上网以避免使用路由器或类似方式实现共享。那么这些限制背后的原理是什么呢？]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">出于利益等原因，许多校园网采取一机一号的管理方式。即一个宽带账号只能由一台规定的设备使用。同时检测共享上网以避免使用路由器或类似方式实现共享。那么这些限制背后的原理是什么呢？</p>



<h2 class="wp-block-heading">一机一号的原理</h2>



<p class="wp-block-paragraph">目前大多数校园网的拨号协议依然是 <a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/PPPOE"></a><a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener">PPPoE</a>，所以要知道一机一号的原理就要先粗略了解一下 <a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener"></a><a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener">PPPoE</a> 。</p>



<p class="wp-block-paragraph"><a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener">PPPoE</a> 的全称是（Point-to-Point Protocol Over Ethernet），即以太网上的点对点协议。以太网本身不具有身份验证功能 但是 <a href="https://baike.baidu.com/item/PPP/6660214" target="_blank" rel="noreferrer noopener">PPP</a> 可以，所以将 <a href="https://baike.baidu.com/item/PPP/6660214" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/PPP/6660214">PPP</a> 引入以太网可以满足 ISP 对用户身份的验证需求。</p>



<p class="wp-block-paragraph"><a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener">PPPoE</a> 运行在 OSI 的数据链路层，TCP/IP 体系结构的网络接口层，同时由于它只在拨号时起作用，所以此时没有 IP 地址起作用，由此可以推测如果要实现一机一号，关键的信息之一是 MAC 地址。事实上也确实如此。</p>



<p class="wp-block-paragraph"><a href="https://baike.baidu.com/item/PPPOE" target="_blank" rel="noreferrer noopener">PPPoE</a> 拨号时会在「发现」阶段向以太网中发送广播，表示自己想要找到一个可以提供网络服务的节点，并在广播中说明自己的 MAC 地址。ISP 的节点接收到消息后会回应这个广播并说明自己的 MAC 地址，然后两个节点就建立了会话。此后便使用 <a href="https://baike.baidu.com/item/PPP/6660214" target="_blank" rel="noreferrer noopener">PPP</a> 通信，通信过程中就包含了身份认证过程。</p>



<p class="wp-block-paragraph">注意到此时 ISP 的节点只有拨号设备的 MAC 地址和宽带的账号和密码。看来答案已经出现了。ISP 通过维护宽带账号和 MAC 地址的一一对应的关系来实现一机一号。只要发现 MAC 地址和宽带账号不匹配直接报错就行了。</p>



<h2 class="wp-block-heading">共享上网检测的原理</h2>



<p class="wp-block-paragraph">仅仅一机一号还不够，因为可以通过路由器实现多人共用同一个宽带。路由器通过 <a href="https://baike.baidu.com/item/nat/320024" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/nat/320024">NAT</a> 让多个内网 IP 共用一个 IP 与 ISP 节点通信，这给共享上网的检测带来的困难，但依然有一些方法。</p>



<h3 class="wp-block-heading">检测 IP 报文头 <code>identification</code> 字段</h3>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="545" src="https://www.addesp.com/wp-content/uploads/2020/10/0-1024x545.png" alt="" class="wp-image-1788" srcset="https://www.addesp.com/wp-content/uploads/2020/10/0-1024x545.png 1024w, https://www.addesp.com/wp-content/uploads/2020/10/0-300x160.png 300w, https://www.addesp.com/wp-content/uploads/2020/10/0-768x409.png 768w, https://www.addesp.com/wp-content/uploads/2020/10/0.png 1271w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">上图就是 IP 报文头的结构，第二行的左边就是需要被检测的字段，下面是该字段的说明。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>An identifying value assigned by the sender to aid in assembling the fragments of a datagram.</p><cite><a href="https://tools.ietf.org/html/rfc791" target="_blank" rel="noreferrer noopener" title="https://tools.ietf.org/html/rfc791">RFC-791</a></cite></blockquote>



<p class="wp-block-paragraph">看起来似乎不能用来检测共享上网？单单这样看确实不能，但是 Windows 网络协议栈在实现的时候这个值是随着 IP 报文的发送数量的增加而增加的，且初始值是一个随机数。所以对于使用 Windows 的设备来说，它们的 <code>identification</code> 的初始值通常是差距很大的，所以可以用来检测共享上网。</p>



<p class="wp-block-paragraph">值得一提的是这并不适用于 Linux、Unix，因为它们的 <code>identification</code> 的值是完全随机的，没有规律可循。</p>



<h3 class="wp-block-heading">TTL 检测</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>TTL是 Time To Live的缩写，该字段指定IP包被路由器丢弃之前允许通过的最大网段数量。</p><cite><a href="https://baike.baidu.com/item/TTL/130248" target="_blank" rel="noreferrer noopener">TTL（生存时间值）_百度百科</a></cite></blockquote>



<p class="wp-block-paragraph">不同的操作系统的 TTL 可能是不同的，所以如果检测到两个不同的 TTL 则可以断定网络中有两台设备。</p>



<h3 class="wp-block-heading">时间戳检测</h3>



<p class="wp-block-paragraph">不同物理机的<a href="https://baike.baidu.com/item/%E6%97%B6%E9%92%9F%E5%81%8F%E7%A7%BB/9106491" target="_blank" rel="noreferrer noopener">时钟偏移</a>通常是不同的，这也就使得物理时钟会与协议栈时钟之间存在一种通常不同于其它设备的特殊的联系。同时主机发送报文的频率与时钟又存在统计上的关系，所以可以通过这一点来检测出又几种不同的时钟偏移从而检测网络中的设备数量。</p>



<h3 class="wp-block-heading">深度报文检测（DPI）</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>DPI（Deep Packet Inspection）是一种基于数据包的深度检测技术，针对不同的网络应用层载荷（例如HTTP、DNS等）进行深度检测，通过对报文的有效载荷检测决定其合法性。</p><cite><a href="https://baike.baidu.com/item/DPI/19506006" target="_blank" rel="noreferrer noopener">DPI（深度报文检测）_百度百科</a></cite></blockquote>



<p class="wp-block-paragraph">主要通过检测应用层的报文来判断设备数量。</p>



<p class="wp-block-paragraph">比如通过检测 <code>Http.UserAgent</code> 字段来判断。一个用户使用的浏览器通常是固定的，而浏览器发送请求时所使用的 <code>UserAgent</code> 也是相对固定的。在排除一个用户设备上的许多客户端应用也使用 HTTP 协议的情况下，如果一个网络中的 <code>Http.UserAgent</code> 有许多不同的值，则可以怀疑这个网络中有许多不同的设备在上网。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://tools.ietf.org/html/rfc2516" target="_blank" rel="noreferrer noopener">RFC 2516 &#8211; A Method for Transmitting PPP Over Ethernet (PPPoE)</a></li><li><a href="https://tools.ietf.org/html/rfc791" target="_blank" rel="noreferrer noopener">RFC 791 &#8211; Internet Protocol</a></li><li><a href="https://www.zhihu.com/question/23009793/answer/200277130" target="_blank" rel="noreferrer noopener" title="https://www.zhihu.com/question/23009793/answer/200277130">电信是怎样查出多台电脑共享上网的？ &#8211; 稍有常识的人的回答 &#8211; 知乎</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1769/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>C 语言中 a[i][j] 的地址是 *(a + i) + j 么？</title>
		<link>https://www.addesp.com/archives/1566</link>
					<comments>https://www.addesp.com/archives/1566#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Fri, 25 Sep 2020 17:55:37 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[C语言]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1566</guid>

					<description><![CDATA[一个因自以为懂得多而犯的错。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">问题</h2>



<p class="wp-block-paragraph">昨晚有人问了我一个问题——下面的代码输出的两行内容相同么？</p>



<pre class="wp-block-code language-c"><code>int main() {
    int a&#91;5]&#91;3];
    printf("%p\n", &amp;a&#91;3]&#91;2]);
    printf("%p", *(a + 3) + 2);
}</code></pre>



<h2 class="wp-block-heading">我的错误答案</h2>



<p class="wp-block-paragraph">我的答案是不一样，因为我认为 <code>*(a + 3)</code> 是一个值，所以一个值加 2 还是一个值，而 <code>&amp;a[3][2]</code> 明显是一个地址，所以不一样。</p>



<p class="wp-block-paragraph">我的理由是，虽然逻辑上 <code>a</code> 是一个二维数组，但是物理上（内存模型）依然是一段连续的内存，且 <code>a</code> 指向内存的首地址，也就是说我认为 <code>int* a = 数组首地址;</code> 。那么对于一个一级指针解引用自然就会得到一个值了。</p>



<p class="wp-block-paragraph">但是结果是我错了，代码所输出的两行内容完全相同。</p>



<h2 class="wp-block-heading">分析</h2>



<p class="wp-block-paragraph">当时我整个人都傻了，感觉对不起 C 语言老师。那么现在的结论是 <code>&amp;a[3][2] == *(a + 3) + 2</code>，要如何解释这个现象呢？没有别的好办法，只能去看汇编了。</p>



<p class="wp-block-paragraph">代码文件 main.c</p>



<pre class="wp-block-code language-c"><code>#include &lt;stdio.h&gt;

int main() {
    int a&#91;5]&#91;3];
    printf("%p\n", a);
    printf("%p", *(a + 1));
}</code></pre>



<p class="wp-block-paragraph">使用 gcc 编译</p>



<pre class="wp-block-code language-shell"><code>gcc -m32 -S main.c</code></pre>



<p class="wp-block-paragraph">得到下面的汇编代码</p>



<pre class="wp-block-preformatted">	.file	"main.c"
	.text
	.section	.rodata
.LC0:
	.string	"%p\n"
.LC1:
	.string	"%p"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	...
	; eax = 二维数组 a 的首地址
	leal	-72(%ebp), %eax
	; 将 eax 的内容作为参数传递给 printf
	pushl	%eax
	; eax = 字符串 "%p\n" 的首地址
	leal	.LC0@GOTOFF(%ebx), %eax
	; 将 eax 的内容作为参数传递给 printf
	pushl	%eax
	; 调用 printf
	call	printf@PLT
	addl	$16, %esp
	; eax = 二维数组 a 的首地址
	leal	-72(%ebp), %eax
	; eax += 12
	; 12 Byte = 3 * 4 Byte
	; int 的大小为 4 Byte
	addl	$12, %eax
	subl	$8, %esp
	; 将 eax 的内容作为参数传递给 printf
	pushl	%eax
	; eax = 字符串 "%p" 的首地址
	leal	.LC1@GOTOFF(%ebx), %eax
	; 将 eax 的内容作为参数传递给 printf
	pushl	%eax
	; 调用 printf
	call	printf@PLT
	...</pre>



<p class="wp-block-paragraph">可以看到对于二维数组 <code>a</code>，执行 <code>a + 1</code> 时编译器会自动地帮我们计算出第 1 行的首地址，而不是像普通的一级指针仅仅移动一个单位而已。</p>



<p class="wp-block-paragraph">后续测试发现将 <code>a + 1</code> 改为 <code>*(a + 1)</code> 后汇编代码没有发生改动。所以对于二维数组 <code>a</code> 来说，<code>a + 1</code> 等价于 <code>*(a + 1)</code>。</p>



<p class="wp-block-paragraph">所以就不难理解为何 <code>&amp;a[3][2] == *(a + 3) + 2</code> 了。</p>



<p class="wp-block-paragraph">为什么编译器会做这种处理呢？首先确定这是不是一个<a href="https://baike.baidu.com/item/%E6%9C%AA%E5%AE%9A%E4%B9%89%E8%A1%8C%E4%B8%BA/10229482" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/%E6%9C%AA%E5%AE%9A%E4%B9%89%E8%A1%8C%E4%B8%BA/10229482">未定义行为</a>。我在 C99 的标准文档中找到了这些内容</p>



<pre class="wp-block-preformatted">6.5.2.1 Array subscripting
Constraints
1 One of the expressions shall have type ‘‘pointer to object type’’,
  the other expression shall
  have integer type, and the result has type ‘‘type’’.
  Semantics
2 A postfix expression followed by an expression in square brackets [] is a subscripted
  designation of an element of an array object. The definition of the subscript operator []
  is that E1[E2] is identical to (*((E1)+(E2))). Because of the conversion rules that
  apply to the binary + operator, if E1 is an array object (equivalently, a pointer to the
  initial element of an array object) and E2 is an integer, E1[E2] designates the E2-th
  element of E1 (counting from zero).
3 Successive subscript operators designate an element of a multidimensional array object.
  If E is an n-dimensional array (n ≥ 2) with dimensions i × j × . . . × k, then E (used as
  other than an lvalue) is converted to a pointer to an (n − 1)-dimensional array with
  dimensions j × . . . × k. If the unary * operator is applied to this pointer explicitly, or
  implicitly as a result of subscripting, the result is the pointed-to (n − 1)-dimensional array,
  which itself is converted into a pointer if used as other than an lvalue. It follows from this
  that arrays are stored in row-major order (last subscript varies fastest).
4 EXAMPLE Consider the array object defined by the declaration
         int x[3][5];
  Here x is a 3 × 5 array of ints; more precisely, x is an array of three element objects, each of which is an
  array of five ints. In the expression x[i], which is equivalent to (*((x)+(i))), x is first converted to
  a pointer to the initial array of five ints. Then i is adjusted according to the type of x, which conceptually
  entails multiplying i by the size of the object to which the pointer points, namely an array of five int
  objects. The results are added and indirection is applied to yield an array of five ints. When used in the
  expression x[i][j], that array is in turn converted to a pointer to the first of the ints, so x[i][j]
  yields an int.</pre>



<p class="wp-block-paragraph">看起来人家早就考虑到这个问题了并将计算规则写入了标准。</p>



<p class="wp-block-paragraph">既然这不是一个<a href="https://baike.baidu.com/item/%E6%9C%AA%E5%AE%9A%E4%B9%89%E8%A1%8C%E4%B8%BA/10229482" target="_blank" rel="noreferrer noopener">未定义行为</a>，那么为什么 C 标准协会会如此规定呢？不知道，不过我猜大概是因为  C 语言还算是一个高级语言，还是要屏蔽一些底层的东西的。如果你有不同的看法希望可以在评论区留言一同讨论。</p>



<h2 class="wp-block-heading">最后一段</h2>



<p class="wp-block-paragraph">起初我得出错误答案的时候还是满脸自信的，告诉他这个绝对不会错，因为内存模型就是一维数组，看成一个一级指针就行了，结果是自己做实验去否定自己。果然还是自以为懂得多啊。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1566/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>谈谈跨服聊天</title>
		<link>https://www.addesp.com/archives/1086</link>
					<comments>https://www.addesp.com/archives/1086#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Wed, 16 Sep 2020 18:09:05 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=1086</guid>

					<description><![CDATA[从合作项目到重新审视《软件工程》]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p><strong>跨服聊天</strong>是指交谈双方，不在同一语境下进行交流。多因双方的兴趣爱好/知识差异或聊天内容本身容易引发歧义引起。</p><p>双方使用语言的环境不同，对语句的理解也不同。同一句话，在不同的主题会产生不同的理解。以「你幸福么？」为例：我姓曾。</p><cite><a href="https://zh.moegirl.org.cn/%E8%B7%A8%E6%9C%8D%E8%81%8A%E5%A4%A9" target="_blank" rel="noreferrer noopener" title="https://zh.moegirl.org.cn/%E8%B7%A8%E6%9C%8D%E8%81%8A%E5%A4%A9">跨服聊天 &#8211; 萌娘百科</a></cite></blockquote>



<p class="wp-block-paragraph">跨服聊天通常会出现在作品中用来提供笑料。如此的话，直觉上跨服聊天应该离我们很远。真的么？以前我这么认为，但是现在我觉得离我很近。</p>



<h2 class="wp-block-heading">一件趣事</h2>



<p class="wp-block-paragraph">作为一个写代码的秃头人士，合作项目是很正常的事情，不过我的经历大都是和身边的人合作，双方都比较了解，有了问题直接面谈，基本不会出现跨服聊天这种情况。</p>



<p class="wp-block-paragraph">今年我尝试与<a href="https://www.yunyoujun.cn/" target="_blank" rel="noreferrer noopener" title="https://www.yunyoujun.cn/">云游君</a>合作一个项目，大概是由于从未谋面，互相不了解，涉足的领域差距比较大，导致跨服聊天变成了家常便饭，很多时候商量了半天的事情，后面会发现讨论的根本不是一个东西。</p>



<p class="wp-block-paragraph">我们在整体架构上出现了分歧。出现分歧很正常，沟通么，交换一定量的信息后通常就可以达成一致了。但是因为跨服聊天的原因，沟通效率十分低下（相对于和身边人的沟通），这段时间大概一周左右，后来因为所使用的库作者表示要进行大的改动而暂停开发，现在这个项目已经咕掉了。</p>



<p class="wp-block-paragraph">虽然是因为外部原因暂停的开发，但是如果按照当时的沟通效率做下去的话，恐怕最终也搞不出什么好的项目来，能跑起来就是神迹了。</p>



<h2 class="wp-block-heading">抢救过程</h2>



<p class="wp-block-paragraph">讨论过程中常见的现象是说着说着出现了分歧，然后沟通讨论，发现没有达成共识的机会，最后发现双方虽然说的是同一个功能，同一个模块，但是细节上却不同。分析后我认为是双方对需求的理解不一致。</p>



<p class="wp-block-paragraph">因此我提出使用<a href="https://baike.baidu.com/item/%E6%95%B0%E6%8D%AE%E6%B5%81%E5%9B%BE" target="_blank" rel="noreferrer noopener">数据流图</a>来协助讨论，<a href="https://www.yunyoujun.cn/" target="_blank" rel="noreferrer noopener">云游君</a>也同意了。于是我们合作完成了一张数据流图，期间又发现了许多分歧，但是都解决了。</p>



<p class="wp-block-paragraph">但是你以为这就完了么？可怕的事情还在后面。没多久我们就开始了初期的编码，结果还是出现了很大分析，期间又是大型跨服聊天现场。在我们还在想怎么提高交流效率的时候，我们所使用的库的作者表示要大改，我们就顺势暂停了开发，然后因为一些原因，这个项目就彻底停下来了。</p>



<h2 class="wp-block-heading">从《软件工程》的角度分析</h2>



<p class="wp-block-paragraph">接下来复习一下，传统方法学的软件生命周期中的三个时期和八个阶段是什么？软件定义时期、软件开发时期和软件维护时期；问题定义、可行性研究、需求分析、概要设计、详细设计、编码和单元测试、综合测试和运行维护。</p>



<p class="wp-block-paragraph">由于双方从未对发起这么项目的目的产生过任何分歧，由此我认为问题定义与可行性研究是没有问题的。而万恶之源显然就是需求分析了。</p>



<p class="wp-block-paragraph">传统方法学中要求在需求分析过程中建立软件三种模型：</p>



<ul class="wp-block-list"><li>数据模型：理解并描述问题的信息域</li><li>功能模型：定义软件应完成的功能</li><li>行为模型：描述系统的状态以及引起系统状态转换的事件</li></ul>



<p class="wp-block-paragraph">回到我与<a href="https://www.yunyoujun.cn/" target="_blank" rel="noreferrer noopener">云游君</a>的讨论中，发现了问题后我提议合作完成一张数据流图，也就是希望建立一个双方都准确地了解并认可的数据模型，客观来看，这推动了我们的初次编码的进程。</p>



<p class="wp-block-paragraph">但是编码没有多久就被迫停下来，一个模块的都有哪些方法，哪些属性？各个模块之间如何联系，这些问题被摆到了讨论桌上，如果不是因为外部原因还不知道要跨服聊天多少次。</p>



<p class="wp-block-paragraph">看起来这次是因为功能模型和行为模型并未建立，所以我们对对方的想法了解的不够准确，就算是准确也未必认同。</p>



<p class="wp-block-paragraph">那么如果三个模型都建立了是不是就没问题了？不知道，但是根据现在的情况来看，基本不可能。事实是虽然我们合作完成了数据流图，但是这张图许多地方需要分解。分解后会出现什么分歧也是未知的，这项工作是需要在概要设计中做的。</p>



<p class="wp-block-paragraph">上文不是什么深入的分析，只是很表层的东西，却让我不得不重新审视《软件工程》这门学科。</p>



<h2 class="wp-block-heading">重新审视《软件工程》</h2>



<p class="wp-block-paragraph">仅仅是两个人合作，就出现了这么麻烦的事情。双方因为经历、涉足领域、之前并不认识和其它未知的原因导致了跨服聊天大量出现。如果实行切实有效的措施，在企业中这种现象只会更加频繁，后果也会更加致命。</p>



<p class="wp-block-paragraph">我也发现了一个情况，就是我们似乎更加重视《数据结构》、《XX 语言开发》这种课程，相对来说对《软件工程》的重视程度就要低很多了。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p><em>软件工程(<em>Software</em>&nbsp;<em>Engineering</em>，<em>SE</em>)软件工程是一门研究用工程化方法构建和维护有效的、实用的和高质量的软件的学科。</em></p><cite><a href="https://wiki.mbalib.com/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B" target="_blank" rel="noreferrer noopener" title="https://wiki.mbalib.com/wiki/%E8%BD%AF%E4%BB%B6%E5%B7%A5%E7%A8%8B">软件工程 &#8211; MBA 智库百科</a></cite></blockquote>



<p class="wp-block-paragraph">这是软件工程的定义之一，当初看到的时候没有什么感觉，认为这是一句说了和没说一样的话。</p>



<p class="wp-block-paragraph">现在我似乎能看到这句话的背后可能有无数的开发组遇到了我和<a href="https://www.yunyoujun.cn/" target="_blank" rel="noreferrer noopener">云游君</a>一样的问题。为了解决问题，前辈们殚精竭虑地总结出了一套行之有效的工程化方法，帮助后人在开发维护过程中少走弯路，提高效率。</p>



<p class="wp-block-paragraph">起初我没有理解这些，被现实教训了一顿。不过学习之路似乎就是这样的，当初并不知道这些有什么用，为啥要学，直到身临其境的时候才会感叹前人的智慧。</p>



<p class="wp-block-paragraph">要说这些工程化方法有哪些问题？一大问题就是许多企业尤其是中小企业无法承受严格按照流程进行开发的成本，可能是时间或者金钱上的成本，或是两者兼有。</p>



<p class="wp-block-paragraph">个人的小软件大多数情况下完全没必要严格按照流程来，更多的是灵光一现立马开始编码，小几百行甚至几十行代码就能实现的功能。</p>



<p class="wp-block-paragraph">应对需求频繁变化的场景前辈提出了<a href="https://baike.baidu.com/item/%E6%9E%81%E9%99%90%E7%BC%96%E7%A8%8B" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/%E6%9E%81%E9%99%90%E7%BC%96%E7%A8%8B">极限编程</a>；为了提高需求分析的质量前辈提出了<a href="https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E5%8E%9F%E5%9E%8B" target="_blank" rel="noreferrer noopener" title="https://baike.baidu.com/item/%E5%BF%AB%E9%80%9F%E5%8E%9F%E5%9E%8B">快速原型</a>；以及现在的微软过程等。每个都是对这门的学科的发展，使其适应新时代的变化，提高生产效率。</p>



<p class="wp-block-paragraph">不过这不是说以后开发软件都要严格按照流程来，能解决问题的方法就是好方法，而且这个方法没准以后也会成为《软件工程》的一部分。毕竟科学界从来都没有真理存在，有的只是能让人信服的证据，以及推翻那些曾经被信服的证据的证据。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/1086/feed</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Windows10 搜索不到 WIFI 的解决方法</title>
		<link>https://www.addesp.com/archives/731</link>
					<comments>https://www.addesp.com/archives/731#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Tue, 08 Sep 2020 16:18:32 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[WIFI]]></category>
		<category><![CDATA[Windows]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=731</guid>

					<description><![CDATA[讲个笑话：“Windows 10 is the best OS for gaming”。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Windows 10 is the best OS for gaming.</p><cite>我信了，你呢？</cite></blockquote>



<p class="wp-block-paragraph">每次我重装 Windows10 之后，只要使用系统自带的更新更新一次，重启后大概率就搜索不到 WIFI 了，而且重装驱动啥的根本没用，最后只能再次重装祈祷不要出事。</p>



<p class="wp-block-paragraph">不愧是最适合玩游戏的系统。</p>



<h2 class="wp-block-heading">症状</h2>



<p class="wp-block-paragraph">WIFI 列表中搜索不到任何信号。</p>



<h2 class="wp-block-heading">解决方法</h2>



<h3 class="wp-block-heading">重启电脑</h3>



<p class="wp-block-paragraph">遇到问题先重启也算是常识了，如果重启无法解决请尝试下一个方法。</p>



<h3 class="wp-block-heading">重装网卡驱动</h3>



<p class="wp-block-paragraph">依次点击：此电脑 -&gt; 管理 -&gt; 设备管理器 -&gt; 网络适配器。</p>



<p class="wp-block-paragraph">如果网卡驱动异常请尝试备份驱动后重新安装。或者下载到官方的驱动后再卸载当前驱动并安装刚下载的驱动。</p>



<p class="wp-block-paragraph">如果无法解决请看下一个方法。</p>



<h3 class="wp-block-heading">清理注册表</h3>



<p class="wp-block-paragraph">本方法属于玄学方法，来自<a href="https://www.lbihua.cn/2021/06/05/Thinking/NIC_dirver/" target="_blank" rel="noreferrer noopener" title="https://www.lbihua.cn/2021/06/05/Thinking/NIC_dirver/">如果问题无法解决，那就换个问法继续解决</a>。该文中提到使用 CCleaner 清理注册表并重启电脑后问题就解决了。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/731/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>谈谈 QQ 和 TIM</title>
		<link>https://www.addesp.com/archives/596</link>
					<comments>https://www.addesp.com/archives/596#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 07 Sep 2020 05:05:53 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[QQ]]></category>
		<guid isPermaLink="false">https://www.addesp.com/?p=596</guid>

					<description><![CDATA[自腾讯公司于 2016 年 11 月 发布 TIM 以来，关于两者到底谁好的争论就一直每个结果，本文也不打算讨论两者中哪一个更好，只是打算说说对于这两款软件的看法而已，这也是我建立本站的原因之一。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">自腾讯公司于 2016 年 11 月 发布 TIM 以来，关于两者到底谁好的争论就一直没个结果，本文也不打算讨论两者中哪一个更好，只是打算说说对于这两款软件的看法而已，这也是我建立本站的原因之一。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>建立这个网站的目的就是想找一个写东西的地方。写写自己的笔记，感受啥的。分享一些好玩的事情，好用的软件。</p><cite><a href="https://www.addesp.com/about">https://www.addesp.com/about</a></cite></blockquote>



<h2 class="wp-block-heading">QQ</h2>



<p class="wp-block-paragraph">QQ 大概是我用的第一款腾讯的软件，也是用的时间最长的腾讯软件了。起初刚接触到它的时候觉得它的功能很丰富，其中最喜欢的就是 QQ 宠物。有个可爱的小企鹅在电脑桌面上蹦蹦跳跳，还能有简单的互动对于当时的我来说有着不小的吸引力。</p>



<p class="wp-block-paragraph">大概过了几年我发现电脑的配置似乎跟不上了，当时电脑的内存只有 2GB，操作系统是 Windows 7 32 位，开机后再开一个 QQ 内存就飙到 60% 以上了。当时似乎也没有去找类似 QQ 轻聊版这样的替代品，或者当时也没有这个东西，也就忍下来了。后来实在受不了了就攒钱买了个 2GB 的内存条插进去才解决了这个问题。</p>



<p class="wp-block-paragraph">后来出现了智能手机，但是我第一部智能手机只有 256MB 的内存（运行内存），明显感觉到安装常规的 QQ 跑起来很吃力，但这次我找到的 QQ 轻聊版，然后一直用到这部手机淘汰。</p>



<p class="wp-block-paragraph">我的第二部手机是攒钱买了红米 Note 系列，具体型号记不清了。内存大小好像是 2GB，我打算换上常规的 QQ 试试。但是此时我发现我已经对 QQ 的聊天以外的功能提不起多少兴趣了，更多地觉得这是一个累赘，不过貌似是因为当时轻聊版已经没了所以就没有继续用了下去。</p>



<p class="wp-block-paragraph">手机 QQ 逐渐推出了如悄悄话等功能，不停地告诉我我不是它的目标用户。这段时间我也在关注着 QQ 的替代品。</p>



<h2 class="wp-block-heading">TIM</h2>



<p class="wp-block-paragraph">不知何时，我知道了有 TIM 这种东西，我试着用了用它，发现这可能才是我想要的 QQ，纯粹的聊天功能，搭配一些办公用的功能，功能足够也不显冗余。我又下载的 PC 端的 TIM，发现依旧很合我的心意，于是我卸载的手机 QQ 和 PC QQ，全部换上了 TIM。</p>



<p class="wp-block-paragraph">从此再也不需要去辨认一些奇形怪状的字体，花里胡哨的特效等，简简单单地聊天很符合我的需求。</p>



<p class="wp-block-paragraph">不过大概在半年前或是一年前，TIM 经常接不到消息或者消息接收延迟好几个小时，还好没有错过什么大事，但是这让我立刻停用了 TIM，换回了常规的手机 QQ。PC 端的 TIM 没有发现这个问题，所以暂时没换。</p>



<p class="wp-block-paragraph">我也是这时才发现诸如 QQ 看点、好看视频和 QQ 天气这类东西，用惯了 TIM 的我对这类东西有很强的排斥心理。甚至现在手机 QQ 也出了一个小程序功能，相当于在手机 QQ 里跑一个阉割版的浏览器内核，感觉完全没必要。只是因为 TIM 出了问题所以暂时先用着常规的手机 QQ 而已。</p>



<p class="wp-block-paragraph">近期觉得 TIM 应该恢复正常了，于是在手机上重新换回的 TIM，熟悉的感觉立马回来了，如果没有出什么意外我大概也会一直用下去。</p>



<h2 class="wp-block-heading">看法</h2>



<p class="wp-block-paragraph">很明显那 QQ 和 TIM 是针对的两种人群，无所谓好坏之分。</p>



<p class="wp-block-paragraph">QQ 的好处就是功能丰富，虽然很多可能用不上，但是想用某项功能的时候可以找到现成的，或者一个新推出的功能非常合用户的胃口。简单点比如情侣可以开启情侣空间，在空间秀秀恩爱等。发说说分享一下自己遇到的事情啥的。这些需求客观存在，并且大量的用户需要这些需求，QQ 进行了跟进，从这点上来说 QQ 做得很好。</p>



<p class="wp-block-paragraph">TIM 的好处就是删掉 QQ 上大量的冗余功能，将办公作为主打方向。这就是导致了其没有 QQ 那么繁杂，界面十分简洁，没有多余的东西去分散用户的注意力。对于我来说比较明显方便之处的就是可以直接在手机端的 TIM 上收发 QQ 邮件，虽然正事我很少用 QQ 邮箱，但是一些常规的交流 QQ 邮箱还是用得挺多的。虽然现在手机 QQ 也能收发 QQ 邮件了，但这在当时对我的吸引力还是挺大的。</p>



<p class="wp-block-paragraph">在这最后我希望 TIM 能一直维护下去，给我一个纯粹的聊天沟通工具。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/596/feed</wfw:commentRss>
			<slash:comments>1</slash:comments>
		
		
			</item>
		<item>
		<title>通俗易懂地解释什么是区块链</title>
		<link>https://www.addesp.com/archives/306</link>
					<comments>https://www.addesp.com/archives/306#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 23 Jul 2020 06:00:00 +0000</pubDate>
				<category><![CDATA[区块链]]></category>
		<category><![CDATA[科普]]></category>
		<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=306</guid>

					<description><![CDATA[不讨论细节，旨在勾勒出区块链的轮廓。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">前言</h2>



<p class="wp-block-paragraph">不讨论细节，旨在勾勒出区块链的轮廓。由于是以介绍为主，所以不会涉及细节，而且<strong>**肯定会有一些错误**</strong>。如果有兴趣的话可以自行查询资料。</p>



<h2 class="wp-block-heading">故事</h2>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>我们先讲个故事：张三是一个三年级的学生，他的父母是做生意的。有一天管帐本的妈妈把账本弄丢了，付出了很大的代价才搞清楚资金的来龙去脉，于是父母商量了一下决定，爸爸和张三每次收支都要告诉妈妈，由妈妈记录这一笔帐，并每天晚上将账本公布，账本开头写上昨日三人的期初余额，结尾写上三人的期末余额。爸爸和张三检查无误后各自抄走一份，如果公布的账本有误则认为是假账，则选择昨日公认的账本作为今天的账本，今日的所有支出作废，损失由妈妈承担。</p></blockquote>



<p class="wp-block-paragraph">检查账本时都要检查下列项目：</p>



<ul class="wp-block-list"><li>每笔交易是否有资金支出者的签名</li><li>资金支出者是否有足够的钱支付</li><li>今日的收支是否计算得出账本结尾声明的三人的期末余额</li><li>今日账本三人的期初余额和昨日账本三人的期末余额一致</li></ul>



<p class="wp-block-paragraph">Q：妈妈是否可以记录一些虚假的交易，比如妈妈擅自记录一笔爸爸转给妈妈&nbsp;100&nbsp;块钱的账？</p>



<p class="wp-block-paragraph">A：不可能，因为这笔转账必须由爸爸的签名才行，如果没有签名则晚上爸爸和张三检查账本的时候会被认为是假账，而损失却由妈妈自己承担。</p>



<p class="wp-block-paragraph">Q：张三是否可以假装爸爸发给妈妈一笔给自己买零食的交替？</p>



<p class="wp-block-paragraph">A：不能，因为没有爸爸的签名。</p>



<hr class="wp-block-separator"/>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>时间一长，妈妈发现记账很累，于是要求每记一笔账都要求支出放支付一块钱给妈妈。后续爸爸和张三发现妈妈记账很赚钱，于是想要自己来记账。一家人商量后达成一致，每个人都将自己的交易发送给其它两人，到了晚上汇总的时候，通过抽签选择采用谁的账本，并给予记账人报酬</p></blockquote>



<hr class="wp-block-separator"/>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>有一次爸爸妈妈一起出差了，抽签抽到爸爸记账，但是爸爸很忙，没时间把账本给张三看，导致张三无法抄写今天的账本。后来一家人商量后达成一致，当允许抄写记账人以外的账本。比如爸爸很忙，但是妈妈有空，妈妈抄完后可以让张三抄。</p></blockquote>



<h2 class="wp-block-heading">解释</h2>



<p class="wp-block-paragraph">这个故事描述了区块链的大致工作流程和特点。<br>区块链中的就是故事中的账本，每天产生一个账本就是每天产生一个「区块」。「链」对应的就是昨日账本的期末余额和今日账本的期初余额，两种余额将两个账本连接在一起，对应将「区块」连接依赖的「链」。</p>



<h3 class="wp-block-heading">区块链的特点</h3>



<ul class="wp-block-list"><li>分布式：对应故事中账本并不是只有记账人有，其它人也有一份。对应现实中数据不只有一份，而是有很多份，在不同的地方存储。</li><li>去中心化：对应故事中对账本起决定作用的人不是记账人，只有一家三口都认同的账本才是有效账本。对应现实中一份数据的内容究竟是什么不是一个人或者一个组织能够决定的，而是要大多数参与者达成共识才能决定。</li><li>点对点：对应故事中抄写账本时可以从任何持有账本的人手中抄写一份，而不是必须从记账人手中抄写。对应现实中一个节点请求数组不是请求中心节点，而是请求任意一个节点即可。</li><li>难以篡改：对应故事中任何人都无法伪造账本中的交易记录，因为必须要有签名。对应现实中依靠密码学保证的防篡改功能。</li></ul>



<h3 class="wp-block-heading">区块链的工作流程</h3>



<ol class="wp-block-list"><li>利用共识选择出一定时间段内的数据的管理员：对应故事中一家三口通过抽签决定记账人。</li><li>当数据发生改动时要对所有的节点进行广播：对应故事中一家三口在晚上决定记账人钱必须将自己的交易告知其余两人。</li><li>对于数据管理员公布的数据要进行检查：对应故事中要对账本进行的四种检查。</li><li>如果检查错误则回滚数据：对应故事中如果检查有误，则选择昨日公认的账本作为今天的账本，今日的所有支出作废。</li><li>如果检查无误则复制一份保存在本地：对应故事中没人都会抄写一份账本。</li></ol>



<h2 class="wp-block-heading">写在最后</h2>



<p class="wp-block-paragraph">本文是指介绍了一下区块链的特点时大致工作流程，并未涉及细节问题，如果有兴趣的话可以自行查询资料。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/306/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>协程和线程的历史</title>
		<link>https://www.addesp.com/archives/211</link>
					<comments>https://www.addesp.com/archives/211#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Tue, 14 Jul 2020 05:27:00 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=211</guid>

					<description><![CDATA[聊聊协程和线程的出现]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">昨天运营商线路出了问题，体验了一把电话线拨号上网一样的网速。</p>



<p class="wp-block-paragraph">打开浏览器是这样的场景：</p>



<ol class="wp-block-list"><li>先白屏一会儿。</li><li>逐渐显示出网页上的全部文字。</li><li>卡一会儿。</li><li>慢慢地把网页上的所有图片显示出来。</li></ol>



<p class="wp-block-paragraph">正在无聊地等待网页加载的时候阴差阳错地意识到浏览器这种一边渲染一边下载的工作方式在历史上还促成了<strong>协程和线程</strong>这一概念的产生。那么就来讲一讲故事吧。</p>



<h2 class="wp-block-heading">背景</h2>



<p class="wp-block-paragraph">早期的多道&nbsp;OS&nbsp;还只有<strong>进程</strong>这一概念，OS&nbsp;的最小调度单位也是<strong>进程</strong>。每个程序都有自己独立的内存空间，互不干扰，看起来十分和谐。但是也有一些不那么方便的地方。</p>



<p class="wp-block-paragraph">就拿浏览器举例，我们把浏览器的工作简化为下列几项：</p>



<ul class="wp-block-list"><li>从服务器下载数据</li><li>显示文本</li><li>解压缩图片</li><li>显示图片</li></ul>



<p class="wp-block-paragraph">如果我们按照从上到下的顺序来执行这些工作，会给用户带来非常差的使用体验，所以开发者希望这些工作可以交替进行，最好是能自由控制什么时机执行哪些工作。</p>



<p class="wp-block-paragraph"><br>能不能通过启动多个不同功能的进程实现呢？很难，因为访问其它<strong>进程</strong>的内存是一件很麻烦或者被&nbsp;OS&nbsp;禁止的事情，这就会导致从服务器下载下来的数据难以被<strong>文本显示进程</strong>等渲染进程获取到。同时因为进程的调度是由&nbsp;Kernel&nbsp;控制的，用户层无法干预，也就无法在合适的时机切换到指定的工作中。</p>



<h2 class="wp-block-heading">协程</h2>



<p class="wp-block-paragraph">于是<strong>协程</strong>就产生了。开发者在用户层自己实现了一套调度机制，可以按照开发者的意愿切换到同一个进程内的不同的执行序列，同时把每个执行序列称为<strong>协程</strong>。</p>



<p class="wp-block-paragraph"><br>将其应用在上文提到的浏览器上就会变成四个执行序列，分别交替执行下载数据、显示文本、解压缩图片和显示图片这四项工作，开发者可以按照自己的意愿切换到某个工作中，比如<strong>图片解压缩协程</strong>完成了一部分解压工作后就主动切换到<strong>图片显示协程</strong>显示出部分图片。看起来很美好，但是还是有问题。<br></p>



<p class="wp-block-paragraph">因为下载数据会涉及到网卡&nbsp;I/O，而一旦启动了网卡&nbsp;I/O，整个进程都会被阻塞，由于上文提到的四个协程都在同一个进程中，所以这四个协程也会随着进程的阻塞而被迫停下来，直到网卡&nbsp;I/O&nbsp;完成，进程被&nbsp;kernel&nbsp;调度到后才能继续。<br>这不是我们想要的，因为我们希望数据下载的过程中先切换到其它协程进行渲染已经下载的数据，不让用户等待那么久，但是现在只要启动了网卡&nbsp;I/O，就不得不停下来等待，渲染工作也就无法进行。</p>



<h2 class="wp-block-heading">线程</h2>



<p class="wp-block-paragraph">大家认识到这个问题后，也出于更好地利用多核和多处理器这一目的，就引入了<strong>线程</strong>这一概念，并很快在各个&nbsp;OS&nbsp;上实将其实现。一个<strong>进程</strong>可以有多个<strong>线程</strong>，这些<strong>线程</strong>共用它们所属的<strong>进程</strong>的内存空间，所以互相通信方便了很多。同时<strong>线程</strong>成为了&nbsp;OS&nbsp;调度的最小单位，其中一个<strong>线程</strong>因为某些原因阻塞不会影响到其它<strong>线程</strong>，于是上面的问题就得到解决了。</p>



<p class="wp-block-paragraph"><br>这时我们可以开四个<strong>线程</strong>，分别交替执行下载数据、显示文本、解压缩图片和显示图片这四项工作。这样即使<strong>数据下载线程</strong>阻塞，<strong>渲染线程</strong>也能继续执行渲染工作。</p>



<p class="wp-block-paragraph">不过缺点是<strong>**线程**</strong>的调度完全由&nbsp;kernel&nbsp;控制，用户层无法干预。不过这不是问题，一种做法就是开两个线程&nbsp;A&nbsp;和&nbsp;B：</p>



<ul class="wp-block-list"><li>A&nbsp;线程：从服务器下载数据。</li><li>B&nbsp;线程：在线程内部启动三个<strong>协程</strong>，分别负责显示文本、解压缩图片和显示图片，这三个渲染工作可以按照开发者的意愿自由地切换，这样也保留一定的自由度。</li></ul>



<h2 class="wp-block-heading">总结</h2>



<p class="wp-block-paragraph"><strong>协程</strong>是最近几年才火起来的，容易让人产生<strong>协程</strong>是新的概念这一错觉，实际上<strong>协程</strong>的出现比<strong>线程</strong>要早。</p>



<p class="wp-block-paragraph"><br><strong>协程</strong>的优点是切换代价比<strong>线程</strong>小很多，自由度高，缺点是一旦启动了&nbsp;I/O&nbsp;会被全部阻塞。</p>



<p class="wp-block-paragraph"><br><strong>线程</strong>的优点就是其中一个<strong>线程</strong>被阻塞，不会影响到其它<strong>线程</strong>，缺点是切换代价比<strong>协程</strong>大很多。</p>



<p class="wp-block-paragraph"><br>折中的方案就是按照程序的特点在特定的<strong>线程</strong>上启动一定数量的<strong>协程</strong>，一些需要&nbsp;I/O&nbsp;的工作则交给其它的<strong>线程</strong>，保证&nbsp;I/O&nbsp;时不会阻塞<strong>协程</strong>。这种方式固然优点都不如<strong>线程</strong>和<strong>协程</strong>那么大，但是缺点也不如<strong>线程</strong>和<strong>协程</strong>那么大。所以有时候太极端也不是好事，折中一下通常是不错的解决方案。放弃了一些东西也会得到一些东西。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/211/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>关于 Windows 任务栏透明原理的探索</title>
		<link>https://www.addesp.com/archives/343</link>
					<comments>https://www.addesp.com/archives/343#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Sat, 04 Jul 2020 15:05:09 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=343</guid>

					<description><![CDATA[概述 Windows&#160;10&#160;的任务栏非常简约，扁平的半透明阴影，但是却没有提供原生的设置透 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">Windows&nbsp;10&nbsp;的任务栏非常简约，扁平的半透明阴影，但是却没有提供原生的设置透明度的方式，为了实现这个功能已经有了如 TranslucentTB 这类优秀的软件。但笔者依然对其原理有些好奇。本来打算看 TranslucentTB 的源码，无奈看起来费劲，先看看能不能蒙出来。</p>



<p class="wp-block-paragraph">目前进度如下:</p>



<ul class="wp-block-list"><li>修改注册表键值（需重启 explorer.exe）</li><li>直接修改 explorer.exe 的内存（进行中）</li><li>反汇编时发现的一个未公开的&nbsp;Win32&nbsp;API</li></ul>



<h2 class="wp-block-heading">注册表键值</h2>



<p class="wp-block-paragraph">通过查阅资料发现透明度可以通过修改注册表键值实现。</p>



<p class="wp-block-paragraph"><code>HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced</code>下的<code>TaskbarAcrylicOpacity</code>键，类型是<code>DWORD</code>，默认不存在。取值范围$[0,255]$，表示透明度。</p>



<p class="wp-block-paragraph"><code>HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced</code>下的<code>UseOLEDTaskbarTransparency</code>键，如果存在则修改为 0，或者删除。</p>



<p class="wp-block-paragraph">修改完成后重启&nbsp;explorer.exe&nbsp;即可看到效果。</p>



<h2 class="wp-block-heading">直接修改&nbsp;explorer.exe&nbsp;的内存（进行中）</h2>



<p class="wp-block-paragraph">将<code>TaskbarAcrylicOpacity</code>设置为 200。</p>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">使用&nbsp;CE&nbsp;搜索这个值，发现透明度存储<code>在explorer.exe&nbsp;+&nbsp;0x30E9D0</code>，修改后可立即生效。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="796" height="815" src="https://www.addesp.com/wp-content/uploads/2020/09/0-14.webp" alt="" class="wp-image-344"/></figure>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">给<code>explorer.exe&nbsp;+&nbsp;0x30E9D0</code>下访问断点后动态调式，发现入口为<code>explorer.exe&nbsp;+&nbsp;0x76424</code>的函数读取了这个代表透明度的值。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1920" height="1033" src="https://www.addesp.com/wp-content/uploads/2020/09/1-12.webp" alt="" class="wp-image-345"/></figure>



<pre class="wp-block-code"><code class="">explorer.exe+76424 - 40 53                 - push rbx
explorer.exe+76426 - 48 83 EC 20           - sub rsp,20 { 32 }
explorer.exe+7642A - 80 3D 7B832900 00     - cmp byte ptr [explorer.exe+30E7AC],00 { (1),0 }
explorer.exe+76431 - 8B D9                 - mov ebx,ecx
explorer.exe+76433 - 74 0C                 - je explorer.exe+76441
explorer.exe+76435 - 8A 05 95852900        - mov al,[explorer.exe+30E9D0] { (200) }
explorer.exe+7643B - 48 83 C4 20           - add rsp,20 { 32 }
explorer.exe+7643F - 5B                    - pop rbx
explorer.exe+76440 - C3                    - ret </code></pre>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">通过&nbsp;callstack&nbsp;发现入口为<code>explorer.exe&nbsp;+&nbsp;0x7629C</code>的函数调用了上文提到的入口为<code>explorer.exe&nbsp;+&nbsp;0x76424</code>的函数，并且读取了<code>UseOLEDTaskbarTransparency</code>键。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1920" height="1026" src="https://www.addesp.com/wp-content/uploads/2020/09/2-12.webp" alt="" class="wp-image-346"/></figure>



<pre class="wp-block-code"><code class="">explorer.exe+7629C - 40 55                 - push rbp
explorer.exe+7629E - 53                    - push rbx
explorer.exe+7629F - 57                    - push rdi
explorer.exe+762A0 - 48 8B EC              - mov rbp,rsp
explorer.exe+762A3 - 48 83 EC 40           - sub rsp,40 { 64 }
explorer.exe+762A7 - 48 8D 55 20           - lea rdx,[rbp+20]
explorer.exe+762AB - 48 8D 4D 38           - lea rcx,[rbp+38]
explorer.exe+762AF - E8 1C070000           - call explorer.exe+769D0
explorer.exe+762B4 - 33 FF                 - xor edi,edi
explorer.exe+762B6 - B0 FF                 - mov al,-01 { 255 }
explorer.exe+762B8 - 40 38 7D 20           - cmp [rbp+20],dil
explorer.exe+762BC - 0F84 93000000         - je explorer.exe+76355
explorer.exe+762C2 - 48 8D 45 30           - lea rax,[rbp+30]
explorer.exe+762C6 - 89 7D 28              - mov [rbp+28],edi
explorer.exe+762C9 - 48 89 44 24 30        - mov [rsp+30],rax
explorer.exe+762CE - 4C 8D 05 8BC32100     - lea r8,[explorer.exe+292660] { ("UseOLEDTaskbarTransparency") }
explorer.exe+762D5 - 48 8D 45 20           - lea rax,[rbp+20]
explorer.exe+762D9 - C7 45 30 04000000     - mov [rbp+30],00000004 { 4 }
explorer.exe+762E0 - 48 89 44 24 28        - mov [rsp+28],rax
explorer.exe+762E5 - 48 8D 15 D4C22100     - lea rdx,[explorer.exe+2925C0] { ("SOFTWARE\Microsoft\Windows\Curr") }
explorer.exe+762EC - 48 8D 45 28           - lea rax,[rbp+28]
explorer.exe+762F0 - 41 B9 12000010        - mov r9d,10000012 { 268435474 }
explorer.exe+762F6 - 48 C7 C1 02000080     - mov rcx,FFFFFFFF80000002 { -2147483646 }
explorer.exe+762FD - 48 89 44 24 20        - mov [rsp+20],rax
explorer.exe+76302 - 48 FF 15 A7002100     - call qword ptr [explorer.exe+2863B0] { ->KERNELBASE.RegGetValueW }
explorer.exe+76309 - 0F1F 44 00 00         - nop dword ptr [rax+rax+00]
explorer.exe+7630E - 8D 4F 0D              - lea ecx,[rdi+0D]
explorer.exe+76311 - 85 C0                 - test eax,eax
explorer.exe+76313 - 74 48                 - je explorer.exe+7635D
explorer.exe+76315 - 3D EA000000           - cmp eax,000000EA { 234 }
explorer.exe+7631A - 0F44 C1               - cmove eax,ecx
explorer.exe+7631D - 8B C8                 - mov ecx,eax
explorer.exe+7631F - 85 C0                 - test eax,eax
explorer.exe+76321 - 7E 09                 - jle explorer.exe+7632C
explorer.exe+76323 - 0FB7 C9               - movzx ecx,cx
explorer.exe+76326 - 81 C9 00000780        - or ecx,80070000 { -2147024896 }
explorer.exe+7632C - 8B 45 20              - mov eax,[rbp+20]
explorer.exe+7632F - 85 C9                 - test ecx,ecx
explorer.exe+76331 - 0FB6 D8               - movzx ebx,al
explorer.exe+76334 - 0F48 DF               - cmovs ebx,edi
explorer.exe+76337 - E8 58010000           - call explorer.exe+76494
explorer.exe+7633C - 84 C0                 - test al,al
explorer.exe+7633E - 74 3A                 - je explorer.exe+7637A
explorer.exe+76340 - 8B 4D 38              - mov ecx,[rbp+38]
explorer.exe+76343 - E8 DC000000           - call explorer.exe+76424
explorer.exe+76348 - 84 DB                 - test bl,bl
explorer.exe+7634A - 0FB6 C0               - movzx eax,al
explorer.exe+7634D - B9 99000000           - mov ecx,00000099 { 153 }
explorer.exe+76352 - 0F45 C1               - cmovne eax,ecx
explorer.exe+76355 - 48 83 C4 40           - add rsp,40 { 64 }
explorer.exe+76359 - 5F                    - pop rdi
explorer.exe+7635A - 5B                    - pop rbx
explorer.exe+7635B - 5D                    - pop rbp</code></pre>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">通过&nbsp;callstack&nbsp;发现入口为<code>explorer.exe&nbsp;+&nbsp;0x761D4</code>的函数调用了上文提到的入口为e<code>xplorer.exe&nbsp;+&nbsp;0x7629C</code>的函数，并且调用了一个&nbsp;Win32&nbsp;API：<code>SetWindowCompositionAttribute</code>。通过查阅资料发现这是一个未公开的&nbsp;API。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1920" height="1018" src="https://www.addesp.com/wp-content/uploads/2020/09/3-9.webp" alt="" class="wp-image-347"/></figure>



<pre class="wp-block-code"><code class="">explorer.exe+761D4 - 48 89 5C 24 10        - mov [rsp+10],rbx
explorer.exe+761D9 - 48 89 74 24 18        - mov [rsp+18],rsi
explorer.exe+761DE - 57                    - push rdi
explorer.exe+761DF - 48 83 EC 60           - sub rsp,60 { 96 }
explorer.exe+761E3 - 48 8B 05 7E522900     - mov rax,[explorer.exe+30B468] { (2.66) }
explorer.exe+761EA - 48 33 C4              - xor rax,rsp
explorer.exe+761ED - 48 89 44 24 50        - mov [rsp+50],rax
explorer.exe+761F2 - 48 8B F1              - mov rsi,rcx
explorer.exe+761F5 - 48 8D 54 24 20        - lea rdx,[rsp+20]
explorer.exe+761FA - 33 C9                 - xor ecx,ecx
explorer.exe+761FC - E8 CF070000           - call explorer.exe+769D0
explorer.exe+76201 - 80 7C 24 20 00        - cmp byte ptr [rsp+20],00 { 0 }
explorer.exe+76206 - BF 01000000           - mov edi,00000001 { 1 }
explorer.exe+7620B - 74 0F                 - je explorer.exe+7621C
explorer.exe+7620D - E8 82020000           - call explorer.exe+76494
explorer.exe+76212 - F6 D8                 - neg al
explorer.exe+76214 - 1B FF                 - sbb edi,edi
explorer.exe+76216 - 83 E7 02              - and edi,02 { 2 }
explorer.exe+76219 - 83 C7 02              - add edi,02 { 2 }
explorer.exe+7621C - E8 7B000000           - call explorer.exe+7629C
explorer.exe+76221 - 0FB6 D8               - movzx ebx,al
explorer.exe+76224 - E8 0B050000           - call explorer.exe+76734
explorer.exe+76229 - 83 64 24 4C 00        - and dword ptr [rsp+4C],00 { 0 }
explorer.exe+7622E - 25 FFFFFF00           - and eax,00FFFFFF { 16777215 }
explorer.exe+76233 - 8B D3                 - mov edx,ebx
explorer.exe+76235 - 89 7C 24 40           - mov [rsp+40],edi
explorer.exe+76239 - C1 E2 18              - shl edx,18 { 24 }
explorer.exe+7623C - 48 8B CE              - mov rcx,rsi
explorer.exe+7623F - 0B C2                 - or eax,edx
explorer.exe+76241 - C7 44 24 38 10000000  - mov [rsp+38],00000010 { 16 }
explorer.exe+76249 - 89 44 24 48           - mov [rsp+48],eax
explorer.exe+7624D - 48 8D 54 24 28        - lea rdx,[rsp+28]
explorer.exe+76252 - B8 13000000           - mov eax,00000013 { 19 }
explorer.exe+76257 - 89 44 24 44           - mov [rsp+44],eax
explorer.exe+7625B - 89 44 24 28           - mov [rsp+28],eax
explorer.exe+7625F - 48 8D 44 24 40        - lea rax,[rsp+40]
explorer.exe+76264 - 48 89 44 24 30        - mov [rsp+30],rax
explorer.exe+76269 - 48 FF 15 08F82000     - call qword ptr [explorer.exe+285A78] { ->user32.SetWindowCompositionAttribute }
explorer.exe+76270 - 0F1F 44 00 00         - nop dword ptr [rax+rax+00]
explorer.exe+76275 - 48 8B 4C 24 50        - mov rcx,[rsp+50]
explorer.exe+7627A - 48 33 CC              - xor rcx,rsp
explorer.exe+7627D - E8 4E2F0200           - call explorer.exe+991D0
explorer.exe+76282 - 4C 8D 5C 24 60        - lea r11,[rsp+60]
explorer.exe+76287 - 49 8B 5B 18           - mov rbx,[r11+18]
explorer.exe+7628B - 49 8B 73 20           - mov rsi,[r11+20]
explorer.exe+7628F - 49 8B E3              - mov rsp,r11
explorer.exe+76292 - 5F                    - pop rdi
explorer.exe+76293 - C3                    - ret </code></pre>



<h2 class="wp-block-heading">一个未公开的&nbsp;Win32&nbsp;API</h2>



<p class="wp-block-paragraph">未完待续</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/343/feed</wfw:commentRss>
			<slash:comments>4</slash:comments>
		
		
			</item>
		<item>
		<title>数字签名为什么可以防篡改</title>
		<link>https://www.addesp.com/archives/311</link>
					<comments>https://www.addesp.com/archives/311#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Mon, 27 Apr 2020 06:30:15 +0000</pubDate>
				<category><![CDATA[科普]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=311</guid>

					<description><![CDATA[概述 你可能在电视剧中看到坏人威胁主角的时候会拿出作为人质的亲人写的信，而主角一看笔迹就知道是亲人所写的了。  [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">你可能在电视剧中看到坏人威胁主角的时候会拿出作为人质的亲人写的信，而主角一看笔迹就知道是亲人所写的了。</p>



<p class="wp-block-paragraph">在这里笔迹起到了两个作用：</p>



<ul class="wp-block-list"><li>证明信的内容确实是亲人所写</li><li>证明内容没有被篡改过</li></ul>



<p class="wp-block-paragraph">在计算机的世界中，我们同样需要对信息一样的验证。</p>



<h2 class="wp-block-heading">例子</h2>



<p class="wp-block-paragraph">下面是一段在线聊天对话：</p>



<ul class="wp-block-list"><li>小明：小红，借我 30w 呗，我去买点水喝。</li><li>小红：可以，不过你要给我打一个欠条，拍照发过来。</li><li>小明：没问题，发给你了。</li><li>小红：好的，明天我把带给你。</li></ul>



<p class="wp-block-paragraph">此时，这笔借款交易的安全性如何？</p>



<ul class="wp-block-list"><li>小红可能会&nbsp;PS&nbsp;照片修改借款数字。</li><li>小明事后可能会抵赖说照片是修改过的。</li><li>照片可能在传输过程中阴差阳错地出现错误导致欠条内容改变。</li><li>其它。</li></ul>



<p class="wp-block-paragraph">这时需要解决的问题就是：</p>



<ul class="wp-block-list"><li>如何证明这个照片是小明发给小红的。</li><li>如何证明这个照片没有被修改过。</li></ul>



<p class="wp-block-paragraph">此时我们就需要对照片进行<strong>数字签名</strong>。</p>



<h2 class="wp-block-heading">前置知识</h2>



<h3 class="wp-block-heading">哈希函数</h3>



<p class="wp-block-paragraph">哈希函数是可以将一段任意长度的信息转化为固定长度信息，这个固定长度的信息称为<strong>哈希值</strong>。</p>



<p class="wp-block-paragraph">哈希函数有两个特点：</p>



<ul class="wp-block-list"><li>任何长度的原始信息经过哈希函数后都会变成等长度的信息。</li><li>一旦输入给给哈希函数的信息发生了改变（哪怕是一点点的改变），最终的<strong>哈希值</strong>就会面目全非。</li></ul>



<p class="wp-block-paragraph">下面我们用一种叫做`SHA256`的哈希函数举个例子：</p>



<pre class="wp-block-preformatted">原始信息：100000000000000
哈希值：7186c3316577cf7ee0568c6ff7f3227b5b3e4b498e7eabba4f6e3d908e2162f4

原始信息：100000000000001
哈希值：11d77ab50791e10caffb01cb6ff851f4929edfb0ad79808111d78182b0048540</pre>



<p class="wp-block-paragraph">结果很明显，仅仅做一点点改动哈希值都会发生巨大的变化。</p>



<h3 class="wp-block-heading">非对称加密</h3>



<p class="wp-block-paragraph">非对称加密有两个密钥，一个称为<strong>私钥</strong>，一个称为<strong>公钥</strong>。<br>经过公钥加密的信息只能被相匹配的私钥解密，也就是说同一个公钥不能解密自己加密的信息。私钥加密的信息只有公钥可以解密，也就是说私钥不能解密自己的信息。同时也可以用公钥验证加密的信息是否是相匹配的私钥加密的。</p>



<h2 class="wp-block-heading">数字签名</h2>



<h3 class="wp-block-heading">签名过程</h3>



<ol class="wp-block-list"><li>小明自己生成一对公私钥。</li><li>小明计算出欠条照片的哈希值。</li><li>小明用私钥对哈希值进行加密形成<strong>签名</strong>。</li></ol>



<p class="wp-block-paragraph">签名的过程就完成了。此时小明需要将照片，公钥以及签名发给小红。</p>



<h3 class="wp-block-heading">验证过程</h3>



<ul class="wp-block-list"><li>小红用公钥解密<strong>签名</strong>得到照片的<strong>哈希值</strong>。</li><li>小红用公钥验证得到解密出来的<strong>哈希值</strong>确实是<strong>小明的私钥</strong>加密的。</li><li>小红计算出欠条照片的<strong>哈希值</strong>。</li><li>将两个哈希值进行对比，如果一样则验证成功，反之则失败。</li></ul>



<p class="wp-block-paragraph">可见验证成功需要同时满足两个条件：</p>



<ul class="wp-block-list"><li>解密出的哈希值与收到的照片的哈希值一致。</li><li>用公钥证明了<strong>哈希值</strong>确实是<strong>小明的私钥</strong>加密的。</li></ul>



<p class="wp-block-paragraph">第一个条件保证了照片没有被修改过。第二个条件则保证了借款双方都无法抵赖。</p>



<p class="wp-block-paragraph"><br>你可能会想，如果小明表示小红的公钥不是自己的，或者小红表示自己的公钥不是小明的岂不是还能抵赖？</p>



<h3 class="wp-block-heading">证书机构</h3>



<p class="wp-block-paragraph">此时我们需要第三方来做公正。小明需要将自己的公钥交给证书机构，证书机构审核后会颁发证书，证明这个公钥是小明的。而且这个证书是公开的，也就是说小红也可以拿到。<br>此时假如抵赖小明说你的公钥是假的，那么小红就可以指着证书对他：<strong>说睁大你的X眼看看，这是你颁发给你的证书，上面已经说了这个公钥是你的</strong>。当小红抵赖的时候也可以这么干。</p>



<h2 class="wp-block-heading">总结</h2>



<p class="wp-block-paragraph">数字签名可以切实有效地地保证双方的信息是没有被修改过的，并且可以明确消息来源。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/311/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>浅析跨站请求伪造（CSRF）攻击</title>
		<link>https://www.addesp.com/archives/218</link>
					<comments>https://www.addesp.com/archives/218#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 23 Apr 2020 02:07:52 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=218</guid>

					<description><![CDATA[概念 &#038; 案例 &#038; 防御]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">介绍下列内容：</p>



<ul class="wp-block-list"><li>什么是 CSRF 攻击</li><li>漏洞举例</li><li>常见的防御方式</li></ul>



<h2 class="wp-block-heading">什么是CSRF攻击</h2>



<h3 class="wp-block-heading">狭义的CSRF攻击</h3>



<p class="wp-block-paragraph">狭义的CSRF攻击就是指让Client在被Server认证的情况下去访问精心设计的恶意链接完成攻击。</p>



<ol class="wp-block-list"><li>因为 HTTP 是无状态的协议，即 Server 不知道每个请求来源于哪个用户。</li><li>所以服务器有时会在Client存储可以标识用户身份的信息。</li><li>在Client发送请求的时候附上用户身份信息保证Server可以识别出用户。</li><li>如果此时用户点击了一个恶意链接，且恶意链接会发送一个恶意请求到 Server。</li><li>一般情况下会因为不能识别用户身份而而被拒绝。</li><li>但是如果此时 Client 是刚刚被服务端认证过的，则发送恶意请求的同时也会附上刚刚认证的身份信息。</li><li>Server 收到恶意请求后发现Client是认证过的，于是将其识别为合法请求。</li><li>攻击者就完成了一次 CSRF 攻击。</li></ol>



<h2 class="wp-block-heading">广义的CSRF攻击</h2>



<p class="wp-block-paragraph">通过一些手段猜测出请求参数以及用户认证信息（如果需要认证的话），这样Server就会将其识别为合理请求。</p>



<p class="wp-block-paragraph"><br>例如某网站转账的URL为<code>https://www.bank.com/transfer?user=小明&amp;money=1000&amp;for=小红</code>，代表小明转账给小红 1000 元。那么攻击者就可以构造出这个URL—<code>https://www.bank.com/transfer?user=小明&amp;money=9999999999&amp;for=</code>小李直接掏空小明的钱。</p>



<h2 class="wp-block-heading">漏洞举例</h2>



<ul class="wp-block-list"><li><a rel="noreferrer noopener" href="https://www.freebuf.com/vuls/210837.html" data-type="URL" data-id="https://www.freebuf.com/vuls/210837.html" target="_blank">挖洞经验&nbsp;|&nbsp;Facebook的Gmail验证机制存在的CSRF漏洞</a> | CSRF&nbsp;Token缺少验证措施</li><li><a rel="noreferrer noopener" href="https://www.freebuf.com/vuls/131691.html" data-type="URL" data-id="https://www.freebuf.com/vuls/131691.html" target="_blank">Facebook上一个有趣的 CSRF</a> | 授权过程中缺少 state 参数</li><li><a rel="noreferrer noopener" href="https://www.freebuf.com/vuls/223496.html" data-type="URL" data-id="https://www.freebuf.com/vuls/223496.html" target="_blank">挖洞经验&nbsp;|&nbsp;利用 Instagram 版权功能构造 CSRF 漏洞删除其他用户文件</a> | 重要的参数直接写在 GET 请求里</li></ul>



<h2 class="wp-block-heading">常见的防御方式</h2>



<h3 class="wp-block-heading">验证码</h3>



<p class="wp-block-paragraph">在执行一些重要请求之前通过验证码的方式验证用户身份，例如短信验证码，图片验证码等。所以你会发现许多网站即使在登录状态先执行一些操作也需要验证。</p>



<h3 class="wp-block-heading">同源检测</h3>



<p class="wp-block-paragraph">根据 HTTP 协议，在 HTTP 头中有一个字段叫 Referer，记录了该HTTP请求的来源地址。&nbsp;对于 Ajax 请求，图片和 script 等资源请求，Referer 为发起请求的页面地址。对于页面跳转，Referer 为打开页面历史记录的前一个页面地址。因此我们使用 Referer 中链接的 Origin 部分可以得知请求的来源域名。</p>



<p class="wp-block-paragraph"><br>这种方法并非万无一失，Referer 的值是由浏览器提供的，虽然 HTTP 协议上有明确的要求，但是每个浏览器对于 Referer 的具体实现可能有差别，并不能保证浏览器自身没有安全漏洞。使用验证&nbsp;Referer&nbsp;值的方法，就是把安全性都依赖于第三方（即浏览器）来保障，从理论上来讲，这样并不是很安全。在部分情况下，攻击者可以隐藏，甚至修改自己请求的 Referer。<br></p>



<p class="wp-block-paragraph">同源验证是一个相对简单的防范方法，能够防范绝大多数的 CSRF 攻击。但这并不是万无一失的，对于安全性要求较高，或者有较多用户输入内容的网站，我们就要对关键的接口做额外的防护措施。</p>



<h3 class="wp-block-heading">CSRF&nbsp;Token</h3>



<p class="wp-block-paragraph">CSRF 攻击之所以能够成功，是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的 Token。服务器通过校验请求是否携带正确的 Token，来把正常的请求和攻击的请求区分开，也可以防范 CSRF 的攻击。</p>



<p class="wp-block-paragraph"><br>通过一套合适的规则（一般来说这套规则要便于分布式校验）来生成随机的 Token 并跟随 form 表单一起提交，由于 HTTPS 在传输过程中是加密的，所以攻击者无法截获 Token，前提是站点没有 XSS 漏洞。<br></p>



<p class="wp-block-paragraph">但是此方法的实现比较复杂，需要给每一个页面都写入 Token（前端无法使用纯静态页面），每一个 Form 及 Ajax 请求都携带这个 Token，后端对每一个接口都进行校验，并保证页面 Token 及请求 Token 一致。这就使得这个防护策略不能在通用的拦截上统一拦截处理，而需要每一个页面和接口都添加对应的输出和校验。这种方法工作量巨大，且有可能遗漏。</p>



<h3 class="wp-block-heading">双重Cookie验证</h3>



<p class="wp-block-paragraph">在会话中存储CSRF&nbsp;Token比较繁琐，而且不能在通用的拦截上统一处理所有的接口。<br>那么另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点，我们可以要求Ajax和表单请求携带一个Cookie中的值。</p>



<p class="wp-block-paragraph"><br>双重Cookie采用以下流程：</p>



<ul class="wp-block-list"><li>在用户访问网站页面时，向请求域名注入一个Cookie，内容为随机字符串（例如csrfcookie=v8g9e4ksfhw）。</li><li>&nbsp;在前端向后端发起请求时，取出Cookie，并添加到URL的参数中（接上例POST&nbsp;<code>https://www.a.com/comment?csrfcookie=v8g9e4ksfhw</code>）。</li><li>后端接口验证 Cookie 中的字段与 URL 参数中的字段是否一致，不一致则拒绝。</li></ul>



<p class="wp-block-paragraph">攻击者可以获取到URL参数中的Token值，但是无法获取到Cookie中的Token值，因此攻击者无法构造出恶意链接。</p>



<p class="wp-block-paragraph"><br>你可能会想既然我已经截取到了URL参数中的Token值，那么攻击者也就可以猜测出Cookie中的Token值。对，没错，因为上面的流程中URL参数和Cookie中存储的是相同的值，那么如果我对Cookie中的值进行加密呢？那么攻击者就无法推测出Cookie的内容了。<br></p>



<p class="wp-block-paragraph">此方法相对于CSRF&nbsp;Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便，只需进行请求中字段的对比，而不需要再进行查询和存储Token。</p>



<p class="wp-block-paragraph"><br>当然，此方法并没有大规模应用，其在大型网站上的安全性还是没有CSRF&nbsp;Token高，因为任何跨域都会导致前端无法获取Cookie中的字段（包括子域名之间）。</p>



<h2 class="wp-block-heading">参考资料</h2>



<ul class="wp-block-list"><li><a href="https://tech.meituan.com/2018/10/11/fe-security-csrf.html" data-type="URL" data-id="https://tech.meituan.com/2018/10/11/fe-security-csrf.html" target="_blank" rel="noreferrer noopener">前端安全系列（二）：如何防止CSRF攻击？</a></li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/218/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>什么是 Stream？为什么要有 Stream？</title>
		<link>https://www.addesp.com/archives/252</link>
					<comments>https://www.addesp.com/archives/252#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Thu, 16 Apr 2020 17:38:04 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=252</guid>

					<description><![CDATA[用了这么久的Stream类，还没了解过什么是stream以及为何要有stream。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">写在前面</h2>



<p class="wp-block-paragraph">用了这么久的 Stream 类，还没了解过什么是 stream 以及为何要有 stream。</p>



<h2 class="wp-block-heading">什么是 Stream？</h2>



<p class="wp-block-paragraph">stream 就是一些等待解释其含义的数据，这些数据通常具有至少一个下面列出的特点：</p>



<ul class="wp-block-list"><li>不要求在处理时全部到达。</li><li>数据的到达时机难以确定。</li><li>数据是按照一定顺序到达。</li><li>数据顺序读写比随机读写效率要高或只能顺序读写。</li><li>读取过一次的数据无法再次读取或再次读取时开销很大。</li></ul>



<p class="wp-block-paragraph">符合上列特点的数据典型的就是在线视频。</p>



<p class="wp-block-paragraph"><br>stream本身没有含义，它的含义取决于你如何解释。这也就是为什么面向对象语言中会有 FileStream，NetworkStream 等多种 Stream 类的派生类，这些类包含了对stream的解释方式。<br>如果以字节为最小处理单位则称字节流，以一位二进制为最小处理单位则称比特流，以此类推。</p>



<p class="wp-block-paragraph"><strong>目前来说stream已经不单单代表数据了，通常也代表与数据配套的操作。</strong></p>



<h2 class="wp-block-heading">为什么要有 Stream</h2>



<ul class="wp-block-list"><li>使得编程更加容易。如果统一使用&nbsp;stream&nbsp;模型处理数据，那么对应不同类型的数据只需要用到不同的&nbsp;stream&nbsp;处理模块，代码改动较少。</li><li>对于在线视频这种业务场景，使用&nbsp;stream&nbsp;来处理再好不过了。<ul><li>视频通过网络传输，即到达时间不确定。</li><li>视频只需要加载一部分就能开始播放以提高用户体验，即不要求在处理时全部到达。</li><li>视频必须要保证顺序正确，即数据是按照一定顺序到达。</li></ul></li><li>对于较大的数据无需一次性装入内存而是分批次处理。虽然 OS 提供的分页机制一定程度上缓解了这个问题，但是频繁的缺页会大大降低运行效率。</li></ul>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/252/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>32 位的操作系统也能使用超过4G的内存-PAE 技术简介</title>
		<link>https://www.addesp.com/archives/242</link>
					<comments>https://www.addesp.com/archives/242#respond</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Wed, 01 Apr 2020 16:03:14 +0000</pubDate>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[x86]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=242</guid>

					<description><![CDATA[概述 阅读本文章前请确认对X86下的分页机制有最基本的了解，否则将难以理解本文中提到的内容。本文介绍的内容均位 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">阅读本文章前请确认对X86下的分页机制有最基本的了解，否则将难以理解本文中提到的内容。<br>本文介绍的内容均位X86架构下的PAE技术，而不是ARM架构下的LPAE技术，虽然两者从本质来说是一样的。</p>



<p class="wp-block-paragraph">介绍下列内容：</p>



<ul class="wp-block-list"><li>PAE 技术因何问世</li><li>PAE 技术可以干什么</li><li>PAE 技术实现原理</li><li>为什么我们所用的 32 位操作系统只能使用 4GB 的内存</li></ul>



<h2 class="wp-block-heading">PAE 技术因何问世</h2>



<p class="wp-block-paragraph">PAE（Physical&nbsp;Address&nbsp;Extension，中文译名：物理地址扩展）技术最初是为了弥补32位地址在服务上不够用的情况。因为32位的地址最多只能支持4GB的内存，而这对于很多服务器来说是远远不够的，而那时64位的CPU和操作系统还没有普及，这个技术临时缓解了一下这类问题。</p>



<h2 class="wp-block-heading">PAE 技术可以干什么</h2>



<p class="wp-block-paragraph">PAE技术可以使得多个进程使用的总内存大于 4GB，但是单个进程的所使用的内存上限依然只有 4GB。</p>



<h2 class="wp-block-heading">PAE 技术实现原理</h2>



<ul class="wp-block-list"><li>地址线由 32 根增加到 36 根，寻址空间自然也就从 4GB 增加到了 64GB。</li><li>改动了分页机构使其可以适应36根地址线的寻址空间。</li></ul>



<p class="wp-block-paragraph">本文主要介绍分页机构的改动。</p>



<h3 class="wp-block-heading">未启动 PAE 下的分页机制</h3>



<h4 class="wp-block-heading">4K 分页</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="414" src="https://www.addesp.com/wp-content/uploads/2020/09/0-9.webp" alt="" class="wp-image-243"/><figcaption><em>未启动 PAE 下的 4K 分页</em></figcaption></figure>



<ol class="wp-block-list"><li>从 <strong>CR3</strong> 寄存器中取出<strong>页目录</strong>的基址。</li><li>取出<strong>线性地址</strong>中的 22 位~31 位共 10 位作为<strong>页目录项</strong>在<strong>页目录</strong>中的索引，由此找到<strong>页目录项</strong>。</li><li>根据<strong>页目录项</strong>中的内中找到对应的<strong>页表</strong>的基址。</li><li>取出<strong>线性地址</strong>中的 12 位~21 位共 10 位作为<strong>页表项</strong>在<strong>页表</strong>中的索引，由此找到<strong>页表项</strong>。</li><li>取出<strong>线性地址</strong>中的 0 位~11 位共 12 位作为<strong>页内偏移</strong>，从<strong>页表项</strong>中取出<strong>页基址</strong>，两者相加则到<strong>物理地址</strong>。</li></ol>



<h4 class="wp-block-heading">4M分页</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="567" height="405" src="https://www.addesp.com/wp-content/uploads/2020/09/1-7.webp" alt="" class="wp-image-244"/><figcaption><em>未启动&nbsp;PAE下的&nbsp;4M&nbsp;分页</em></figcaption></figure>



<ol class="wp-block-list"><li>从 <strong>CR3 </strong>寄存器中取出<strong>页表</strong>的基址。</li><li>取出<strong>线性地址</strong>中的 22 位~31 位共 10 位作为<strong>页表项</strong>在<strong>页表</strong>中的索引，由此找到<strong>页表项</strong>。</li><li>取出<strong>线性地址</strong>中的 0 位~21 位共 22 位作为<strong>页内偏移</strong>，从<strong>页表项</strong>中取出<strong>页基址</strong>，两者相加则到<strong>物理地址</strong>。</li></ol>



<h3 class="wp-block-heading">启动&nbsp;PAE&nbsp;下的分页机制</h3>



<h4 class="wp-block-heading"><>4K&nbsp;分页</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="621" height="396" src="https://www.addesp.com/wp-content/uploads/2020/09/2-7.webp" alt="" class="wp-image-245"/><figcaption><em>启动&nbsp;PAE下的&nbsp;4K分页</em></figcaption></figure>



<ol class="wp-block-list"><li>从 <strong>CR3 </strong>寄存器中取出<strong>页目录指针表</strong>的基址。</li><li>取出<strong>线性地址</strong>的 30 位~31 位共 2 位作为<strong>页目录指针</strong>在<strong>页目录指针表</strong>中的索引，由此找到<strong>页目录指针</strong>。</li><li>根据<strong>页目录指针</strong>获得对应的<strong>页目录</strong>的基址</li><li>取出<strong>线性地址</strong>中的 21 位~29 位共 9 位作为<strong>页目录项</strong>在<strong>页目录</strong>中的索引，由此找到<strong>页目录项</strong>。</li><li>根据<strong>页目录项</strong>找到对应<strong>页表</strong>的基址。</li><li>取出<strong>线性地址</strong>中的 12 位~20 位共 9 位作为<strong>页表项</strong>在<strong>页表</strong>中索引，由此找到<strong>页表项</strong>。</li><li>取出<strong>线性地址</strong>中的 0 位~11 位共 12 位作为<strong>页内偏移</strong>，从<strong>页表项</strong>中取出<strong>页基</strong>址，两者相加则到<strong>物理地址</strong>。</li></ol>



<h4 class="wp-block-heading">4M&nbsp;分页</h4>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="612" height="378" src="https://www.addesp.com/wp-content/uploads/2020/09/3-5.webp" alt="" class="wp-image-246"/><figcaption><em>启动 &nbsp;PAE 下的&nbsp;4M&nbsp;分页</em></figcaption></figure>



<ol class="wp-block-list"><li>从 <strong>CR3 </strong>寄存器中取出<strong>页表指针表</strong>的基址。</li><li>取出<strong>线性地址</strong>的 30 位~ 31 位共 2 位作为<strong>页表指针</strong>在<strong>页表指针表</strong>中的索引，由此找到<strong>页表指针</strong>。</li><li>根据<strong>页表指针</strong>找到对应的<strong>页表</strong>的基址。</li><li>取出<strong>线性地址</strong>中的 21 位~29 位共 9 位作为<strong>页表项</strong>在<strong>页表</strong>中的索引，由此找到<strong>页表项</strong>。</li><li>取出<strong>线性地址</strong>中的 0 位~21 位共 22 位作为<strong>页内偏移</strong>，从<strong>页表项</strong>中取出<strong>页基址</strong>，两者相加则到<strong>物理地址</strong>。</li></ol>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/242/feed</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>简要说明 HTTPS 是如何保证数据安全</title>
		<link>https://www.addesp.com/archives/284</link>
					<comments>https://www.addesp.com/archives/284#comments</comments>
		
		<dc:creator><![CDATA[ADD-SP]]></dc:creator>
		<pubDate>Sat, 28 Mar 2020 17:48:43 +0000</pubDate>
				<category><![CDATA[科普]]></category>
		<category><![CDATA[HTTPS]]></category>
		<category><![CDATA[网络安全]]></category>
		<guid isPermaLink="false">http://69a274df.addesp.com/?p=284</guid>

					<description><![CDATA[本文简要介绍HTTPS协议是如何保证数据在传输过程中的安全的。]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">概述</h2>



<p class="wp-block-paragraph">本文简要介绍HTTPS协议是如何保证数据在传输过程中的安全的。<br>由于是以介绍为主，所以不会涉及细节，而且<strong>肯定会有一些错误</strong>。如果想要详细了解可自行查阅资料。</p>



<h2 class="wp-block-heading">正文</h2>



<p class="wp-block-paragraph">在HTTPS协议出现前大家用的都是HTTP协议，这种协议传输的都是明文信息，任何人都可以窃取并篡改，毫无安全性可言。</p>



<p class="wp-block-paragraph"><br>HTTP协议的简要通信流程如下图：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="796" height="582" src="https://www.addesp.com/wp-content/uploads/2020/09/0-10.webp" alt="" class="wp-image-286"/><figcaption><em>HTTP&nbsp;协议简要通信流程</em></figcaption></figure>



<p class="wp-block-paragraph">由于传输的信息都是明文的，所以攻击者就可以窃取并篡改信息，如下图：</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="796" height="582" src="https://www.addesp.com/wp-content/uploads/2020/09/1-8.webp" alt="" class="wp-image-287"/><figcaption><em>中间人攻击</em></figcaption></figure>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">你可能会想，我们把信息加密不就可以了么，比如像下图一样</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="866" height="582" src="https://www.addesp.com/wp-content/uploads/2020/09/2-8.webp" alt="" class="wp-image-288"/><figcaption><em>使用对称密钥加密信息</em></figcaption></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>对称加密：双方持有同一个对称密钥，任何一方通过对称密钥加密的信息，对方可以通过相同的对称密钥解密出原文。</p></blockquote>



<p class="wp-block-paragraph">但是攻击者依然可以通过各种方式获取到对称密钥：</p>



<ul class="wp-block-list"><li>如果密钥是通过服务器下发给客户端，则直接截取服务器的消息就行了。</li><li>如果对称密钥是提前保存在双方的电脑上，那就可以通过某种手段入侵客户端或者服务器来窃取对称密钥。</li><li>其它方式。</li></ul>



<p class="wp-block-paragraph">截取到密钥后攻击者仍然可以进行攻击</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="956" height="582" src="https://www.addesp.com/wp-content/uploads/2020/09/3-6.webp" alt="" class="wp-image-289"/><figcaption><em>中间人攻击</em></figcaption></figure>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">那么我们就需要对“对称密钥”进行保护了，如下图</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1265" height="729" src="https://www.addesp.com/wp-content/uploads/2020/09/4-3.webp" alt="" class="wp-image-290"/><figcaption><em>保护对称密钥</em></figcaption></figure>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>非对称加密：一方持有私钥，一方持有公钥，使用公钥加密的信息只能被私钥解密。私钥不会用于加密，不过通常会用于加一段签名代表信息是完整，即信息是否丢失了一部分或者因为某种原因而发生的改变。</p></blockquote>



<p class="wp-block-paragraph">这次攻击者看似无法通过截取信息来获得对称密钥了，但是攻击者依然有机会发起攻击。</p>



<p class="wp-block-paragraph">不过先说明一点，目前公钥都是通过服务器下发的证书获得的，所以更接近下图相对来说更接近真实的通信流程</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="916" height="632" src="https://www.addesp.com/wp-content/uploads/2020/09/5-2.webp" alt="" class="wp-image-291"/><figcaption><em>服务器下发证书</em></figcaption></figure>



<p class="wp-block-paragraph">那么攻击者会怎样攻击呢？如下图（图片如果看不清可以右键图片选择“在新的标签页中打开图像”或类似选项）</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="956" height="1462" src="https://www.addesp.com/wp-content/uploads/2020/09/6-2.webp" alt="" class="wp-image-292"/><figcaption><em>中间人攻击</em></figcaption></figure>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">这时显然不能对公钥再次加密了，因为无法判断客户端得到的证书是真的，还是攻击者伪造的。<br>这时就需要第三方介入了</p>



<ol class="wp-block-list"><li>合法服务器的证书都是经过审核机构审核的，服务器在下发证书的时候就会下发经过审核的证书</li><li>客户端会存有经过审核的证书的列表。</li><li>客户端收到服务器下发的证书后会验证收到的证书是否经过了审核。</li><li>如果经过了审核则继续通信，反之断开连接或者弹出警告。</li></ol>



<p class="wp-block-paragraph">现在的通信流程图下图，也是HTTPS的大致通信流程</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="916" height="632" src="https://www.addesp.com/wp-content/uploads/2020/09/7-2.webp" alt="" class="wp-image-293"/><figcaption><em>更接近于&nbsp;HTTPS&nbsp;的通信流程</em></figcaption></figure>



<hr class="wp-block-separator"/>



<p class="wp-block-paragraph">不过HTTPS的一大软肋就是第三方证书审核机构，如果审核不严将攻击者的证书列入了合法证书列表，那么HTTPS所作的努力将全部白费。</p>



<p class="wp-block-paragraph"><br>总的来说，HTTPS增加了攻击者的攻击成本，使得数据的传输过程变得更加完全。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.addesp.com/archives/284/feed</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
	</channel>
</rss>
