<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Aidan</title>
    <description>Aidan&apos;s personal website</description>
    <link>https://www.aidandai.com/</link>
    <atom:link href="https://www.aidandai.com/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Fri, 17 Jan 2025 02:18:50 +0000</pubDate>
    <lastBuildDate>Fri, 17 Jan 2025 02:18:50 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>北京二环骑行攻略</title>
        <description>&lt;p&gt;来一次说走就走的骑行！&lt;/p&gt;

&lt;h1 id=&quot;1-骑行时间&quot;&gt;1 骑行时间&lt;/h1&gt;

&lt;p&gt;预计时间：2018-09-02 07:00 ~ 2018-09-02 12:00&lt;/p&gt;

&lt;h1 id=&quot;2-骑行路线&quot;&gt;2 骑行路线&lt;/h1&gt;

&lt;p&gt;出发点：西三旗桥&lt;/p&gt;

&lt;p&gt;目的地：西三旗桥&lt;/p&gt;

&lt;p&gt;全程路线，约 61.9 公里&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;西三旗桥 -&amp;gt; 北二环/积水潭桥入口（约 14.7 公里）: &lt;a href=&quot;https://j.map.baidu.com/k4n3Z&quot;&gt;https://j.map.baidu.com/k4n3Z&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-25-北京二环骑行攻略/000.png&quot; alt=&quot;西三旗桥 -&amp;gt; 北二环/积水潭桥入口&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;二环（约 32.7 公里）：&lt;a href=&quot;https://map.baidu.com/?newmap=1&amp;amp;shareurl=1&amp;amp;l=14.156322592179535&amp;amp;tn=B_NORMAL_MAP&amp;amp;hb=B_SATELLITE_STREET&amp;amp;c=12955325,4825918&amp;amp;s=s%26da_src%3DsearchBox.button%26wd%3D%E4%BA%8C%E7%8E%AF%E8%B7%AF-%E9%81%93%E8%B7%AF%26c%3D131%26src%3D0%26wd2%3D%26pn%3D0%26sug%3D0%26l%3D14%26from%3Dwebmap%26biz_forward%3D%7B%22scaler%22%3A1%2C%22styles%22%3A%22pl%22%7D%26sug_forward%3D%26auth%3DwYKf6J01X167vwNHxJ0KSSLVG%3DcFDvb1uxHBHxNRxRVtzljPyBYYx1GgvPUDZYOYIZuztFexLwDJvRqqkqHf2PWv3GuzVtUvhgMZSguxVVVVVVVVVtWvPYuxt8zv7u%40ZPuVtcvY1SGpuxVtn1GDf0wd0vyICIyOOSMCu0kMMxACwDhheFQW%26device_ratio%3D1&quot;&gt;北京二环路&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-25-北京二环骑行攻略/001.png&quot; alt=&quot;二环&quot; /&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;北二环/积水潭桥入口 -&amp;gt; 西三旗桥（约 14.5 公里）：&lt;a href=&quot;https://j.map.baidu.com/3_n3Z&quot;&gt;https://j.map.baidu.com/3_n3Z&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-25-北京二环骑行攻略/002.png&quot; alt=&quot;北二环/积水潭桥入口 -&amp;gt; 西三旗桥&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 25 Aug 2018 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/%E5%8C%97%E4%BA%AC%E4%BA%8C%E7%8E%AF%E9%AA%91%E8%A1%8C%E6%94%BB%E7%95%A5.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/%E5%8C%97%E4%BA%AC%E4%BA%8C%E7%8E%AF%E9%AA%91%E8%A1%8C%E6%94%BB%E7%95%A5.html</guid>
        
        <category>骑行</category>
        
        
        <category>生活随想录</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>凤凰岭一日骑行攻略</title>
        <description>&lt;p&gt;来一次说走就走的骑行！&lt;/p&gt;

&lt;h1 id=&quot;1-骑行时间&quot;&gt;1 骑行时间&lt;/h1&gt;

&lt;p&gt;出发时间：2018-08-19 08:00&lt;/p&gt;

&lt;p&gt;回程时间：2018-08-19 16:00&lt;/p&gt;

&lt;h1 id=&quot;2-骑行路线&quot;&gt;2 骑行路线&lt;/h1&gt;

&lt;p&gt;出发点：西三旗桥&lt;/p&gt;

&lt;p&gt;途径点：生命科学原 - 地铁站&lt;/p&gt;

&lt;p&gt;目的地：凤凰岭自然风景区&lt;/p&gt;

&lt;p&gt;行程：&lt;a href=&quot;https://j.map.baidu.com/l-XbZ&quot;&gt;https://j.map.baidu.com/l-XbZ&lt;/a&gt;；全程约 26.6 公里&lt;/p&gt;

&lt;p&gt;回程：&lt;a href=&quot;https://goo.gl/maps/11m5oQm4GSF2&quot;&gt;https://goo.gl/maps/11m5oQm4GSF2&lt;/a&gt;；全程约 27.4 公里&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-12-凤凰岭一日骑行攻略/001.png&quot; alt=&quot;行程&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-12-凤凰岭一日骑行攻略/002.png&quot; alt=&quot;回程&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;3-活动&quot;&gt;3 活动&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/4129.html&quot;&gt;北京凤凰岭自然风景公园（普通票：25元；3-4小时）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;午饭&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/5978850.html&quot;&gt;龙泉寺（门票：24元；600 米）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/806810.html&quot;&gt;修仙洞（门票：25元；900 米）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;推荐活动：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/813292.html&quot;&gt;车耳营村（1.4 公里）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/813063.html&quot;&gt;七王坟（1.9 公里）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/9431447.html&quot;&gt;紫云台香草园（2.5公里 ）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/6628359.html&quot;&gt;海淀醇亲王墓（2.9公里 ）&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.mafengwo.cn/poi/7318.html&quot;&gt;白虎涧自然风景区（门票60元，学生票/团票50元；2.7公里）&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;活动地图：&lt;a href=&quot;https://j.map.baidu.com/7X_bZ&quot;&gt;https://j.map.baidu.com/7X_bZ&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2018-08-12-凤凰岭一日骑行攻略/003.png&quot; alt=&quot;活动地图&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;4-骑行装备&quot;&gt;4 骑行装备&lt;/h1&gt;

&lt;h2 id=&quot;个人装备&quot;&gt;个人装备&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;身份证（可能有用）&lt;/li&gt;
  &lt;li&gt;学生证&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;团队装备&quot;&gt;团队装备&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;打气筒&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;后记：虽然圆满的完成了骑行，不过很遗憾由于近期暴雨凤凰岭关闭，没能上山去。&lt;/p&gt;
</description>
        <pubDate>Sun, 12 Aug 2018 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/%E5%87%A4%E5%87%B0%E5%B2%AD%E4%B8%80%E6%97%A5%E9%AA%91%E8%A1%8C%E6%94%BB%E7%95%A5.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/%E5%87%A4%E5%87%B0%E5%B2%AD%E4%B8%80%E6%97%A5%E9%AA%91%E8%A1%8C%E6%94%BB%E7%95%A5.html</guid>
        
        <category>骑行</category>
        
        
        <category>生活随想录</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>购买电视指南</title>
        <description>&lt;p&gt;最近打算给家里换个电视，特此整理一份攻略。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-智能电视品牌&quot; id=&quot;markdown-toc-1-智能电视品牌&quot;&gt;1 智能电视品牌&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-尺寸选择&quot; id=&quot;markdown-toc-2-尺寸选择&quot;&gt;2 尺寸选择&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-智能电视的基本配置要求&quot; id=&quot;markdown-toc-3-智能电视的基本配置要求&quot;&gt;3 智能电视的基本配置要求&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#31-屏幕&quot; id=&quot;markdown-toc-31-屏幕&quot;&gt;3.1 屏幕&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#32处理器&quot; id=&quot;markdown-toc-32处理器&quot;&gt;3.2处理器&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#33-gpu&quot; id=&quot;markdown-toc-33-gpu&quot;&gt;3.3 GPU&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#34-ram&quot; id=&quot;markdown-toc-34-ram&quot;&gt;3.4 RAM&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#35-rom&quot; id=&quot;markdown-toc-35-rom&quot;&gt;3.5 ROM&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#36-蓝牙&quot; id=&quot;markdown-toc-36-蓝牙&quot;&gt;3.6 蓝牙&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#37-语音搜索&quot; id=&quot;markdown-toc-37-语音搜索&quot;&gt;3.7 语音搜索&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#38-操作系统&quot; id=&quot;markdown-toc-38-操作系统&quot;&gt;3.8 操作系统&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#39-接口&quot; id=&quot;markdown-toc-39-接口&quot;&gt;3.9 接口&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-最终选择&quot; id=&quot;markdown-toc-4-最终选择&quot;&gt;4 最终选择&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#参考链接&quot; id=&quot;markdown-toc-参考链接&quot;&gt;参考链接&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1-智能电视品牌&quot;&gt;1 智能电视品牌&lt;/h1&gt;

&lt;p&gt;海信、索尼、夏普、创维、长虹、康佳、TCL、小米、微鲸、乐视、酷开等。&lt;/p&gt;

&lt;h1 id=&quot;2-尺寸选择&quot;&gt;2 尺寸选择&lt;/h1&gt;

&lt;p&gt;2.5 米内适合 50 寸以下，2.5 米到 3 米适合 55 寸，3 米到 3.5 米适合 65 寸，3.5 米到 4 米适合 70 寸以上。（以上为建议，具体以个人喜好为准）&lt;/p&gt;

&lt;h1 id=&quot;3-智能电视的基本配置要求&quot;&gt;3 智能电视的基本配置要求&lt;/h1&gt;

&lt;h2 id=&quot;31-屏幕&quot;&gt;3.1 屏幕&lt;/h2&gt;

&lt;p&gt;RGB 真 4K 屏幕（不要选 RGBW 四色 4K 屏幕）&lt;/p&gt;

&lt;p&gt;国产屏幕进口屏幕均可，软屏硬屏均可，不太推荐曲面屏，建议选择平板电视。&lt;/p&gt;

&lt;h2 id=&quot;32处理器&quot;&gt;3.2处理器&lt;/h2&gt;

&lt;p&gt;Cortex－A53 四核处理器（一般频率越高性能越强，如 1.7GHZ &amp;gt; 1.4GHZ）、Cortex－A73 双核处理器（性能差不多）&lt;/p&gt;

&lt;p&gt;最高档的是 A72 双核 + A53 双核处理器，这是目前顶级智能电视 soc 了。&lt;/p&gt;

&lt;h2 id=&quot;33-gpu&quot;&gt;3.3 GPU&lt;/h2&gt;

&lt;p&gt;ARM Mali-450 MP4 及以上（一般选好 CPU 就行了，GPU 也不会差）&lt;/p&gt;

&lt;h2 id=&quot;34-ram&quot;&gt;3.4 RAM&lt;/h2&gt;

&lt;p&gt;至少 2GB（索尼还在用 1GB，海信 3GB 都常见，4GB 运行内存机型都有了！）&lt;/p&gt;

&lt;h2 id=&quot;35-rom&quot;&gt;3.5 ROM&lt;/h2&gt;

&lt;p&gt;至少8GB存储空间。&lt;/p&gt;

&lt;h2 id=&quot;36-蓝牙&quot;&gt;3.6 蓝牙&lt;/h2&gt;

&lt;p&gt;蓝牙 4.0 以上&lt;/p&gt;

&lt;h2 id=&quot;37-语音搜索&quot;&gt;3.7 语音搜索&lt;/h2&gt;

&lt;p&gt;支持遥控器语音搜索（遥控器输入文字是噩梦，语音输入才方便！）&lt;/p&gt;

&lt;h2 id=&quot;38-操作系统&quot;&gt;3.8 操作系统&lt;/h2&gt;

&lt;p&gt;安卓 5.0 以上，低于安卓 5.0 可能是旧款机型，不建议选择。&lt;/p&gt;

&lt;h2 id=&quot;39-接口&quot;&gt;3.9 接口&lt;/h2&gt;

&lt;p&gt;至少有一个USB 3.0，至少有一个HDMI 2.0（或者HDMI ARC）&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/blog/2018-07-29-shopping-tv-guide/000.jpg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;4-最终选择&quot;&gt;4 最终选择&lt;/h1&gt;

&lt;p&gt;&lt;a href=&quot;https://www.mi.com/mitv4A/65/&quot;&gt;小米电视 4A 65英寸&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;参考链接&quot;&gt;参考链接&lt;/h1&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/21943798&quot;&gt;液晶电视哪个牌子好？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/37641477&quot;&gt;为什么要买智能电视而不是普通电视+电视盒子？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/37640550&quot;&gt;为什么买智能电视不是只看屏幕好坏就行的？&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/278850694/answer/403337652&quot;&gt;65寸6000以内预算电视推荐？&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sun, 29 Jul 2018 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/shopping-tv-guide.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/shopping-tv-guide.html</guid>
        
        <category>TV</category>
        
        
        <category>生活随想录</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>WebSocket 协议简介</title>
        <description>&lt;h1 id=&quot;websocket-协议简介&quot;&gt;WebSocket 协议简介&lt;/h1&gt;

&lt;p&gt;首先我们来看看什么是 WebSocket, 下面是引用自 wikipedia 的定义：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;WebSocket 是一种通过单个 TCP 连接提供全双工通讯的计算机通信协议。WebSocket 协议在2011年由 IETF 标准化为 &lt;a href=&quot;https://tools.ietf.org/html/rfc6455&quot;&gt;RFC 6455&lt;/a&gt;，并且 WebSocket API 也被 W3C 定为标准。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;WebSocket 是一种不同于 HTTP 的应用层协议。WebSocket 和 HTTP 协议都位于 &lt;a href=&quot;https://en.wikipedia.org/wiki/OSI_model&quot;&gt;OSI 模型&lt;/a&gt;的第7层，因此都依赖于第4层的 TCP 协议。尽管它们不同，但 RFC 6455 规定 WebSocket 被设计为通过HTTP 的80和443端口工作，以及支持 HTTP 代理和中介，从而使其与 HTTP 协议兼容。为了实现可靠性，WebSocket 握手使用 &lt;a href=&quot;https://en.wikipedia.org/wiki/HTTP/1.1_Upgrade_header&quot;&gt;HTTP Upgrade header&lt;/a&gt; 从 HTTP 协议更改为 WebSocket 协议。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;WebSocket 协议支持浏览器和 Web 服务器之间的交互，具有较低的开销，便于从服务器进行实时数据传输。这可以通过提供标准化的方式使服务器将内容发送到浏览器而不被客户端请求，并允许在保持连接打开的同时传递消息。以这种方式，可以在浏览器和服务器之间进行全双工通讯。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;通过上面的介绍我们知道 WebSocket 是一种应用层协议，支持全双工通信。但它与 HTTP 有什么关系，以及是怎么实现全双工通信的呢？等等问题。接下来我们主要通过简单的介绍 WebSocket 协议来解答这些疑惑。&lt;/p&gt;

&lt;p&gt;这里大概整理了下，详细内容可以查看：&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#websocket-协议简介&quot; id=&quot;markdown-toc-websocket-协议简介&quot;&gt;WebSocket 协议简介&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#1-websocket-uri&quot; id=&quot;markdown-toc-1-websocket-uri&quot;&gt;1 WebSocket URI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-打开阶段握手&quot; id=&quot;markdown-toc-2-打开阶段握手&quot;&gt;2 打开阶段握手&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#3-数据帧&quot; id=&quot;markdown-toc-3-数据帧&quot;&gt;3 数据帧&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#31-基本帧协议&quot; id=&quot;markdown-toc-31-基本帧协议&quot;&gt;3.1 基本帧协议&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#32-客户端到服务器掩码&quot; id=&quot;markdown-toc-32-客户端到服务器掩码&quot;&gt;3.2 客户端到服务器掩码&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#33-分片fragmentation&quot; id=&quot;markdown-toc-33-分片fragmentation&quot;&gt;3.3 分片（Fragmentation）&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#34-控制帧&quot; id=&quot;markdown-toc-34-控制帧&quot;&gt;3.4 控制帧&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#35-数据帧&quot; id=&quot;markdown-toc-35-数据帧&quot;&gt;3.5 数据帧&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#36-可扩展性&quot; id=&quot;markdown-toc-36-可扩展性&quot;&gt;3.6 可扩展性&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#4-发送和接收数据&quot; id=&quot;markdown-toc-4-发送和接收数据&quot;&gt;4 发送和接收数据&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#41-发送数据&quot; id=&quot;markdown-toc-41-发送数据&quot;&gt;4.1 发送数据&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#42-接收数据&quot; id=&quot;markdown-toc-42-接收数据&quot;&gt;4.2 接收数据&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#5-websocket-协议中常见问题&quot; id=&quot;markdown-toc-5-websocket-协议中常见问题&quot;&gt;5 WebSocket 协议中常见问题&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#51-websockethttp-与-tcp&quot; id=&quot;markdown-toc-51-websockethttp-与-tcp&quot;&gt;5.1 WebSocket、HTTP 与 TCP&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#52-websocket-与-scoket&quot; id=&quot;markdown-toc-52-websocket-与-scoket&quot;&gt;5.2 WebSocket 与 Scoket&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1-websocket-uri&quot;&gt;1 WebSocket URI&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ws-URI = &quot;ws:&quot; &quot;//&quot; host [ &quot;:&quot; port ] path [ &quot;?&quot; query ]
wss-URI = &quot;wss:&quot; &quot;//&quot; host [ &quot;:&quot; port ] path [ &quot;?&quot; query ]

host = &amp;lt;host, defined in [RFC3986], Section 3.2.2&amp;gt;
port = &amp;lt;port, defined in [RFC3986], Section 3.2.3&amp;gt;
path = &amp;lt;path-abempty, defined in [RFC3986], Section 3.3&amp;gt;
query = &amp;lt;query, defined in [RFC3986], Section 3.4&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;2-打开阶段握手&quot;&gt;2 打开阶段握手&lt;/h1&gt;

&lt;p&gt;一个典型的 WebSocket 握手请求如下：&lt;/p&gt;

&lt;p&gt;客户端请求&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;GET /chat HTTP/1.1
Host: server.example.com
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;服务器响应&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HTTP/1.1 101 Switching Protocols
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;(1) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Upgrade&lt;/code&gt; 表示的意思是：客户端准备使用 WebSocket 协议进行通讯，服务端如果支持的话，咱们就换 WebSocket 协议吧！&lt;/p&gt;

&lt;p&gt;(2) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Key&lt;/code&gt; 是一种验证服务端是不是只是 WebSocket 的验证算法。与服务器端响应中的 Sec-WebSocket-Accept 是对应的。&lt;/p&gt;

&lt;p&gt;(3) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Protocol&lt;/code&gt; 头字段用于 WebSocket 打开握手。它从客户端发送到服务器，并从服务器返回到客户端，以确认连接的子协议。 这使脚本能够选择子协议，并确保服务器同意服务该子协议。&lt;/p&gt;

&lt;p&gt;(4) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Version&lt;/code&gt; 是指浏览器支持的 WebSocket 版本号。需要注意的是这里不会出现9~12的版本号，因为 WebSocket 协议规定 9~12 是保留字。&lt;/p&gt;

&lt;p&gt;(5) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Accept&lt;/code&gt; 与 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Key&lt;/code&gt; 的对应算法是：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Sec-WebSocket-Accept = base64(
    hash1(
        Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11
    )
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果返回的 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Accept&lt;/code&gt; 不对，在 chrome 下会出现 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Accept&lt;/code&gt; dismatch 的错误。&lt;/p&gt;

&lt;p&gt;(6) HTTP Status: Response 返回的是 101 , 代表服务器端说 ”我们双方后面就按照 webscoket 协议来进行数据传输吧“。&lt;/p&gt;

&lt;p&gt;(7) &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sec-WebSocket-Extensions&lt;/code&gt; 头字段用于 WebSocket 打开握手。 它最初从客户端发送到服务器，然后从服务器发送到客户端，以商定在连接期间使用的一组协议级扩展。&lt;/p&gt;

&lt;p&gt;上面介绍了 WebSocket 握手阶段的一些头字段，我们显然也能看出来他还是 HTTP 请求。握手还是经典的三次握手，在这里就不在讨论了。WebSocket 的建立总结如下：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;客户端浏览器首先要向服务器发起一个 HTTP 请求，然后等待服务器响应。需要说明的是：这个请求和通常的 HTTP 请求不同，包含了一些附加头信息，其中附加头信息 ”Upgrade: WebSocket“ 表明这是一个申请协议升级的 HTTP 请求。&lt;/li&gt;
  &lt;li&gt;服务器解析这些附加的头信息，然后返回握手响应，告诉浏览器将后续的数据按照 WebSocket 协议指定的数据格式传过来。此时，客户端和服务器端的 WebSocket 连接就建立起来了。&lt;/li&gt;
  &lt;li&gt;客户端和服务器端有任何需要传递的数据时，可以通过这个连接通道自由的传递信息。&lt;/li&gt;
  &lt;li&gt;这个连接会持续存在，直到客户端或服务器的某一方主动关闭连接。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;整个流程如下图：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/001.png&quot; alt=&quot;WebSocket 连接&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;3-数据帧&quot;&gt;3 数据帧&lt;/h1&gt;

&lt;p&gt;在 WebSocket 协议中，数据通过帧序列来传输。 为避免混淆网络中间件（例如拦截代理）和出于安全原因，客户端必须掩码（mask）它发送到服务器的所有帧（注意不管 WebSocket 协议是否运行在 TLS 至上，掩码都要做。） 当收到一个没有掩码的帧时，服务器必须关闭连接。 在这种情况下，服务器可能发送状态码 1002（协议错误）的Close 帧。 服务器必须不掩码发送到客户端的所有帧。 如果客户端检测到掩码的帧，它必须关闭连接。 在这种情况下，它可能发送状态码1002（协议错误）。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://chenjianlong.gitbooks.io/rfc-6455-websocket-protocol-in-chinese/content/section5/section5.html&quot;&gt;基本帧协议&lt;/a&gt; 定义了带有操作码（opcode）的帧类型、负载长度、和用于“扩展数据”与“应用数据”及它们一起定义的“负载数据”的指定位置。 某些字节和操作码保留用于未来协议的扩展。&lt;/p&gt;

&lt;p&gt;一个数据帧可以被客户端或者服务器在打开阶段握手完成之后和端点发送 Close 帧之前的任何时候传输。&lt;/p&gt;

&lt;h2 id=&quot;31-基本帧协议&quot;&gt;3.1 基本帧协议&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/003.png&quot; alt=&quot;基本帧协议&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/004.png&quot; alt=&quot;基本帧协议&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;FIN&lt;/code&gt;(1 bit): 是否为消息的最后一个数据帧&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSV1&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSV2&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;RSV3&lt;/code&gt;(每个1 bit): 必须是0，除非一个扩展协商为非零值定义含义。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Opcode&lt;/code&gt;(4 bits): 定义了“负载数据”的解释（这4位转为16进制值表示的意思如下）
    &lt;ul&gt;
      &lt;li&gt;%x0 代表一个继续帧&lt;/li&gt;
      &lt;li&gt;%x1 代表一个文本帧&lt;/li&gt;
      &lt;li&gt;%x2 代表一个二进制帧&lt;/li&gt;
      &lt;li&gt;%x3～7 保留用于未来的非控制帧&lt;/li&gt;
      &lt;li&gt;%x8 代表连接关闭&lt;/li&gt;
      &lt;li&gt;%x9 代表ping&lt;/li&gt;
      &lt;li&gt;%xA 代表pong&lt;/li&gt;
      &lt;li&gt;%xB～F 保留用于未来的控制帧&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Mask&lt;/code&gt;(1 bit): 定义 “负载数据”是否是经过掩码的。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Payload length&lt;/code&gt;(7 bits, 7 + 16 bits, 或者 7+64 bits): 定义  “负载数据”的长度，以字节为单位。
    &lt;ul&gt;
      &lt;li&gt;a. 如果数据长度小于等于125，那么该7位用来表示实际数据长度。&lt;/li&gt;
      &lt;li&gt;b. 如果数据长度为126到65535(65535 = 1111111111111111)之间，该7位的值固定为126，也就是1111110。往后扩展两个字节（16位，第三个区块）用于存储实际数据长度。&lt;/li&gt;
      &lt;li&gt;c. 如果数据长度大于65535，该7位的值固定为127，也就是1111111。往后扩展8个字节（64位，第三个区块）用于存储实际数据长度。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Masking-key&lt;/code&gt;(0 or 4 bytes): 该区块用于存储掩码密钥（masking key），只有在第二字节中的mask为1，也就是消息进行了掩码处理时才有，否则没有。所以服务端向客户端发送消息就没有这块。&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Payload data&lt;/code&gt; [ (x+y) bytes ]: “负载数据”定义为“扩展数据”连接“应用数据”。
    &lt;ul&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Extension data&lt;/code&gt;(x bytes): “扩展数据”是 0 字节除非已经协商了一个扩展。 任何扩展必须指定“扩展数据”的长度，或长度是如何计算的，以及扩展如何使用必须在打开阶段握手期间协商。 如果存在，“扩展数据”包含在总负载长度中。&lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Application data&lt;/code&gt;(y bytes): 任意的“应用数据”，占用“扩展数据”之后帧的剩余部分。 “应用数据”的长度等于负载数据长度减去“扩展数据”长度。&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;32-客户端到服务器掩码&quot;&gt;3.2 客户端到服务器掩码&lt;/h2&gt;

&lt;p&gt;从浏览器向服务器发送的 WebSocket 帧在实际内容之前还有一个 4字节的掩码，这是为了不常见的安全原因，以及改进与现有 HTTP 代理的兼容性。&lt;/p&gt;

&lt;p&gt;WebSocket 协议要求客户端所发送的帧必需掩码，帧头在第二个字节的第一位表示该帧是否使用了掩码。&lt;/p&gt;

&lt;p&gt;WebSocket 服务器接收的每个载荷在处理之前首先需要处理掩码，解除掩码之后，服务器将得到原始消息内容。二进制消息可以直接交付，文本消息将进行 UTF-8 解码并输出到字符串中。&lt;/p&gt;

&lt;p&gt;没理解，可以讨论下？&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.zhihu.com/question/40019896&quot;&gt;Websocket为什么在客户端向服务端发送报文的时候需要掩码加密，而服务端向客户端不需要呢？&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;33-分片fragmentation&quot;&gt;3.3 分片（Fragmentation）&lt;/h2&gt;

&lt;p&gt;分片的主要目的是允许当发送一个未知大小的消息时可以直接开始而不用缓存那条消息。如果消息不能被分片，那么端点将不得不缓冲整个消息以便在首字节发送之前统计出它的长度。对于分片，服务器或中间件可以选择一个合适大小的缓冲，当缓冲满时，写一个片段到网络。&lt;/p&gt;

&lt;p&gt;第二个分片的用例是用于多路复用，一个逻辑通道上的一个大消息独占输出通道是不可取的，因此多路复用需要可以分割消息为更小的分段来更好的共享输出通道。&lt;/p&gt;

&lt;h2 id=&quot;34-控制帧&quot;&gt;3.4 控制帧&lt;/h2&gt;

&lt;p&gt;控制帧用于传达有关 WebSocket 的状态。 控制帧可以插入到分片消息的中间。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Close&lt;/li&gt;
  &lt;li&gt;Ping&lt;/li&gt;
  &lt;li&gt;Pong&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;当收到一个 Ping 帧时，一个端点必须在响应中发送一个Pong帧，除非它早已接收到一个关闭帧。 它应该尽可能快地以 Pong 帧响应。&lt;/p&gt;

&lt;p&gt;一个端点可以在连接建立之后并在连接关闭之前的任何时候发送一个 Ping 帧(一个 Ping 即可以充当一个 keepalive，也可以作为验证远程端点仍可响应的手段)。&lt;/p&gt;

&lt;h2 id=&quot;35-数据帧&quot;&gt;3.5 数据帧&lt;/h2&gt;

&lt;p&gt;数据帧由操作码最高位是0的操作码标识。当前为数据帧定义的操作码包括0x1(文本)、0x2（二进制）。&lt;/p&gt;

&lt;h2 id=&quot;36-可扩展性&quot;&gt;3.6 可扩展性&lt;/h2&gt;

&lt;p&gt;协议被设计为允许扩展，这将增加功能到基础协议。 端点的一个连接必须在打开阶段握手期间协商使用的任何扩展。&lt;/p&gt;

&lt;h1 id=&quot;4-发送和接收数据&quot;&gt;4 发送和接收数据&lt;/h1&gt;

&lt;h2 id=&quot;41-发送数据&quot;&gt;4.1 发送数据&lt;/h2&gt;

&lt;p&gt;为了发送一个 WebSocket 消息，端点必须执行以下步骤:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;端点必须确保 WebSocket 连接处于 OPEN 状态。 如果在任何时刻WebSocket连接的状态改变了，端点必须终止以下步骤。&lt;/li&gt;
  &lt;li&gt;端点必须封装数据到一个 WebSocket 帧。如果要发送的数据太大，或如果在端点想要开始发生数据时数据作为一个整体不可用，端点可以按照基本帧协议交替地封装数据到一系列的帧中。&lt;/li&gt;
  &lt;li&gt;第一个包含数据的帧的操作码（帧-opcode）必须按照基本帧协议被设置为适当的值用于接收者解释数据是文本还是二进制数据。&lt;/li&gt;
  &lt;li&gt;包含数据的最后帧的 FIN 位（帧-fin）必须基本帧协议的定义设置为 1&lt;/li&gt;
  &lt;li&gt;如果数据正由客户端发送，帧必须被掩码。&lt;/li&gt;
  &lt;li&gt;如果任何扩展已经协商用于 WebSocket 连接，额外的考虑可以按照这些扩展定义来应用。&lt;/li&gt;
  &lt;li&gt;已成形的帧必须在底层网络连接之上传输。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;42-接收数据&quot;&gt;4.2 接收数据&lt;/h2&gt;

&lt;p&gt;为了接收 WebSocket 数据，端点监听底层网络连接。传入数据必须按照基本帧协议解析为 WebSocket 帧。 如果接收到一个控制帧，帧必须按照控制帧的定义来处理。 当接收到一个数据帧时，端点必须注意操作码（帧-opcode）定义的数据的 /type/。 一个帧的“应用数据”被定义为消息的 /data/ 。 如果帧由一个未分片的消息组成，这是说已经接收到一个 WebSocket 消息，其类型为 /type/ 且数据为 /data/ 。 如果帧是一个分片消息的一部分，随后数据帧的“应用数据”连接在一起形成 /data/ 。当接收到由 FIN 位（帧-fin）指示的最后的片段时，这是说已经接收到一个 WebSocket 消息，其数据为/data/（由连续片段的“应用数据”组成）且类型为 /type/（分配消息的第一个帧指出）。 随后的数据帧必须被解释为属于一个新的 WebSocket 消息。&lt;/p&gt;

&lt;p&gt;扩展可以改变数据如何读的语义，尤其包括什么组成一个消息的边界。 扩展，除了在负载中的“应用数据”之前添加“扩展数据”外，也可以修改“应用数据”（例如压缩它）。&lt;/p&gt;

&lt;h1 id=&quot;5-websocket-协议中常见问题&quot;&gt;5 WebSocket 协议中常见问题&lt;/h1&gt;

&lt;h2 id=&quot;51-websockethttp-与-tcp&quot;&gt;5.1 WebSocket、HTTP 与 TCP&lt;/h2&gt;

&lt;p&gt;WebScoket 和 HTTP 都属于应用层协议，它们都通过 TCP 协议传输数据&lt;/p&gt;

&lt;p&gt;WebSocket 是全双工通信协议，HTTP 是单向的通信协议&lt;/p&gt;

&lt;p&gt;对于 WebSocket 来说，它必须依赖 HTTP 协议进行一次握手，握手成功后，数据就直接从 TCP 通道传输，此后就与 HTTP 无关了&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/002.png&quot; alt=&quot;WebSocket、HTTP 与 TCP&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;52-websocket-与-scoket&quot;&gt;5.2 WebSocket 与 Scoket&lt;/h2&gt;

&lt;p&gt;Scoket 不是一个协议，它是应用层与 TCP/IP 协议族通信的中间软件抽象层，是一组接口。它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面。对用户来说，一组简单的接口就是全部，让 Socket 去组织数据，以符合指定的协议。&lt;/p&gt;

&lt;p&gt;而 WebScoket 则不同，它是一个完整的应用层协议，包含一整套标准的 API。所以，从使用上来说，WebSocket 更易用，而 Scoket 更灵活。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/003.jpg&quot; alt=&quot;Socket&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-10-22-websocket-principle/005.jpg&quot; alt=&quot;Socket&quot; /&gt;&lt;/p&gt;
</description>
        <pubDate>Sun, 22 Oct 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/websocket-principle.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/websocket-principle.html</guid>
        
        <category>WebSocket</category>
        
        
        <category>web 通信</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>通过例子来解释函数防抖(debounce)和函数节流(throttle)</title>
        <description>&lt;blockquote&gt;

  &lt;p&gt;原文: &lt;a href=&quot;https://css-tricks.com/debouncing-throttling-explained-examples/&quot;&gt;David Corbacho: Debouncing and Throttling Explained Through Examples&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;译者: &lt;a href=&quot;https://github.com/AidanDai&quot;&gt;Aidan&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;

&lt;p&gt;以下是 David Corbacho 的客座邮件，David Corbacho 是一位来自伦敦的前端开发工程师。我们以前&lt;a href=&quot;https://css-tricks.com/the-difference-between-throttling-and-debouncing/&quot;&gt;已经讨论过这个话题了&lt;/a&gt;，但是这次，David 将通过互动的例子去讲解它们，通过互动的例子使得它们的概念更清楚。&lt;/p&gt;

&lt;p&gt;函数防抖和函数节流是两个相似的去控制某个函数在一段被允许执行多少次的技术，但它们的本质确是不同的。&lt;/p&gt;

&lt;p&gt;当我们将函数注册到特定的 DOM 的事件上时，这时候函数防抖和函数节流将特别有用。为什么这么说呢？因为我们在事件和函数的执行之间加了一层自己的控制，请记住，我们不能控制这写 DOM 事件被触发的频率，它是变化的。&lt;/p&gt;

&lt;p&gt;让我么来聊聊 scroll 事件吧，请看下面这个例子：&lt;/p&gt;

&lt;iframe id=&quot;cp_embed_PZOZgB&quot; src=&quot;//codepen.io/dcorb/embed/PZOZgB?height=257&amp;amp;theme-id=1&amp;amp;slug-hash=PZOZgB&amp;amp;default-tab=result&amp;amp;user=dcorb&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; height=&quot;257&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot; name=&quot;CodePen Embed&quot; title=&quot;CodePen Embed 7&quot; class=&quot;cp_embed_iframe &quot; style=&quot;width: 100%; overflow: hidden; height: 250px&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;当我们通过触摸触控板，滚动滚轮或者拖动滚动条时很容易美秒触发30多次 scroll 事件，而且在测试中，当慢慢滚动滚动条时每秒可能触发多达100个 scroll 事件。您的滚动事件处理函数准备好以这个速率执行了吗？&lt;/p&gt;

&lt;p&gt;在 2011, Twitter 网站出了一个问题：当您向下滚动您的 Twitter Feed 时，它变的非常慢并且失去了响应。John Resig 发表了&lt;a href=&quot;https://johnresig.com/blog/learning-from-twitter/&quot;&gt;一篇关于这个问题的博客&lt;/a&gt;，在博客里他解释了直接将昂贵的功能附加到滚动事件是多么糟糕一个想法。&lt;/p&gt;

&lt;p&gt;John（五年前的那个时候）的建议解决方案是在 onScroll 事件之外每 250ms 运行一次循环。这样处理程序变没有耦合到事件。通过这种简单的技术，我们可以避免破坏用户体验。&lt;/p&gt;

&lt;p&gt;现在处理事件的方法稍微复杂一些。让我介绍一下 Debounce，Throttle 和 requestAnimationFrame。我们还会查看匹配的用例。&lt;/p&gt;

&lt;h1 id=&quot;debounce&quot;&gt;Debounce&lt;/h1&gt;

&lt;p&gt;函数防抖技术允许你将多个顺序触发的事件绑定函数合并成一个。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-09-12-debouncing-and-throttling-explained-through-examples/001.webp&quot; alt=&quot;Debounce&quot; /&gt;&lt;/p&gt;

&lt;p&gt;想像一下你在电梯里。电梯门即将关闭，这时候突然另一个人试图进去。电梯还没有改变它的楼层，电梯门再一次打开了。现在这件事又发生在了另一个人身上。电梯在延迟执行它的功能（移动楼层），这样它就优化了资源。&lt;/p&gt;

&lt;p&gt;可以自己试一下，点击 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Trigger area&lt;/code&gt; 的按钮或者在按钮内移动鼠标。&lt;/p&gt;

&lt;iframe id=&quot;cp_embed_KVxGqN&quot; src=&quot;//codepen.io/dcorb/embed/KVxGqN?height=391&amp;amp;theme-id=1&amp;amp;slug-hash=KVxGqN&amp;amp;default-tab=result&amp;amp;user=dcorb&quot; scrolling=&quot;no&quot; frameborder=&quot;0&quot; height=&quot;391&quot; allowtransparency=&quot;true&quot; allowfullscreen=&quot;true&quot; name=&quot;CodePen Embed&quot; title=&quot;CodePen Embed 6&quot; class=&quot;cp_embed_iframe &quot; style=&quot;width: 100%; overflow: hidden; height: 380px;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;可以看到多个快速被触发的顺序事件被一个 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;debounced event&lt;/code&gt; 代表，而且如果这些被触发的事件间隔事件较久，去抖动将不会发生。&lt;/p&gt;

&lt;h1 id=&quot;leading-edge-or-immediate&quot;&gt;Leading edge (or “immediate”)&lt;/h1&gt;
</description>
        <pubDate>Tue, 12 Sep 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/debouncing-and-throttling-explained-through-examples.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/debouncing-and-throttling-explained-through-examples.html</guid>
        
        <category>debounce</category>
        
        <category>throttle</category>
        
        
        <category>web</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>加盐 hash 保存密码的正确方式</title>
        <description>&lt;p&gt;如果你是Web开发者，你很可能需要开发一个用户账户系统。这个系统最重要的方面，就是怎样保护用户的密码。存放帐号的数据库经常成为入侵的目标，所以你必须做点什么来保护密码，以防网站被攻破时发生危险。最好的办法就是对密码进行加盐哈希，这篇文章将介绍它是如何做到这点。&lt;/p&gt;

&lt;p&gt;在对密码进行哈希加密的问题上，人们有许多争论和误解，这大概是由于网络上广泛的误传吧。密码哈希是一件非常简单的事情，但是依然有很多人理解错误了。本文阐述的并不是进行密码哈希唯一正确的方法，但是会告诉你为什么这样是正确的。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;**郑重警告**：如果你在试图编写自己的密码哈希代码，赶紧停下来！那太容易搞砸了。即使你受过密码学的高等教育，也应该听从这个警告。这是对所有人说的：不要自己写加密函数！安全存储密码的难题现在已经被解决了，请使用phpass或者本文给出的一些源代码。&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;如果因为某些原因你忽视了上面那个红色警告，请翻回去好好读一遍，我是认真的。这篇文章的目的不是教你研究出自己的安全算法，而是讲解为什么密码应该被这样储存。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-为什么密码需要进行哈希&quot; id=&quot;markdown-toc-1-为什么密码需要进行哈希&quot;&gt;1 为什么密码需要进行哈希？&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#2-如何破解哈希加密&quot; id=&quot;markdown-toc-2-如何破解哈希加密&quot;&gt;2 如何破解哈希加密&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#21-字典和暴力破解攻击dictionary-and-brute-force-attacks&quot; id=&quot;markdown-toc-21-字典和暴力破解攻击dictionary-and-brute-force-attacks&quot;&gt;2.1 字典和暴力破解攻击(Dictionary and Brute Force Attacks)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#22-查表破解lookup-tables&quot; id=&quot;markdown-toc-22-查表破解lookup-tables&quot;&gt;2.2 查表破解(Lookup Tables)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#23-反向查表破解reverse-lookup-tables&quot; id=&quot;markdown-toc-23-反向查表破解reverse-lookup-tables&quot;&gt;2.3 反向查表破解(Reverse Lookup Tables)&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#24-彩虹表-rainbow-tables&quot; id=&quot;markdown-toc-24-彩虹表-rainbow-tables&quot;&gt;2.4 彩虹表 (Rainbow Tables)&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-加盐&quot; id=&quot;markdown-toc-3-加盐&quot;&gt;3 加盐&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#31-实现加盐哈希中的错误&quot; id=&quot;markdown-toc-31-实现加盐哈希中的错误&quot;&gt;3.1 实现加盐哈希中的错误&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#311-短盐值和盐值重复&quot; id=&quot;markdown-toc-311-短盐值和盐值重复&quot;&gt;3.1.1 短盐值和盐值重复&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#312-两次哈希和组合哈希函数&quot; id=&quot;markdown-toc-312-两次哈希和组合哈希函数&quot;&gt;3.1.2 两次哈希和组合哈希函数&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#32-哈希碰撞&quot; id=&quot;markdown-toc-32-哈希碰撞&quot;&gt;3.2 哈希碰撞&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#33-正确的做法恰当使用哈希加密&quot; id=&quot;markdown-toc-33-正确的做法恰当使用哈希加密&quot;&gt;3.3 正确的做法：恰当使用哈希加密&lt;/a&gt;            &lt;ul&gt;
              &lt;li&gt;&lt;a href=&quot;#331-基本要素加盐哈希&quot; id=&quot;markdown-toc-331-基本要素加盐哈希&quot;&gt;3.3.1 基本要素：加盐哈希&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#332-存储密码的步骤&quot; id=&quot;markdown-toc-332-存储密码的步骤&quot;&gt;3.3.2 存储密码的步骤&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#333-校验密码的步骤&quot; id=&quot;markdown-toc-333-校验密码的步骤&quot;&gt;3.3.3 校验密码的步骤&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#334-在web程序中永远在服务器端进行哈希加密&quot; id=&quot;markdown-toc-334-在web程序中永远在服务器端进行哈希加密&quot;&gt;3.3.4 在Web程序中，永远在服务器端进行哈希加密&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#335-让密码更难破解慢哈希函数&quot; id=&quot;markdown-toc-335-让密码更难破解慢哈希函数&quot;&gt;3.3.5 让密码更难破解：慢哈希函数&lt;/a&gt;&lt;/li&gt;
              &lt;li&gt;&lt;a href=&quot;#336-无法破解的哈希加密密钥哈希和密码哈希设备&quot; id=&quot;markdown-toc-336-无法破解的哈希加密密钥哈希和密码哈希设备&quot;&gt;3.3.6 无法破解的哈希加密：密钥哈希和密码哈希设备&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
          &lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1-为什么密码需要进行哈希&quot;&gt;1 为什么密码需要进行哈希？&lt;/h1&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hash(&quot;hello&quot;) = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash(&quot;hbllo&quot;) = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366
hash(&quot;waltz&quot;) = c0e81794384491161f1777c232bc6bd9ec38f616560b120fda8e90f383853542
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;哈希算法是一个单向函数。它可以将任何大小的数据转化为定长的“指纹”，并且无法被反向计算。另外，即使数据源只改动了一丁点，哈希的结果也会完全不同（参考上面的例子）。这样的特性使得它非常适合用于保存密码，因为我们需要加密后的密码无法被解密，同时也能保证正确校验每个用户的密码。&lt;/p&gt;

&lt;p&gt;在基于哈希加密的账户系统中，通常用户注册和认证的流程是这样的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;用户注册一个帐号&lt;/li&gt;
  &lt;li&gt;密码经过哈希加密储存在数据库中。只要密码被写入磁盘，任何时候都不允许是明文&lt;/li&gt;
  &lt;li&gt;当用户登录的时候，从数据库取出已经加密的密码，和经过哈希的用户输入进行对比&lt;/li&gt;
  &lt;li&gt;如果哈希值相同，用户获得登入授权，否则，会被告知输入了无效的登录信息&lt;/li&gt;
  &lt;li&gt;每当有用户尝试登录，以上两步都会重复&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;在第 4 步中，永远不要告诉用户到底是用户名错了，还是密码错了。只需要给出一个大概的提示，比如“无效的用户名或密码”。这可以防止攻击者在不知道密码或不知道用户名的情况下，枚举出有效的用户名或密码。&lt;/p&gt;

&lt;p&gt;需要提到的是，用于保护密码的哈希函数和你在数据结构中学到的哈希函数是不同的。比如用于实现哈希表这之类数据结构的哈希函数，它们的目标是快速查找，而不是高安全性。只有加密哈希函数才能用于保护密码，例如　SHA256，SHA512，RipeMD　和　WHIRLPOOL。&lt;/p&gt;

&lt;p&gt;也许你很容易就认为只需要简单地执行一遍加密哈希函数，密码就能安全，那么你大错特错了。有太多的办法可以快速地把密码从简单哈希值中恢复出来，但也有很多比较容易实现的技术能使攻击者的效率大大降低。黑客的进步也在激励着这些技术的进步，比如这样一个网站：你可以提交一系列待破解的哈希值，并且在不到　1　秒的时间内得到了结果。显然，简单哈希加密并不能满足我们对安全性的需求。&lt;/p&gt;

&lt;p&gt;那么下一节会讲到几种常用的破解简单哈希加密的办法。&lt;/p&gt;

&lt;h2 id=&quot;2-如何破解哈希加密&quot;&gt;2 如何破解哈希加密&lt;/h2&gt;

&lt;h3 id=&quot;21-字典和暴力破解攻击dictionary-and-brute-force-attacks&quot;&gt;2.1 字典和暴力破解攻击(Dictionary and Brute Force Attacks)&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Dictionary Attack
Trying apple : failed
Trying blueberry : failed
Trying justinbeiber : failed
...
Trying letmein : failed
Trying s3cr3t : success!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Brute Force Attack
Trying aaaa : failed
Trying aaab : failed
Trying aaac : failed
...
Trying acdb : failed
Trying acdc : success!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;破解哈希加密最简单的办法，就是去猜，将每个猜测值哈希之后的结果和目标值比对，如果相同则破解成功。两种最常见的猜密码的办法是&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;字典攻击&lt;/code&gt;和&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;暴力攻击&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;字典攻击需要使用一个字典文件，它包含单词、短语、常用密码以及其他可能用作密码的字符串。其中每个词都是进行过哈希后储存的，用它们和密码哈希比对，如果相同，这个词就是密码。字典文件的构成是从大段文本中分解出的单词，甚至还包括一些数据库中真实的密码。然后还可以对字典文件进行更进一步的处理使它更有效，比如把单词中的字母替换为它们的“形近字”（hello变为h3110）。&lt;/p&gt;

&lt;p&gt;暴力攻击会尝试每一个在给定长度下各种字符的组合。这种攻击会消耗大量的计算，也通常是破解哈希加密中效率最低的办法，但是它最终会找到正确的密码。因此密码需要足够长，以至于遍历所有可能的字符串组合将耗费太长时间，从而不值得去破解它。&lt;/p&gt;

&lt;p&gt;我们没有办法阻止字典攻击和暴力攻击，尽管可以降低它们的效率，但那也不是完全阻止。如果你的密码哈希系统足够安全，唯一的破解办法就是进行字典攻击或者暴力遍历每一个哈希值。&lt;/p&gt;

&lt;h3 id=&quot;22-查表破解lookup-tables&quot;&gt;2.2 查表破解(Lookup Tables)&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Searching: 5f4dcc3b5aa765d61d8327deb882cf99: FOUND: password5
Searching: 6cbe615c106f422d23669b610b564800: not in database
Searching: 630bf032efe4507f2c57b280995925a9: FOUND: letMEin12
Searching: 386f43fab5d096a7a66d67c8f213e5ec: FOUND: mcd0nalds
Searching: d5ec75d5fe70d428685510fae36492d9: FOUND: p@ssw0rd!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;对于特定的hash类型，如果需要破解大量hash的话，查表是一种非常有效而且快速的方式。它的理念就是预先计算(pre-compute)出密码字典中每一个密码的hash。然后把hash和对应的密码保存在一个表里。一个设计良好的查询表结构，即使存储了数十亿个hash，每秒钟仍然可以查询成百上千个hash。&lt;/p&gt;

&lt;p&gt;如果你想更好地体验查表法的速度，尝试使用 CrackStation 的 &lt;a href=&quot;https://crackstation.net/&quot;&gt;free hash cracker&lt;/a&gt; 来破解下面四个 SHA256 加密的哈希值吧。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;c11083b4b0a7743af748c85d343dfee9fbb8b2576c05f3a7f0d632b0926aadfc

08eac03b80adc33dc7d8fbe44b7c7b05d3a2c511166bdb43fcb710b03ba919e7

e4ba5cbd251c98e6cd1c23f126a3b81d8d8328abc95387229850952b3ef9f904

5206b8b8a996cf5320cb12ca91c7b790fba9f030408efe83ebb83548dc3007bd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;23-反向查表破解reverse-lookup-tables&quot;&gt;2.3 反向查表破解(Reverse Lookup Tables)&lt;/h3&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Searching for hash(apple) in users&apos; hash list...     : Matches [alice3, 0bob0, charles8]
Searching for hash(blueberry) in users&apos; hash list... : Matches [usr10101, timmy, john91]
Searching for hash(letmein) in users&apos; hash list...   : Matches [wilson10, dragonslayerX, joe1984]
Searching for hash(s3cr3t) in users&apos; hash list...    : Matches [bruce19, knuth1337, john87]
Searching for hash(z@29hjja) in users&apos; hash list...  : No users used this password
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这种方法可以使攻击者同时对多个哈希值发起字典攻击或暴力攻击，而不需要预先计算出一个查询表。&lt;/p&gt;

&lt;p&gt;首先攻击者构造一个基于密码-用户名的一对多的表，当然数据需要从某个已经被入侵的数据库获得，然后猜测一系列哈希值并且从表中查找拥有此密码的用户。通常许多用户可能有着相同的密码，因此这种攻击方式也显得尤为有效。&lt;/p&gt;

&lt;h3 id=&quot;24-彩虹表-rainbow-tables&quot;&gt;2.4 彩虹表 (Rainbow Tables)&lt;/h3&gt;

&lt;p&gt;彩虹表是一种在时间和空间的消耗上找寻平衡的破解技术。它和查表法很类似，但是为了使查询表占用的空间更小而牺牲了破解速度。因为它更小，于是我们可以在一定的空间内存储更多的哈希值，从而使攻击更加有效。能够破解任何 8 位及以下长度 MD5 值的彩虹表已经出现了。&lt;/p&gt;

&lt;p&gt;下面我们会讲到一种让查表法和彩虹表都失去作用的技术，叫做加盐。&lt;/p&gt;

&lt;h2 id=&quot;3-加盐&quot;&gt;3 加盐&lt;/h2&gt;

&lt;blockquote&gt;

  &lt;p&gt;盐（Salt）&lt;/p&gt;

  &lt;p&gt;在密码学中，是指通过在密码任意固定位置插入特定的字符串，让散列后的结果和使用原始密码的散列结果不相符，这种过程称之为“加盐”。&lt;/p&gt;

&lt;/blockquote&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hash(&quot;hello&quot;) = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash(&quot;hello&quot; + &quot;QxLUF1bgIAdeQX&quot;) = 9e209040c863f84a31e719795b2577523954739fe5ed3b58a75cff2127075ed1
hash(&quot;hello&quot; + &quot;bv5PehSMfV11Cd&quot;) = d1d3ec2e6f20fd420d50e2642992841d8338a314b8ea157c9e18477aaef226ab
hash(&quot;hello&quot; + &quot;YYLmfY6IehjZMQ&quot;) = a49670c3c18b9e079b9cfaf51634f563dc8ae3070db2c4a8544305df1b60f007
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;查表法和彩虹表只有在所有密码都以相同方式进行哈希加密时才有效。如果两个用户密码相同，那么他们密码的哈希值也是相同的。我们可以通过“随机化”哈希来阻止这类攻击，于是当相同的密码被哈希两次之后，得到的值就不相同了。&lt;/p&gt;

&lt;p&gt;比如可以在密码中混入一段“随机”的字符串再进行哈希加密，这个被字符串被称作盐值。如同上面例子所展示的，这使得同一个密码每次都被加密为完全不同的字符串。为了校验密码是否正确，我们需要储存盐值。通常和密码哈希值一起存放在账户数据库中，或者直接存为哈希字符串的一部分。&lt;/p&gt;

&lt;p&gt;盐值并不需要保密，由于随机化了哈希值，查表法、反向查表法和彩虹表都不再有效。攻击者无法确知盐值，于是就不能预先计算出一个查询表或者彩虹表。这样每个用户的密码都混入不同的盐值后再进行哈希，因此反向查表法也变得难以实施。&lt;/p&gt;

&lt;p&gt;下面讲讲我们在实现加盐哈希的过程中通常会犯哪些错误。&lt;/p&gt;

&lt;h3 id=&quot;31-实现加盐哈希中的错误&quot;&gt;3.1 实现加盐哈希中的错误&lt;/h3&gt;

&lt;h4 id=&quot;311-短盐值和盐值重复&quot;&gt;3.1.1 短盐值和盐值重复&lt;/h4&gt;

&lt;p&gt;最常见的错误就是在多次哈希加密中使用相同的盐值或者太短的盐值。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;盐值重复&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;每次哈希加密都使用相同的盐值是很容易犯的一个错误，这个盐值要么被硬编码到程序里，要么只在第一次使用时随机获得。这样加盐的方式是做无用功，因为两个相同的密码依然会得到相同的哈希值。攻击者仍然可以使用反向查表法对每个值进行字典攻击，只需要把盐值应用到每个猜测的密码上再进行哈希即可。如果盐值被硬编码到某个流行的软件里，可以专门为这个软件制作查询表和彩虹表，那么破解它生成的哈希值就变得很简单了。&lt;/p&gt;

&lt;p&gt;用户创建账户或每次修改密码时，都应该重新生成新的盐值进行加密。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;短盐值&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果盐值太短，攻击者可以构造一个查询表包含所有可能的盐值。以只有 3 个 ASCII 字符的盐值为例，一共有 95 x 95 x 95 = 857,375 种可能。这看起来很多，但是如果对于每个盐值查询表只包含 1MB 最常见的密码，那么总共只需要 837GB 的储存空间。一个不到 100 美元的 1000GB 硬盘就能解决问题。
同样地，用户名也不应该被用作盐值。尽管在一个网站中用户名是唯一的，但是它们是可预测的，并且经常重复用于其他服务中。攻击者可以针对常见用户名构建查询表，然后对用户名盐值哈希发起进攻。&lt;/p&gt;

&lt;p&gt;为了使攻击者无法构造包含所有可能盐值的查询表，盐值必须足够长。一个好的做法是使用和哈希函数输出的字符串等长的盐值，比如 SHA256 算法的输出是 256bits (32 bytes)，那么盐值也至少应该是 32 个随机字节。&lt;/p&gt;

&lt;h4 id=&quot;312-两次哈希和组合哈希函数&quot;&gt;3.1.2 两次哈希和组合哈希函数&lt;/h4&gt;

&lt;p&gt;（译注：此节标题原文中的 Wacky Hash Functions 直译是古怪的哈希函数，大概是由于作者不认可这种组合多种哈希函数的做法，为了便于理解，本文还是翻译为组合哈希函数）&lt;/p&gt;

&lt;p&gt;这节讲述了另一种对密码哈希的误解：使用组合哈希函数。人们经常不由自主地认为将不同的哈希函数组合起来，结果会更加安全。实际上这样做几乎没有好处，仅仅造成了函数之间互相影响的问题，甚至有时候会变得更加不安全。永远不要尝试发明自己的加密方法，只需只用已经被设计好的标准算法。有的人会说使用多种哈希函数会使计算更慢，从而破解也更慢，但是还有其他的办法能更好地减缓破解速度，后面会提到的。&lt;/p&gt;

&lt;p&gt;这里有些低端的组合哈希函数，我在网上某些论坛看到它们被推荐使用：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;md5(sha1(password))
md5(md5(salt) + md5(password))
sha1(sha1(password))
sha1(str_rot13(password + salt))
md5(sha1(md5(md5(password) + sha1(password)) + md5(password)))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;不要使用其中任何一种。&lt;/p&gt;

&lt;p&gt;注意：这节内容是有争议的。我已经收到的大量的邮件，为组合哈希函数而辩护。他们的理由是如果攻击者不知道系统使用的哪种哈希函数，那么也就很难预先为这种组合构造出彩虹表，于是破解起来会花费更多的时间。&lt;/p&gt;

&lt;p&gt;诚然，攻击者在不知道加密算法的时候是无法发动攻击的，但是不要忘了 Kerckhoffs’s principle，攻击者通常很容易就能拿到源码（尤其是那些免费或开源的软件）。通过系统中取出的一些密码-哈希值对应关系，很容易反向推导出加密算法。破解组合哈希函数确实需要更多时间，但也只是受了一点可以确知的因素影响。更好的办法是使用一个很难被并行计算出结果的迭代算法，然后增加适当的盐值防止彩虹表攻击。&lt;/p&gt;

&lt;p&gt;当然你实在想用“标准的”组合哈希函数，比如 HMAC，也是可以的。但如果只是为了使破解起来更慢，那么先读读下面讲到的密钥扩展。&lt;/p&gt;

&lt;p&gt;创造新的哈希函数可能带来安全问题，构造哈希函数的组合又可能带来函数间互相影响的问题，它们带来的一丁点好处和这些比起来真是微不足道。显然最好的做法是使用标准的、经过完整测试的算法。&lt;/p&gt;

&lt;h3 id=&quot;32-哈希碰撞&quot;&gt;3.2 哈希碰撞&lt;/h3&gt;

&lt;p&gt;哈希函数将任意大小的数据转化为定长的字符串，因此其中一定有些输入经过哈希计算之后得到了相同的结果。加密哈希函数的设计就是为了使这样的碰撞尽可能难以被发现。随着时间流逝，密码学家发现攻击者越来越容易找到碰撞了，最近的例子就是MD5算法的碰撞已经确定被发现了。&lt;/p&gt;

&lt;p&gt;碰撞攻击的出现表明很可能有一个和用户密码不同的字符串却和它有着相同的哈希值。然而，即使在 MD5 这样脆弱的哈希函数中找到碰撞也需要耗费大量的计算，因此这样的碰撞“意外地”在实际中出现的可能性是很低的。于是站在实用性的角度上可以这么说，加盐 MD5 和加盐 SHA256 的安全性是一样的。不过可能的话，使用本身更安全的哈希函数总是好的，比如 SHA256、SHA512、RipeMD 或者 WHIRLPOOL。&lt;/p&gt;

&lt;h3 id=&quot;33-正确的做法恰当使用哈希加密&quot;&gt;3.3 正确的做法：恰当使用哈希加密&lt;/h3&gt;

&lt;p&gt;本节会准确讲述应该如何对密码进行哈希加密。其中第一部分介绍最基本的要素，也是在哈希加密中一定要做到的；后面讲解怎样在这个基础上进行扩展，使得加密更难被破解。&lt;/p&gt;

&lt;h4 id=&quot;331-基本要素加盐哈希&quot;&gt;3.3.1 基本要素：加盐哈希&lt;/h4&gt;

&lt;p&gt;忠告：你不仅仅要用眼睛看文章，更要自己动手去实现后面讲到的“让密码更难破解：慢哈希函数”。&lt;/p&gt;

&lt;p&gt;在前文中我们已经看到，利用查表法和彩虹表，普通哈希加密是多么容易被恶意攻击者破解，也知道了可以通过随机加盐的办法也解决这个问题。那么到底应该使用怎样的盐值呢，又如何把它混入密码？&lt;/p&gt;

&lt;p&gt;盐值应该使用基于加密的伪随机数生成器（Cryptographically Secure Pseudo-Random Number Generator – CSPRNG）来生成。CSPRNG 和普通的随机数生成器有很大不同，如 C 语言中的 rand() 函数。物如其名，CSPRNG 专门被设计成用于加密，它能提供高度随机和无法预测的随机数。我们显然不希望自己的盐值被猜测到，所以一定要使用 CSPRNG 。下面的表格列出了当前主流编程语言中的 CSPRNG 方法：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;Platform&lt;/th&gt;
      &lt;th style=&quot;text-align: left&quot;&gt;CSPRNG&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;PHP&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;mcrypt_create_iv, openssl_random_pseudo_bytes&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Java&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;java.security.SecureRandom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Dot NET (C#, VB)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;System.Security.Cryptography.RNGCryptoServiceProvider&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Ruby&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;SecureRandom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Python&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;os.urandom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Perl&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Math::Random::Secure&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;C/C++ (Windows API)&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;CryptGenRandom&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Any language on GNU/Linux or Unix&lt;/td&gt;
      &lt;td style=&quot;text-align: left&quot;&gt;Read from /dev/random or /dev/urandom&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;对于每个用户的每个密码，盐值都应该是独一无二的。每当有新用户注册或者修改密码，都应该使用新的盐值进行加密。并且这个盐值也应该足够长，使得有足够多的盐值以供加密。一个好的标准的是：盐值至少和哈希函数的输出一样长；盐值应该被储存和密码哈希一起储存在账户数据表中。&lt;/p&gt;

&lt;h4 id=&quot;332-存储密码的步骤&quot;&gt;3.3.2 存储密码的步骤&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;使用CSPRNG生成一个长度足够的盐值&lt;/li&gt;
  &lt;li&gt;将盐值混入密码，并使用标准的加密哈希函数进行加密，如SHA256&lt;/li&gt;
  &lt;li&gt;把哈希值和盐值一起存入数据库中对应此用户的那条记录&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;333-校验密码的步骤&quot;&gt;3.3.3 校验密码的步骤&lt;/h4&gt;

&lt;ol&gt;
  &lt;li&gt;从数据库取出用户的密码哈希值和对应盐值&lt;/li&gt;
  &lt;li&gt;将盐值混入用户输入的密码，并且使用同样的哈希函数进行加密&lt;/li&gt;
  &lt;li&gt;比较上一步的结果和数据库储存的哈希值是否相同，如果相同那么密码正确，反之密码错误&lt;/li&gt;
&lt;/ol&gt;

&lt;h4 id=&quot;334-在web程序中永远在服务器端进行哈希加密&quot;&gt;3.3.4 在Web程序中，永远在服务器端进行哈希加密&lt;/h4&gt;

&lt;p&gt;如果你正在开发一个 Web 程序，你可能会疑惑到底在哪进行加密。是使用JavaScript在用户的浏览器上操作呢，还是将密码“裸体”传送到服务器再进行加密？&lt;/p&gt;

&lt;p&gt;即使浏览器端用JavaScript加密了，你仍然需要在服务端再次进行加密。试想有个网站在浏览器将密码经过哈希后传送到服务器，那么在认证用户的时候，网站收到哈希值和数据库中的值进行比对就可以了。这看起来比只在服务器端加密安全得多，因为至始至终没有将用户的密码明文传输，但实际上不是这样。&lt;/p&gt;

&lt;p&gt;问题在于，从客户端来看，经过哈希的密码逻辑上成为用户真正的密码。为了通过服务器认证，用户只需要发送密码的哈希值即可。如果有坏小子获取了这个哈希值，他甚至可以在不知道用户密码的情况通过认证。更进一步，如果他用某种手段入侵了网站的数据库，那么不需要去猜解任何人的密码，就可以随意使用每个人的帐号登录。&lt;/p&gt;

&lt;p&gt;这并不是说你不应该在浏览器端进行加密，但是如果你这么做了，一定要在服务端再次加密。在浏览器中进行哈希加密是个好想法，不过实现的时候注意下面几点：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;客户端密码哈希并不能代替 HTTPS（SSL/TLS）。如果浏览器和服务器之间的连接是不安全的，那么中间人攻击可以修改 JavaScript 代码，删除加密函数，从而获取用户密码。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;有些浏览器不支持 JavaScript，也有的用户禁用了浏览器的 JavaScript 功能。为了最好的兼容性，你的程序应该检测 JavaScript 是否可用，如果答案为否，需要在服务端模拟客户端的加密。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;客户端哈希同样需要加盐，很显然的办法就是向服务器请求用户的盐值，但是不要这么做。因为这给了坏蛋一个机会，能够在不知道密码的情况下检测用户名是否有效。既然你已经在服务端对密码进行了加盐哈希，那么在客户端把用户名（或邮箱）加上网站特有的字符串（如域名）作为盐值是可行的。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;335-让密码更难破解慢哈希函数&quot;&gt;3.3.5 让密码更难破解：慢哈希函数&lt;/h4&gt;

&lt;p&gt;加盐使攻击者无法采用特定的查询表和彩虹表快速破解大量哈希值，但是却不能阻止他们使用字典攻击或暴力攻击。高端的显卡（GPU）和定制的硬件可以每秒进行数十亿次哈希计算，因此这类攻击依然可以很高效。为了降低攻击者的效率，我们可以使用一种叫做密钥扩展的技术。&lt;/p&gt;

&lt;p&gt;这种技术的思想就是把哈希函数变得很慢，于是即使有着超高性能的 GPU 或定制硬件，字典攻击和暴力攻击也会慢得让攻击者无法接受。最终的目标是把哈希函数的速度降到足以让攻击者望而却步，但造成的延迟又不至于引起用户的注意。&lt;/p&gt;

&lt;p&gt;密钥扩展的实现是依靠一种 CPU 密集型哈希函数。不要尝试自己发明简单的迭代哈希加密，如果迭代不够多，是可以被高效的硬件快速并行计算出来的，就和普通哈希一样。应该使用标准的算法，比如 &lt;a href=&quot;https://en.wikipedia.org/wiki/PBKDF2&quot;&gt;PBKDF2&lt;/a&gt; 或者 &lt;a href=&quot;https://en.wikipedia.org/wiki/Bcrypt&quot;&gt;bcrypt&lt;/a&gt; 。&lt;/p&gt;

&lt;p&gt;这类算法使用一个安全因子或迭代次数作为参数，这个值决定了哈希函数会有多慢。对于桌面软件或者手机软件，获取参数最好的办法就是执行一个简短的性能基准测试，找到使哈希函数大约耗费 0.5 秒的值。这样，你的程序就可以尽可能保证安全，而又不影响到用户体验。&lt;/p&gt;

&lt;p&gt;如果你在一个 Web 程序中使用密钥扩展，记得你需要额外的资源处理大量认证请求，并且密钥扩展也使得网站更容易遭受拒绝服务攻击（DoS）。但我依然推荐使用密钥扩展，不过把迭代次数设定得低一点，你应该基于认证请求最高峰时的剩余硬件资源来计算迭代次数。要求用户每次登录时输入验证码可以消除拒绝服务的威胁。另外，一定要把你的系统设计为迭代次数可随时调整的。&lt;/p&gt;

&lt;p&gt;如果你担心计算量带来的负载，但又想在Web程序中使用密钥扩展，可以考虑在浏览器中用 JavaScript 完成。&lt;a href=&quot;https://github.com/bitwiseshiftleft/sjcl/tree/version-0.8&quot;&gt;Stanford JavaScript Crypto Library&lt;/a&gt; 里包含了 PBKDF2 的实现。迭代次数应该被设置到足够低，以适应速度较慢的客户端，比如移动设备。同时当客户端不支持 JavaScript 的时候，服务端应该接手计算。客户端的密钥扩展并不能免除服务端进行哈希加密的职责，你必须对客户端传来的哈希值再次进行哈希加密，就像对付一个普通密码一样。&lt;/p&gt;

&lt;h4 id=&quot;336-无法破解的哈希加密密钥哈希和密码哈希设备&quot;&gt;3.3.6 无法破解的哈希加密：密钥哈希和密码哈希设备&lt;/h4&gt;

&lt;p&gt;只要攻击者可以检测对一个密码的猜测是否正确，那么他们就可以进行字典攻击或暴力攻击。因此下一步就是向哈希计算中增加一个密钥，只有知道这个密钥的人才能校验密码。有两种办法可以实现：将哈希值加密，比如使用 AES 算法；将密钥包含到哈希字符串中，比如使用密钥哈希算法 HMAC 。&lt;/p&gt;

&lt;p&gt;听起来很简单，做起来就不一样了。这个密钥需要在任何情况下都不被攻击者获取，即使系统因为漏洞被攻破了。如果攻击者获取了进入系统的最高权限，那么不论密钥被储存在哪，他们都可以窃取到。因此密钥需要储存在外部系统中，比如另一个用于密码校验的物理服务器，或者一个关联到服务器的特制硬件，如 YubiHSM 。&lt;/p&gt;

&lt;p&gt;我强烈推荐大型服务（10万用户以上）使用这类办法，因为我认为面对如此多的用户是有必要的。&lt;/p&gt;

&lt;p&gt;如果你难以负担多个服务器或专用的硬件，仍然有办法在一个普通 Web 服务器上利用密钥哈希技术。大部分针对数据库的入侵都是由于 SQL 注入攻击，因此不要给攻击者进入本地文件系统的权限（禁止数据库服务访问本地文件系统，如果它有这个功能的话）。这样一来，当你随机生成一个密钥存到通过 Web 程序无法访问的文件中，然后混入加盐哈希，得到的哈希值就不再那么脆弱了，即便这时数据库遭受了注入攻击。不要把将密钥硬编码到代码里，应该在安装时随机生成。这当然不如独立的硬件系统安全，因为如果 Web 程序存在 SQL 注入点，那么可能还存在其他一些问题，比如本地文件包含漏洞（Local File Inclusion），攻击者可以利用它读取本地密钥文件。无论如何，这个措施比没有好。&lt;/p&gt;

&lt;p&gt;请注意密钥哈希不代表无需进行加盐。高明的攻击者迟早会找到办法窃取密钥，因此依然对密码哈希进行加盐和密钥扩展很重要。&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;原文：&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://blog.jobbole.com/61872/&quot;&gt;加盐密码哈希：如何正确使用&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;
</description>
        <pubDate>Mon, 17 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/salt.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/salt.html</guid>
        
        <category>salt</category>
        
        
        <category>web</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>DH 密钥交换算法</title>
        <description>&lt;p&gt;迪菲－赫尔曼密钥交换（Diffie–Hellman key exchange，简称“D–H”） 是一种安全协议。它可以让双方在完全没有对方任何预先信息的条件下通过不安全信道建立起一个密钥。这个密钥可以在后续的通讯中作为对称密钥来加密通讯内容。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-算法描述&quot; id=&quot;markdown-toc-1-算法描述&quot;&gt;1 算法描述&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-安全性&quot; id=&quot;markdown-toc-2-安全性&quot;&gt;2 安全性&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;1-算法描述&quot;&gt;1 算法描述&lt;/h2&gt;

&lt;p&gt;离散对数的概念：&lt;/p&gt;

&lt;p&gt;原根：如果 a 是素数 p 的一个原根，那么数值：a mod p，a^2 mod p，…，a^(p-1) mod p 是各不相同的整数，且以某种排列方式组成了从 1 到 p-1 的所有整数。
离散对数：如果对于一个整数　b　和素数　p　的一个原根　a，可以找到一个唯一的指数 i，使得：b =（a的i次方） mod　p；其中　0　≦　i ≦　p-1，那么指数　i　称为　b　的以　a　为基数的模　p　的离散对数。&lt;/p&gt;

&lt;p&gt;Diffie-Hellman 算法的有效性依赖于计算离散对数的难度，其含义是：当已知大素数　p　和它的一个原根　a　后，对给定的 b，要计算 i ，被认为是很困难的，而给定 i 计算　b 却相对容易。&lt;/p&gt;

&lt;p&gt;Diffie-Hellman算法：&lt;/p&gt;

&lt;p&gt;假如用户 A 和用户 B 希望交换一个密钥。
取素数 p 和整数 a，a 是 p 的一个原根，公开 a 和 p。
用户 A 选择随机数 XA &amp;lt; p，并计算　YA = a^XA mod p。
用户 B　选择随机数 XB &amp;lt; p，并计算 YB　= a^XB mod p。
每一方都将 X 保密而将 Y 公开让另一方得到。
用户 A 计算密钥的方式是：K = (YB)^XA mod p
用户 B 计算密钥的方式是：K = (YA)^XB mod p&lt;/p&gt;

&lt;p&gt;证明：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;	(YB)^XA mod p = (a^XB mod　p)^XA mod p
= (a^XB)^XA mod p = (a^XA)^XB mod p    (密钥即为 a^(XA*XB) mod p)
=　(a^XA mod　p)^XB mod p　= (YA)^XB mod p
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于 XA 和 XB 是保密的，而第三方只有 p、a、YB、YA 可以利用，只有通过取离散对数来确定密钥，但对于大的素数 p，计算离散对数是十分困难的。&lt;/p&gt;

&lt;p&gt;例子：&lt;/p&gt;

&lt;p&gt;假如用户　Alice　和用户　Bob　希望交换一个密钥。
取一个素数　p =　97　和　97　的一个原根　a　=　5。
Alice　和　Bob　分别选择秘密密钥　XA　=　36　和　XB　=　58，并计算各自的公开密钥：
YA　=　a^XA mod p　=　5^36 mod 97　=　50
YB　=　a^XB mod p　=　5^58 mod 97　=　44
Alice　和　Bob　交换了公开密钥之后，计算共享密钥如下：
Alice：K　=　(YB)^XA mod p　=　44^36 mod 97　=　75
Bob：K　=　(YA)^XB mod p　=　50^58 mod 97　=　75&lt;/p&gt;

&lt;h2 id=&quot;2-安全性&quot;&gt;2 安全性&lt;/h2&gt;

&lt;p&gt;当然，为了使这个例子变得安全，必须使用非常大的 XA, XB 以及 p， 否则可以实验所有的可能取值。(总共有最多 97 个这样的值, 就算 XA 和 XB 很大也无济于事)。如果 p 是一个至少 300 位的质数，并且 XA 和 XB 至少有 100 位长， 那么即使使用全人类所有的计算资源和当今最好的算法也不可能从a, p和a^(XA&lt;em&gt;XB) mod p 中计算出 XA&lt;/em&gt;XB。这个问题就是著名的离散对数问题。注意 g 则不需要很大, 并且在一般的实践中通常是 2 或者 5。&lt;/p&gt;

&lt;p&gt;在最初的描述中，迪菲－赫尔曼密钥交换本身并没有提供通讯双方的身份验证服务，因此它很容易受到中间人攻击。 一个中间人在信道的中央进行两次迪菲－赫尔曼密钥交换，一次和 Alice 另一次和 Bob，就能够成功的向 Alice 假装自己是 Bob ，反之亦然。而攻击者可以解密（读取和存储）任何一个人的信息并重新加密信息，然后传递给另一个人。因此通常都需要一个能够验证通讯双方身份的机制来防止这类攻击。有很多种安全身份验证解决方案使用到了迪菲－赫尔曼密钥交换。例如当 Alice 和 Bob 共有一个公钥基础设施时，他们可以将他们的返回密钥进行签名。&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;原文：&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://blog.csdn.net/fw0124/article/details/8462373&quot;&gt;DH 密钥交换算法&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;
</description>
        <pubDate>Mon, 17 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/dh-algorithm.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/dh-algorithm.html</guid>
        
        <category>DH</category>
        
        
        <category>算法</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>Node.js crypto 模块简介</title>
        <description>&lt;p&gt;密码技术是互联网应用的一项最基本的技术之一，主要保证了数据的安全。安全定义是多维度的，通过不可逆的hash算法可以保证登陆密码的安全；通过非对称的加密算法，可以保证数据存储的安全性；通过数字签名，可以验证数据在传输过程中是否被篡改。&lt;/p&gt;

&lt;p&gt;Node.js的Crypto库就提供各种加密算法，可以非常方便地让我们使用密码技术，解决应用开发中的问题。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-crypto-简介&quot; id=&quot;markdown-toc-1-crypto-简介&quot;&gt;1 Crypto 简介&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#2-hash-算法&quot; id=&quot;markdown-toc-2-hash-算法&quot;&gt;2 Hash 算法&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#21-简介&quot; id=&quot;markdown-toc-21-简介&quot;&gt;2.1 简介&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#22-nodejs-中的使用&quot; id=&quot;markdown-toc-22-nodejs-中的使用&quot;&gt;2.2 node.js 中的使用&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;#hmac-算法&quot; id=&quot;markdown-toc-hmac-算法&quot;&gt;Hmac 算法&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#21-简介-1&quot; id=&quot;markdown-toc-21-简介-1&quot;&gt;2.1 简介&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#22-nodejs-中的使用-1&quot; id=&quot;markdown-toc-22-nodejs-中的使用-1&quot;&gt;2.2 node.js 中的使用&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#3-加密和解密算法&quot; id=&quot;markdown-toc-3-加密和解密算法&quot;&gt;3 加密和解密算法&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#31-简介&quot; id=&quot;markdown-toc-31-简介&quot;&gt;3.1 简介&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#32-nodejs-中的使用&quot; id=&quot;markdown-toc-32-nodejs-中的使用&quot;&gt;3.2 node.js 中的使用&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1-crypto-简介&quot;&gt;1 Crypto 简介&lt;/h1&gt;

&lt;p&gt;Crypto　利用　OpenSSL　库来实现它的加密技术，它提供　OpenSSL　中的一系列哈希方法，包括　hmac、cipher、decipher、签名和验证等方法的封装。&lt;/p&gt;

&lt;p&gt;Crypto 官方文档： &lt;a href=&quot;http://nodejs.org/api/crypto.html&quot;&gt;http://nodejs.org/api/crypto.html&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;2-hash-算法&quot;&gt;2 Hash 算法&lt;/h1&gt;

&lt;h2 id=&quot;21-简介&quot;&gt;2.1 简介&lt;/h2&gt;

&lt;p&gt;哈希算法是指将任意长度的二进制值映射为较短的固定长度的二进制值，这个小的二进制值称为哈希值。哈希值是一段数据唯一且极其紧凑的数值表示形式。如果散列一段明文而且哪怕只更改该段落的一个字母，随后的哈希都将产生不同的值。要找到散列为同一个值的两个不同的输入，在计算上是不可能的，所以数据的哈希值可以检验数据的完整性。一般用于快速查找和加密算法。&lt;/p&gt;

&lt;p&gt;通常我们对登陆密码，一般都是使用　Hash　算法进行加密，典型的哈希算法包括 ‘md5′,’sha’,’sha1′,’sha256′,’sha512′,’RSA-SHA’等。&lt;/p&gt;

&lt;p&gt;关于 Hash 算法的详细资料可以参考:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://www.cnblogs.com/wangjy/archive/2011/09/08/2171638.html&quot;&gt;王家逸：Hash 算法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;由于　md5　已经有了大量的字典库，对于安全级别一般的网站用　sha1　吧；如果安全级别要求很高，CPU　配置也很牛，可以考虑用　sha512。为了更安全的可以加　salt，我会在另一篇博客里面做详细的介绍。&lt;/p&gt;

&lt;h2 id=&quot;22-nodejs-中的使用&quot;&gt;2.2 node.js 中的使用&lt;/h2&gt;

&lt;p&gt;node.js 中的　crypto 中支持多种　Hash 算法，下面是使用　md5 算法的例子：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createHash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;md5&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// 可任意多次调用update()&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello, world!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello, nodejs!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;hash&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;digest&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// 7e1977739c748beac0c0fd14fd26a544&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;hmac-算法&quot;&gt;Hmac 算法&lt;/h1&gt;

&lt;h2 id=&quot;21-简介-1&quot;&gt;2.1 简介&lt;/h2&gt;

&lt;p&gt;HMAC 是密钥相关的哈希运算消息认证码（Hash-based Message Authentication Code）,HMAC 运算利用哈希算法，以一个密钥和一个消息为输入，生成一个消息摘要作为输出。HMAC可以有效防止一些类似md5的彩虹表等攻击，比如一些常见的密码直接MD5存入数据库的，可能被反向破解。&lt;/p&gt;

&lt;p&gt;定义 HMAC 需要一个加密用散列函数（表示为 H，可以是 MD5 或者 SHA-1）和一个密钥K。我们用 B 来表示数据块的字节数。（以上所提到的散列函数的分割数据块字长B=64），用L来表示散列函数的输出数据字节数（ MD5 中 L = 16, SHA-1 中 L = 20）。鉴别密钥的长度可以是小于等于数据块字长的任何正整数值。&lt;strong&gt;应用程序中使用的密钥长度若是比 B 大，则首先用使用散列函数 H 作用于它，然后用 H 输出的 L 长度字符串作为在 HMAC 中实际使用的密钥。一般情况下，推荐的最小密钥 K 长度是 L 个字节&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;关于 Hmac 算法的详细资料可以参考:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/feiyangxiaomi/article/details/34445005&quot;&gt;消息摘要算法-HMAC算法&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;http://blog.csdn.net/fw0124/article/details/8473858&quot;&gt;HMAC算法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;22-nodejs-中的使用-1&quot;&gt;2.2 node.js 中的使用&lt;/h2&gt;

&lt;p&gt;同样的　node.js 中的　crypto 中支持多种　Hmac 算法，下面是使用　sha256 算法的例子：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;const crypto = require(&apos;crypto&apos;)

const hmac = crypto.createHmac(&apos;sha256&apos;, &apos;secret-key&apos;)

hmac.update(&apos;Hello, world!&apos;)
hmac.update(&apos;Hello, nodejs!&apos;)

console.log(hmac.digest(&apos;hex&apos;)) // 80f7e22570...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;只要密钥发生了变化，那么同样的输入数据也会得到不同的签名，因此，可以把 Hmac 理解为用随机数“增强”的哈希算法。&lt;/p&gt;

&lt;h2 id=&quot;3-加密和解密算法&quot;&gt;3 加密和解密算法&lt;/h2&gt;

&lt;h3 id=&quot;31-简介&quot;&gt;3.1 简介&lt;/h3&gt;

&lt;p&gt;对于登陆密码来说，是不需要考虑解密的，通常都会用不可逆的算法，像　md5,　sha-1　等。但是，对于有安全性要求的数据来说，我们是需要加密存储，然后解密使用的，这时需要用到可逆的加密算法。对于这种基于　Key　算法，可以分为对称加密和不对称加密。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;对称加密算法：通信一方用　Key　加密明文，另一方收到之后用同样的　Key　来解密就可以得到明文。&lt;/li&gt;
  &lt;li&gt;不对称加密算法：使用两把完全不同但又是完全匹配的一对　Key（公钥和私钥）。在使用不对称加密算法加密文件时，只有使用匹配的一对公钥和私钥，才能完成对明文的加密和解密过程。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;32-nodejs-中的使用&quot;&gt;3.2 node.js 中的使用&lt;/h3&gt;

&lt;p&gt;同样的　node.js 中的　crypto 中支持多种加密解密算法，下面是使用　aes 算法的例子：&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;aesEncrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cipher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createCipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;aes192&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;crypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;cipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;final&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypted&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;aesDecrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decipher&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;crypto&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createDecipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;aes192&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decrypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;decrypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decipher&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;final&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decrypted&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Hello, this is a secret message!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Password!&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encrypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;aesEncrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decrypted&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;aesDecrypt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Plain text: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Encrypted text: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;encrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;console&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;Decrypted text: &lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;decrypted&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;运行结果如下&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;：&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Plain text: Hello, this is a secret message!
Encrypted text: 8a944d97bdabc157a5b7a40cb180e7…
Decrypted text: Hello, this is a secret message!&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
可以看出，加密后的字符串通过解密又得到了原始内容。

注意到 aes 有很多不同的算法，如 aes192，aes-128-ecb，aes-256-cbc 等，aes 除了密钥外还可以指定 IV（[Initialization Vector](https://zh.wikipedia.org/wiki/%E5%88%9D%E5%A7%8B%E5%90%91%E9%87%8F)）参见：[crypto.createCipheriv](http://nodejs.cn/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv)，不同的系统只要 IV 不同，用相同的密钥加密相同的数据得到的加密结果也是不同的。加密结果通常有两种表示方法：hex 和 base64，这些功能 Node.js 全部都支持，但是在应用中要注意，如果加解密双方一方用 Node.js，另一方用 Java、PHP 等其它语言，需要仔细测试。如果无法正确解密，要确认双方是否遵循同样的 aes 算法，字符串密钥和 IV 是否相同，加密后的数据是否统一为 hex 或 base64 格式。

## 4 签名和验证算法

### 4.1 简介

我们除了对数据进行加密和解密，还需要判断数据在传输过程中，是否真实际和完整，是否被篡改了。那么就需要用到签名和验证的算法，利用不对称加密算法，通过私钥进行数字签名，公钥验证数据的真实性。

数字签名的制作和验证过程，如下图所示。

![数字签名的制作和验证过程](../asset/image/blog/2017-04-17-crypto-module-of-node.js-introduce/001.png)

### 4.2 node.js 中的使用

同样可以使用多种算法，下面是　HASH256　算发的例子：(签名和验证算法必须使用一对公钥和私钥来签名和验证)

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;const crypto = require(‘crypto’)&lt;/p&gt;

&lt;p&gt;function sign (input, algorithm, privateSecret) {
  const signature = crypto.createSign(algorithm).update(input).sign(privateSecret, ‘hex’)&lt;/p&gt;

&lt;p&gt;return signature
}&lt;/p&gt;

&lt;p&gt;function verify (input, algorithm, publicSecret, signture) {
  const passVerify = crypto.createVerify(algorithm).update(input).verify(
    publicSecret,
    signature,
    ‘hex’
  )&lt;/p&gt;

&lt;p&gt;return passVerify
}&lt;/p&gt;

&lt;p&gt;const token = ‘I am Aidan’
const publicSecret =  &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD8dB8TsTVR8Dnulb+E7rORNWZS
RmfpVGHwXofKckiI8b2FG/a9hbaf8xsb1oLjW3LUYWK6m4PpoaL7qukd/XFr+dhD
toncv078llzwDjh2TRW8Vf68/vSwoYOXAE0dXDiLLhb7s7kH8z60VW3ooTYTfLyt
e5D3Diixl0GFEFMkAQIDAQAB
-----END PUBLIC KEY-----&lt;/code&gt;
const privateSecret = &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQD8dB8TsTVR8Dnulb+E7rORNWZSRmfpVGHwXofKckiI8b2FG/a9
hbaf8xsb1oLjW3LUYWK6m4PpoaL7qukd/XFr+dhDtoncv078llzwDjh2TRW8Vf68
/vSwoYOXAE0dXDiLLhb7s7kH8z60VW3ooTYTfLyte5D3Diixl0GFEFMkAQIDAQAB
AoGBANnEARqnfesUYaSgn/g3P8Y+XekSuofXNjR2FoRXWKJohKbRnGGXehU3S2cT
/wvH0qHI77UwePWLbF/S6gvol3BqGgdN1ieikcH7LyEMtoNv6TX0n7HusfDdQDkh
xQACb/4pN/MhN7HnhJh0EZIepq7/OTocRVusxeEgT83XlqWRAkEA/q/Ew1WirFZj
VavM1WWLpDaGHdMvtR5ganymHos3pdwfIsJyfTzSMqjA1zoozfiB57YSY/6mI8Df
pifbtI1powJBAP3BZ6NsygBz4KcCxlFerkajhNenOGEF76L/YHPsvwGM+2PcL/rS
/xIfPAj6ykaVoTSfwCVSe98dHGEAhzePngsCQQCQgaiR8IfxYr7QAD+joQ8/aFRm
ncoG6SppoTocQH+dkyzzawLM/nKBnfB07iHy5BrJHyyGIhmgVbJQM3NcmZQjAkBu
YZ8Pe9cy8zUZ8R8LbkApAiBbHqZrrgVbxfLS+nzr08PW4IUOepHx9BxNW6p5ocUJ
+yO+GG9B0ovxtiUbiiGZAkA0txGLM3ciSjOkYITCnsjZu3ZklbtilA7VpDwDnzok
C95wpYcaTHMBYX1fOUj+TNROmeS8/CT6TpQtZxJ7v4bc
-----END RSA PRIVATE KEY-----&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;// 私钥签名，公钥验证
const signature = sign(token, ‘RSA-SHA256’, privateSecret)
const passVerify = verify(token, ‘RSA-SHA256’, publicSecret, signature)&lt;/p&gt;

&lt;p&gt;console.log(signature)
console.log(passVerify) // true&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
注：秘钥对的生成

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;openssl genrsa -out rsa_private_key.pem&lt;/p&gt;

&lt;p&gt;openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
## 5 协商秘钥算法　Diffie-Hellman

### 5.1 DH 密钥交换算法

[参见我的另一篇博客：DH　密钥交换算法](/posts/dh-algorithm.html)

### 5.2 Node.js 中的使用

```javascript
const crypto = require(&apos;crypto&apos;);

// Generate Alice&apos;s keys...
const alice = crypto.createDiffieHellman(2048);
const aliceKey = alice.generateKeys();

// Generate Bob&apos;s keys...
const bob = crypto.createDiffieHellman(alice.getPrime(), alice.getGenerator());
const bobKey = bob.generateKeys();

// Exchange and generate the secret...
const aliceSecret = alice.computeSecret(bobKey);
const bobSecret = bob.computeSecret(aliceKey);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;

  &lt;p&gt;参考资料：&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://ju.outofmemory.cn/entry/118198&quot;&gt;Node.js 加密算法库 Crypto&lt;/a&gt;
&lt;a href=&quot;http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/001434501504929883d11d84a1541c6907eefd792c0da51000&quot;&gt;crypto&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;
</description>
        <pubDate>Mon, 17 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/crypto-module-of-node.js-introduce.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/crypto-module-of-node.js-introduce.html</guid>
        
        <category>crypto</category>
        
        
        <category>Node.js</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>jwt 详解</title>
        <description>&lt;p&gt;之前自认为对　jwt 有所了解，直到最近被面试官一问，才知道自己的浅薄，于是才有了下面这篇文章，重新认识一下　jwt，当然仍然缺乏实践，还请各位前被多多指正。&lt;/p&gt;

&lt;ul id=&quot;markdown-toc&quot;&gt;
  &lt;li&gt;&lt;a href=&quot;#1-jwt-是什么&quot; id=&quot;markdown-toc-1-jwt-是什么&quot;&gt;1 JWT 是什么？&lt;/a&gt;    &lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;#11-jwt-简介&quot; id=&quot;markdown-toc-11-jwt-简介&quot;&gt;1.1 JWT 简介&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#12-jwt的主要应用场景&quot; id=&quot;markdown-toc-12-jwt的主要应用场景&quot;&gt;1.2 JWT　的主要应用场景&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;#13-jwt-的结构&quot; id=&quot;markdown-toc-13-jwt-的结构&quot;&gt;1.3 JWT 的结构&lt;/a&gt;        &lt;ul&gt;
          &lt;li&gt;&lt;a href=&quot;#131-header&quot; id=&quot;markdown-toc-131-header&quot;&gt;1.3.1 Header&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#132-payload&quot; id=&quot;markdown-toc-132-payload&quot;&gt;1.3.2 Payload&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#133-signature&quot; id=&quot;markdown-toc-133-signature&quot;&gt;1.3.3 Signature&lt;/a&gt;&lt;/li&gt;
          &lt;li&gt;&lt;a href=&quot;#14-完整的-jwt&quot; id=&quot;markdown-toc-14-完整的-jwt&quot;&gt;1.4 完整的 JWT&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id=&quot;1-jwt-是什么&quot;&gt;1 JWT 是什么？&lt;/h1&gt;

&lt;h2 id=&quot;11-jwt-简介&quot;&gt;1.1 JWT 简介&lt;/h2&gt;

&lt;p&gt;JWT 是一种用于双方之间传递安全信息的简洁的、URL 安全的表述性声明规范。JWT 作为一个开放的标准（ &lt;a href=&quot;https://tools.ietf.org/html/rfc7519&quot;&gt;RFC 7519&lt;/a&gt; ），定义了一种简洁的，自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。因为数字签名的存在，这些信息是可信的，JWT 可以使用 HMAC 算法或者是 RSA 的公私秘钥对进行签名。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;简洁(Compact): 可以通过 URL，POST 参数或者在 HTTP header 中发送，因为数据量小，传输速度也很快&lt;/li&gt;
  &lt;li&gt;自包含(Self-contained)：负载中包含了所需要的所有用户信息，避免了多次查询数据库&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;12-jwt的主要应用场景&quot;&gt;1.2 JWT　的主要应用场景&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;身份认证(Authentication)：这是使用　JWT　最常见的情形。一旦用户完成了登陆，在接下来的每个请求中包含　JWT，可以用来验证用户身份以及对路由，服务和资源的访问权限进行验证。由于它的开销非常小，可以轻松的在不同域名的系统中传递，所有目前在单点登录（SSO）中比较广泛的使用了该技术。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;信息交换(Information Exchange)：JSON Web Token 是一个在双方间安全传递信息的好方法，因为它们能被签名。例如：我们可以使用公私秘钥对签名，这样就可以确保发送者以及他们发送的信息的真实性。此外，当签名使用头部和有效载荷计算时，还可以验证内容未被篡改。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;13-jwt-的结构&quot;&gt;1.3 JWT 的结构&lt;/h2&gt;

&lt;p&gt;JWT包含了使用 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 分隔的三部分：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Header(头部)&lt;/li&gt;
  &lt;li&gt;Payload(负载)&lt;/li&gt;
  &lt;li&gt;Signature(签名)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其结构看起来是这样的&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;xxxxx.yyyyy.zzzzz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;131-header&quot;&gt;1.3.1 Header&lt;/h3&gt;

&lt;p&gt;在 header 中通常包含了两部分：token 类型，也就是　&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JWT&lt;/code&gt;；以及采用的加密算法，例如　HMAC SHA256 或者 RSA。&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;alg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;HS256&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;typ&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;JWT&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;接下来对这部分内容使用 Base64Url 编码组成了　JWT　结构的第一部分。&lt;/p&gt;

&lt;h3 id=&quot;132-payload&quot;&gt;1.3.2 Payload&lt;/h3&gt;

&lt;p&gt;Token的第二部分是负载，它包含了 claim， Claim 是一些实体（通常指的用户）的状态和额外的元数据，有三种类型的 claim： reserved, public 和 private。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Reserved claims: 这些 claim 是 JWT 预先定义的，在 JWT 中并不会强制使用它们，而是推荐使用，常用的有 iss（签发者）, exp（过期时间戳）, sub（面向的用户）, aud（接收方）, iat（签发时间）。&lt;/li&gt;
  &lt;li&gt;Public claims：这些可以由使用 JWT 的人随意定义。但为避免冲突，应在 IANA JSON Web 令牌注册表中定义它们，或者将其定义为包含防冲突命名空间的 URI。&lt;/li&gt;
  &lt;li&gt;Private claims：这些是为了在同意使用它们的各方之间共享信息而创建的自定义声明。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;负载使用的例子：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sub&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;1234567890&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;John Doe&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;admin&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;上述的负载需要经过 Base64Url 编码后作为 JWT 结构的第二部分。&lt;/p&gt;

&lt;h3 id=&quot;133-signature&quot;&gt;1.3.3 Signature&lt;/h3&gt;

&lt;p&gt;创建签名需要使用编码后的 header 和 payload 以及一个秘钥，使用 header 中指定签名算法进行签名。例如如果希望使用 HMAC SHA256 算法，那么签名应该使用下列方式创建：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;HMACSHA256(
  base64UrlEncode(header) + &quot;.&quot; + base64UrlEncode(payload),
  secret
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;14-完整的-jwt&quot;&gt;1.4 完整的 JWT&lt;/h3&gt;

&lt;p&gt;JWT 格式的输出是以 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.&lt;/code&gt; 分隔的三段 Base64 编码，与 SAML 等基于 XML 的标准相比，JWT 在 HTTP 和 HTML 环境中更容易传递。&lt;/p&gt;

&lt;p&gt;下列的 JWT 展示了一个完整的 JWT 格式，它拼接了之前的 Header， Payload 以及秘钥签名：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-04-16-what-is-jwt/001.png&quot; alt=&quot;jwt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;签名用于验证消息的发送者以及消息是没有经过篡改的。&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;参考资料：&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;https://segmentfault.com/a/1190000005047525&quot;&gt;JWT 简介&lt;/a&gt;
&lt;a href=&quot;https://segmentfault.com/a/1190000009030769&quot;&gt;手动实现一个 json web token&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;
</description>
        <pubDate>Sun, 16 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/what-is-jwt.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/what-is-jwt.html</guid>
        
        <category>jwt</category>
        
        
        <category>web</category>
        
      </item>
      
    
      <!-- 跳过需要渲染但不想发表的文章 -->  
      <item>
        <title>base64 编码原理</title>
        <description>&lt;p&gt;本文简单介绍下　base64 编码原理&lt;/p&gt;

&lt;p&gt;Base64　编码过程：（base64　的编码都是按字符串长度）
1　首先以每　3　个　8bit　的字符为一组
2　然后针对每组，首先获取每个字符的　ASCII　编码
3　然后将　ASCII　编码转换成　8bit　的二进制，得到一组　3*8　=　24bit　的字节的二进制
4　然后再将这　24bit　划分为　4　个　6bit　的字节，并在每个　6bit　的字节前面都填两个高位　0　，得到　4　个　8bit　的字节
5　然后将这 4 个 8bit 的字节转换成 10 进制，对照 Base64 编码表 （下表），得到对应编码后的字符&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;要求被编码字符是 8bit 的，所以须在 ASCII 编码范围内，\u0000-\u00ff，中文就不行。&lt;/li&gt;
  &lt;li&gt;如果被编码字符长度不是 3 的倍数的时候，则都用 0 代替，对应的输出字符为 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;../asset/image/blog/2017-04-16-base64-coding-principle/001.png&quot; alt=&quot;Base64 Encoding/Decoding Table&quot; /&gt;&lt;/p&gt;

&lt;p&gt;比如举下面2个例子：&lt;/p&gt;

&lt;p&gt;a) 字符长度为能被 3 整除时：比如 “Tom” ：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;字母:				T           o           m
ASCII:      84          111         109
8bit字节:   	01010100    01101111    01101101
6bit字节:		010101      000110      111101      101101
十进制:     	21          6           61          45
对应编码:   	V           G           9           t
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;new Buffer(&apos;Tom&apos;).toString(&apos;base64&apos;) = &apos;VG9t&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;b) 字符串长度不能被 3 整除时，比如 “Lucy”：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;字母:				L           u           c           y
ASCII:      76          117         99          121
8bit字节:   	01001100    01110101    01100011    01111001      00000000    00000000
6bit字节:   	010011      000111      010101      100011      	011110  		010000  		000000  		000000
十进制:     	19          7           21          35            30      		16      		(异常) 			(异常)      
对应编码:   	T           H           V           j             e       		Q       		=       		=
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;由于 Lucy 只有 4 个字母，所以按 3 个一组的话，第二组还有两个空位，所以需要用 0 来补齐。这里就需要注意，因为是需要补齐而出现的 0 ，所以转化成十进制的时候就不能按常规用 base64 编码表来对应，所以不是 a ， 可以理解成为一种特殊的“异常”，编码应该对应	&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;=&lt;/code&gt;。&lt;/p&gt;

&lt;blockquote&gt;

  &lt;p&gt;参考资料：&lt;/p&gt;

  &lt;p&gt;&lt;a href=&quot;http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html&quot;&gt;关于 base64 编码的原理及实现&lt;/a&gt;&lt;/p&gt;

&lt;/blockquote&gt;
</description>
        <pubDate>Sun, 16 Apr 2017 00:00:00 +0000</pubDate>
        <link>https://www.aidandai.com/posts/base64-coding-principle.html</link>
        <guid isPermaLink="true">https://www.aidandai.com/posts/base64-coding-principle.html</guid>
        
        <category>base64</category>
        
        
        <category>web</category>
        
      </item>
      
    
  </channel>
</rss>
