-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
291 lines (151 loc) · 106 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>iOS导航栏的心得</title>
<link href="/2019/03/25/iOS%E5%AF%BC%E8%88%AA%E6%A0%8F%E7%9A%84%E5%BF%83%E5%BE%97/"/>
<url>/2019/03/25/iOS%E5%AF%BC%E8%88%AA%E6%A0%8F%E7%9A%84%E5%BF%83%E5%BE%97/</url>
<content type="html"><![CDATA[<p><a href="https://www.jianshu.com/p/0d4337b2e18a" target="_blank" rel="noopener">https://www.jianshu.com/p/0d4337b2e18a</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> UI </tag>
</tags>
</entry>
<entry>
<title>iOS项目优化</title>
<link href="/2019/03/06/%E7%BC%A9%E5%B0%8F%E5%8C%85%E4%BD%93/"/>
<url>/2019/03/06/%E7%BC%A9%E5%B0%8F%E5%8C%85%E4%BD%93/</url>
<content type="html"><![CDATA[<h2 id="资源文件优化"><a href="#资源文件优化" class="headerlink" title="资源文件优化"></a>资源文件优化</h2><h3 id="压缩图片资源"><a href="#压缩图片资源" class="headerlink" title="压缩图片资源"></a>压缩图片资源</h3><p>综合各种因素,无论是无损还是有损<a href="https://pngquant.org/" target="_blank" rel="noopener"><code>pngquant</code></a>都表现很好。使用方式有很多种,常用的命令行和GUI都很方便。这里选用的是<a href="https://imageoptim.com/howto.html" target="_blank" rel="noopener"><code>ImageOptim</code></a>,不但性能高效,使用方便,还集成了很多目前最好的图片优化工具。</p><p><code>ImageOptim</code>会对每张图片分别应用勾选的压缩算法,然后对比每种压缩算法产出的图片,选取最小的那张作为输出结果,非常方便。<br><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1g0vhguupbvj30if0axabz.jpg" alt=""></p><hr><p><strong>我们先看一下无损压缩</strong><br>无损压缩,通过删除图片中不必要的元数据,实现优化图片大小。</p><blockquote><p>Xcode’s conversion is applied even when it makes files bigger. It will <a href="https://bjango.com/articles/pngcompression/" target="_blank" rel="noopener">undo ImageOptim’s savings.</a></p></blockquote><p>可以看到<a href="https://imageoptim.com/xcode.html" target="_blank" rel="noopener">imageoptim官网</a>提到关于<code>Xcode's built-in (de)optimization</code>。<br>原来<code>xcode</code> 在编译的时候会对 <code>png</code> 图片进行 <code>recompress</code>,生成苹果喜欢的 <code>CgBI</code> 格式,就会<code>undo</code>掉<code>ImageOptim</code>所做的操作,为了优化图片解码,减少不必要的 <code>GPU</code> 和 <code>CPU</code> 的开销。</p><p><strong>注:</strong>详细说明xcode在打包时对图片所做的事情:<a href="https://cloud.tencent.com/developer/article/1368027" target="_blank" rel="noopener">iOS减包实战:Compress PNG Files作用分析</a>,通过此文我们可以知道<code>xcode pngcompression</code>时都采取了什么操作,所以引言中<code>undo</code>不应该为撤销,翻译成取消或者破坏比较合适。以下对此开关做个对比实验。<br><img src="https://ws1.sinaimg.cn/large/006tKfTcgy1g0vh2gkwb1j30c502dq31.jpg" alt=""><br><code>Compress PNG Files</code>就是xcode用于控制图片转换的开关,默认开启。<br>如果你项目没有这个配置。<a href="https://stackoverflow.com/questions/33082838/compress-png-files-missing-from-build-settings" target="_blank" rel="noopener">解决没有Compress PNG Files - Packaging</a></p><p><strong>实验结果如下:</strong></p><table><thead><tr><th>Assets管理图片</th><th>原始图片</th><th>无损压缩图片</th></tr></thead><tbody><tr><td>图片大小</td><td>35.3MB</td><td>27.6MB</td></tr><tr><td>Compress开启,.app大小</td><td>76.1MB</td><td>76.1MB</td></tr><tr><td>Compress关闭,.app大小</td><td>76.1MB</td><td>76.1MB</td></tr></tbody></table><table><thead><tr><th>项目中</th><th>原始图片</th><th>无损压缩图片</th></tr></thead><tbody><tr><td>图片大小</td><td>38.9MB</td><td>31.3MB</td></tr><tr><td>Compress开启,.app大小</td><td>38.2MB</td><td>37.7MB</td></tr><tr><td>Compress关闭,.app大小</td><td>38.3MB</td><td>30.6MB</td></tr></tbody></table><p>素材图片是一堆大小不一的图</p><p><strong>这里的大小衡量的是ipa解压后Payload文件夹下.app的大小。</strong></p><p><em>由此可以看出只有图片在项目目录的情况下,无损压缩才生效。</em>证明开关是有效的,但是对<code>Asset Catalog</code>中的图片无效,那么有没有其他设置可以控制<code>Asset Catalog</code>中的图片不进行这种转换呢。</p><p><code>Asset Catalog</code>其中有个参数<code>Compression</code>,让我们测试一下是否能发挥无损压缩图片的作用。<br><img src="https://ws3.sinaimg.cn/large/006tKfTcgy1g11b1ho2ngj306n0413zr.jpg" alt=""></p><table><thead><tr><th>项目中</th><th>原始图片</th><th>无损压缩图片</th></tr></thead><tbody><tr><td>图片大小</td><td>14.5MB</td><td>12.3MB</td></tr><tr><td>.app大小,默认Automatic</td><td>27.9MB</td><td>27.9MB</td></tr><tr><td>.app大小,Lossless</td><td>27.9MB</td><td>27.9MB</td></tr><tr><td>.app大小,Basic</td><td>27.9MB</td><td>27.9MB</td></tr><tr><td>.app大小,GPU Best Quality</td><td>35.2MB</td><td>35.2MB</td></tr><tr><td>.app大小,GPU Smallest Size</td><td>35.2MB</td><td>35.2MB</td></tr></tbody></table><p>以上数据<code>Compress</code>开关处于<code>NO</code>。<br>从上图可以看到并没有能发挥出无损压缩图片作用的开关。</p><p>自此有查阅了很多资料,发现几乎没有相关内容,只有一篇<a href="https://techblog.toutiao.com/2018/06/04/gan-huo-jin-ri-tou-tiao-iosduan-an-zhuang-bao-da-xiao-you-hua-si-lu-yu-shi-jian/" target="_blank" rel="noopener">《干货|今日头条iOS端安装包大小优化—思路与实践》</a>提到,并且探索了很远,非常值学习一下,膜拜。可是最终结果还是失败了,苹果并没有留下任何操作<code>Asset</code>的“软硬开关”。可以得出结论,<code>Compress PNG Files</code>还是开启比较好,毕竟还能提升加载速度。关于iOS的无损压缩到此告一段落。</p><hr><p><strong>接下来我们看一下有损压缩</strong><br>从<a href="https://techblog.toutiao.com/2018/06/04/gan-huo-jin-ri-tou-tiao-iosduan-an-zhuang-bao-da-xiao-you-hua-si-lu-yu-shi-jian/" target="_blank" rel="noopener">《干货|今日头条iOS端安装包大小优化—思路与实践》</a>中的实验数据得到结论,综合<code>App Slicing</code>等因素我们还是使用<code>Asset Catalog</code>来管理图片最好,所以就不考虑项目中零散图片的处理了。</p><table><thead><tr><th>Assets管理图片</th><th>原始大小</th><th>有损压缩80%</th></tr></thead><tbody><tr><td>图片大小</td><td>14.3MB</td><td>6.6MB</td></tr><tr><td>Compress开启,.app大小</td><td>28.6MBMB</td><td>19MB</td></tr><tr><td>Compress关闭,.app大小</td><td>28.6MBMB</td><td>19MB</td></tr></tbody></table><p>换了一批大图用于测试,15张。<br>可以非常直观的看到,开关对有损压缩后的图片不起任何作用。80%有损压缩还是非常有成效的,打包后的包体小了<code>9MB</code>,并且仍然很清晰,对比了多张几百<code>KB</code>的图片一般体积会变小一半,但没有肉眼可见的区别。</p><hr><p><strong>webP</strong><br>谷歌有一个webP格式的图片,压缩效果也非常优秀,不过对比上面的<code>imageoptim</code>并没有出色太多,而且对于<code>iOS</code>使用起来稍微麻烦一些,而且比<code>PNG</code>多消耗2倍左右<code>CPU</code>和解码时间,感觉不是大量依赖图片的<code>app</code>没必要采用。<br><a href="https://www.jianshu.com/p/ed7562a34af1" target="_blank" rel="noopener">webP 格式图片在 iOS 中的应用</a><br><a href="https://blog.devzeng.com/blog/ios-webp-usage.html" target="_blank" rel="noopener">在iOS项目中使用WebP格式图片</a></p><h3 id="使用工具清理未使用的图片"><a href="#使用工具清理未使用的图片" class="headerlink" title="使用工具清理未使用的图片"></a>使用工具清理未使用的图片</h3><p><a href="https://github.com/tinymind/LSUnusedResources" target="_blank" rel="noopener">LSUnusedResources</a><br>这个<code>app</code>的原理是,对某一文件目录下所有的源代码文件进行扫描,用正则表达式匹配出所有的<code>@"xxx"</code>字符串(会根据不同类型的源代码文件使用不同的匹配规则),形成“使用到的图片的集合”,然后扫描所有的图片文件名,查看哪些图片文件名不在“使用到的图片的集合”中,然后将这些图片文件呈现在结果中。</p><p>新的加入的正则</p><blockquote><p>regex: ([-_]?\d+)</p></blockquote><p><img src="https://ws3.sinaimg.cn/large/006tKfTcgy1g16s3mxn02j30a500x748.jpg" alt=""></p><p>不过比较复杂的名字拼接方法,还是手动查看一下图片资源是否被使用最为稳妥。</p><h3 id="删除重复的资源"><a href="#删除重复的资源" class="headerlink" title="删除重复的资源"></a>删除重复的资源</h3><p>这里的重复资源(主要指图片)不是指命名重复而是内容相同。<br>介绍一个<a href="https://github.com/adrianlopezroche/fdupes" target="_blank" rel="noopener">fdupes</a>是Linux下的一个工具,它由<code>Adrian Lopez</code>用C编程语言编写并基于<code>MIT</code>许可证发行,该应用程序可以在指定的目录及子目录中查找重复的文件。fdupes有各种选项,可以实现对文件的列出、删除、替换为文件副本的硬链接等操作。<br>文件对比以下列顺序开始:<br>大小对比 > 部分<code>MD5</code>签名对比 > 完整<code>MD5</code>签名对比 > 逐字节对比</p><pre><code>$ brew install fdupes # mac通过brew安装$ fdupes -r ~/path # 搜索重复子文件在终端展示$ fdupes -Sr ~/path # 搜索重复子文件&显示每个重复文件的大小在终端展示$ fdupes -Sr ~/path > ~/log.txt # log输出到指定路径文件夹</code></pre><h2 id="编译器优化选项"><a href="#编译器优化选项" class="headerlink" title="编译器优化选项"></a>编译器优化选项</h2><h3 id="CPU指令集优化"><a href="#CPU指令集优化" class="headerlink" title="CPU指令集优化"></a>CPU指令集优化</h3><p>注:看到很多文章还在说能减小包体难道都穿越回去了吗?<br><code>app slicing</code>是2015年<code>iOS9</code>增加的功能。当用户从<code>app store</code>上下载<code>app</code>时,可以只下载适用于其设备的<code>app</code>架构版本和所需资源,从而减少app所占的空间。<br>也就是说额外的<code>CPU</code>指令集将不再会影响用户在<code>AppStore</code>看到的和实际安装的包体。<br><a href="https://www.appcoda.com/app-thinning/" target="_blank" rel="noopener">Working with App Thinning in iOS 9</a></p><p>CPU指令集相关<br><a href="https://www.jianshu.com/p/3fce0bd6f045" target="_blank" rel="noopener">iOS 中的 armv7,armv7s,arm64,i386,x86_64 都是什么</a></p><p><img src="https://ws3.sinaimg.cn/large/006tKfTcgy1g0v67uyo1qj30hu03jaah.jpg" alt=""><br>实际打包所包含的指令集是这两个选项的集合。</p><h3 id="Generate-Debug-Symbols"><a href="#Generate-Debug-Symbols" class="headerlink" title="Generate Debug Symbols"></a>Generate Debug Symbols</h3><p>注:好多都说把这个设置为<code>NO</code>可以减小包体,但是真的去尝试了吗?效果如何?都考虑包体大小问题了,难道不考虑解决线上问题需要符号表吗?连<code>bugly</code>都没用过吗?<br>实测项目<code>100+ MB</code>,打包出来的<code>Symbols</code>不足<code>10KB</code>。</p><h3 id="Optimization"><a href="#Optimization" class="headerlink" title="Optimization"></a>Optimization</h3><p><code>Build Setting > Asset Catalog Compiler - Options</code></p><p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1g0v5kd6bk0j30cf021glm.jpg" alt=""><br>该选项<code>xcode</code>默认<code>nothing</code></p><table><thead><tr><th></th><th>nothing</th><th>space</th></tr></thead><tbody><tr><td>小项目.app大小</td><td>76.1MB</td><td>61.6MB</td></tr><tr><td>大项目.app大小</td><td>185MB</td><td>174MB</td></tr></tbody></table><p>至于<code>time</code>选项则是运行时的读取时间优化,对于主流的设备而言来说并无太大意义,也很难测算太详细数据。</p><h3 id="Flatten-Compiles-XIB-Files"><a href="#Flatten-Compiles-XIB-Files" class="headerlink" title="Flatten Compiles XIB Files"></a>Flatten Compiles XIB Files</h3><p><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1g0v5bo980dj30c004f0t2.jpg" alt=""><br>官方文档:<br><img src="https://ws1.sinaimg.cn/large/006tKfTcgy1g194frrq0tj30qn08h3zd.jpg" alt=""><br>官方解释是:指定是否在编译时剥离nib文件以优化它们的大小,设为YES时编译出来的nib文件会被压缩但是不能编辑,谨慎选择。</p><h3 id="Dead-Code-Stripping"><a href="#Dead-Code-Stripping" class="headerlink" title="Dead Code Stripping"></a>Dead Code Stripping</h3><p>build setting 里 = YES(好像默认就是YES)。 确定 dead code(代码被定义但从未被调用)被剥离,去掉冗余的代码,即使一点冗余代码,编译后体积也是很可观的。</p><p>2.4. 清理无用代码<br>2.4.1. Dead Code Stripping</p><p>Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping. Remove functions and data that are unreachable by the entry point or exported symbols</p><p>Xcode 默认会开启此选项,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,但是对于 Objective-C 等动态语言是无效的。因为 Objective-C 是建立在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,所有的部分应该都是会被判别为有效代码。</p><p>将Dead Code Stripping 设置为YES 也能够一定程度上对程序安装包进行优化,只是优化的效果一般,对于一些比较小的项目甚至没有什么优化体现,所以这里也就没有上测试数据。Dead Code Stripping 是对程序编译出的可执行二进制文件中没有被实际使用的代码进行Strip操作。对于更深层次的解读,在参考链接的文章里有详细描述。参考:Dead Code Stripping</p><h2 id="项目中的实际表现"><a href="#项目中的实际表现" class="headerlink" title="项目中的实际表现"></a>项目中的实际表现</h2><ul><li><p>80%有损压缩<br><code>105MB->86.7MB</code></p></li><li><p><code>Options</code>设置<code>space</code><br><code>86.7MB->82.6MB</code></p></li><li><p>使用<code>LSUnusedResources</code>删除不用资源文件<br>删除多余静态库<br><code>82.6MB->67.8MB</code></p></li><li><p>使用<code>fdupes</code>找到226条记录<br><code>67.8MB->67.6MB</code></p></li><li><p>删除历史遗留业务和又一次<code>LSUnusedResources</code>删除资源<br><code>67.8MB->62.2MB</code></p></li></ul><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://juejin.im/post/5c6826656fb9a049dc02c958" target="_blank" rel="noopener">iOS 本地图片优化实践</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 优化 </tag>
</tags>
</entry>
<entry>
<title>iOS如何在HTTPS中使用TLS证书验证</title>
<link href="/2019/02/12/iOS%E5%A6%82%E4%BD%95%E5%9C%A8HTTPS%E4%B8%AD%E4%BD%BF%E7%94%A8TLS%E8%AF%81%E4%B9%A6%E9%AA%8C%E8%AF%81/"/>
<url>/2019/02/12/iOS%E5%A6%82%E4%BD%95%E5%9C%A8HTTPS%E4%B8%AD%E4%BD%BF%E7%94%A8TLS%E8%AF%81%E4%B9%A6%E9%AA%8C%E8%AF%81/</url>
<content type="html"><![CDATA[<h2 id="NSURLSession-如何使用TLS证书验证"><a href="#NSURLSession-如何使用TLS证书验证" class="headerlink" title="NSURLSession 如何使用TLS证书验证"></a>NSURLSession 如何使用TLS证书验证</h2><p><code>NSURLSession</code>的代理方法<code>URLSession:didReceiveChallenge:completionHandler:</code><br><code>completionHandler</code>这个<code>block</code>需要相关认证信息(<code>NSURLSessionAuthChallengeDisposition</code>,<code>NSURLCredential</code>)才能完成连接。这个方法能拿到一个<code>NSURLAuthenticationChallenge</code>类型的<code>challenge</code>,这个<code>challenge</code>里面包含着绝大部分网络请求的参数与信息,非常强大,可以详见备注。然后我们就可以根据<code>challenge</code>在这个代理方法中提供一些自定义的信息。</p><h3 id="默认的实现"><a href="#默认的实现" class="headerlink" title="默认的实现"></a>默认的实现</h3><p>如果我们不自定义内容称为<code>NSURLSession</code>的代理,不做任何处理的话那么系统为我们做了些什么呢?</p><p>系统的默认实现是验证<code>challenge</code>中返回的信任链,结果是有效的话则根据 <code>serverTrust</code> 创建 <code>credential</code> 用于同服务端确立 <code>SSL</code> 连接。否则会得到 <code>The certificate for this server is invalid...</code> 这样的错误而无法访问。<br>比如在访问 <code>https://www.baidu.com</code> 的时候咧,我们不实现这个方法也能访问成功的。系统对百度服务器返回来的证书链,从叶节点证书往根证书层层验证(有效期、签名等等),遇到根证书时,发现作为可信锚点的它存在与可信证书列表中,那么验证就通过,允许与服务端建立连接。<br>也就是<a href="https://xiaolit.github.io/2018/07/15/iOS-s-HTTPS/">《iOS应该了解的HTTPS》</a>开篇提到的,如果你只是想iOS端普通的使用HTTPS的接口那就不必做任何操作,iOS默认都帮我们做好了。</p><h3 id="深入简出介入“验证数字证书有效性”步骤"><a href="#深入简出介入“验证数字证书有效性”步骤" class="headerlink" title="深入简出介入“验证数字证书有效性”步骤"></a>深入简出介入“验证数字证书有效性”步骤</h3><p><code>NSURLSession</code>的代理方法<code>URLSession:didReceiveChallenge:completionHandler:</code><br>回调中会收到一个<code>NSURLAuthenticationChallenge</code>类型的<code>challenge</code>。它其中的一个属性<code>NSURLProtectionSpace</code>这是权限认证的核心,它通常被称为保护空间,表示需要认证的服务器或者域,它定义了一系列的约束去告诉我们需要向服务器提供什么样的认证,通过判断<code>authenticationMethod</code>指定授权方式,如果这个值是 <code>NSURLAuthenticationMethodServerTrust</code> 的时候,这个网络连接才会返回证书链做HTTPS的握手校验流程,也就是我们可以从<code>protectionSpace</code>中拿到<code>serverTrust</code>,我们就可以插手 TLS 握手中“验证数字证书有效性”这个一步骤了。</p><pre><code>//当取得保护空间要求我们认证的方式为NSURLAuthenticationMethodServerTrust时才会有serverTrustif ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { //拿到系统的 SecTrustRef 它包含验证策略(SecPolicyRef)以及一系列受信任的锚点证书 SecTrustRef trust = challenge.protectionSpace.serverTrust; ...} </code></pre><p>拿到了trust证书链表后,我们需要把我们自己的证书放进去。也就是设置锚点证书。就是<a href="https://xiaolit.github.io/2019/02/12/iOS如何在HTTPS中使用TLS证书验证/">HTTPS的证书X.509</a>中<strong>HTTPS中的验证</strong>中提到的那个可自定义的锚点证书。</p><h4 id="1-首先加载证书"><a href="#1-首先加载证书" class="headerlink" title="1.首先加载证书"></a>1.首先加载证书</h4><pre><code>//证书路径NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cerName" ofType:@".cer"];NSData * cerData = [NSData dataWithContentsOfFile:cerPath];SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));//此处数组可以放多张证书NSArray *trustedCertificates = @[CFBridgingRelease(certificate)];</code></pre><h4 id="2-设置锚点证书"><a href="#2-设置锚点证书" class="headerlink" title="2.设置锚点证书"></a>2.设置锚点证书</h4><pre><code>SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);</code></pre><p>对于<a href="https://xiaolit.github.io/2019/02/01/X-509certificate/">HTTPS的证书X.509</a>中<strong>HTTPS中的验证</strong>中提到的那个锚点证书通常来说指的是系统根证书,但是可以自定义说的就是这里。<br>注:<code>SecTrustSetAnchorCertificates(serverTrust对象, 本地证书数组)</code>将本地证书数组设置成需要参与验证的锚点证书。最后还是通过<code>SecTrustEvaluate()</code>方法进行校验,假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书.</p><p>只调用 <code>SecTrustSetAnchorCertificates ()</code> 这个函数的话,那么就只有作为参数被传入的证书作为锚点证书,连系统本身信任的 CA 证书不能作为锚点验证证书链。要想恢复系统中 CA 证书作为锚点的功能,还要再调用下面这个函数:<br>//true 代表仅被传入的证书作为锚点,false 允许系统 CA 证书也作为锚点。</p><pre><code>SecTrustSetAnchorCertificatesOnly(trust, true);</code></pre><p>注:这里设置为true,就可以防止答案就在于用户让系统信任了不应该信任的证书。用户设置系统信任的证书,会作为锚点证书<code>(Anchor Certificate)</code>来验证其他证书,当返回的服务器证书是锚点证书或者是基于该证书签发的证书(可以是多个层级)都会被信任。这就是基于信任链校验方式的最大弱点。我们不能完全相信系统的校验。这也是TLS证书验证的重要理由之一。</p><h4 id="3-验证证书"><a href="#3-验证证书" class="headerlink" title="3.验证证书"></a>3.验证证书</h4><p>证书校验函数,在函数的内部递归地从叶节点证书到根证书验证。需要验证证书本身的合法性(验证签名完整性,验证证书有效期等);验证证书颁发者的合法性(查找颁发者的证书并检查其合法性,这个过程是递归的)。而递归的终止条件是证书验证过程中遇到了锚点证书。</p><pre><code>OSStatus status = SecTrustEvaluate(trust, &result);</code></pre><p>其中当<code>result</code>等于<code>kSecTrustResultProceed</code>表示<code>serverTrust</code>验证成功,且该验证得到了用户认可(例如在弹出的是否信任的<code>alert</code>框中选择<code>always trust</code>)。 <code>kSecTrustResultUnspecified</code>表示<code>serverTrust</code>验证成功,此证书也被暗中信任了,但是用户并没有显示地决定信任该证书。 两者取其一就可以认为对<code>serverTrust</code>验证成功。</p><p>判断验证结果</p><pre><code>if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) { credential = [NSURLCredential credentialForTrust:trust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; }} else { /* 无效的话,取消 */ disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;}if (completionHandler) { completionHandler(disposition, credential);}</code></pre><p>对于代理方法中的<code>completionHandler</code>所需要的参数</p><pre><code>(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandlerNSURLSessionAuthChallengeDisposition:NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理credential会被忽略NSURLSessionAuthChallengeUseCredential:使用指定的证书NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消NSURLCredential://直接创建credential = [NSURLCredential credentialForTrust:trust];</code></pre><p>一多半的功告成,因为AFN也只不过是多分装和处理了一下,调用<code>API</code>不同不过底层<code>API</code>都是相同的即上面我们搞的那些,看着很多其实理清思路了并不多,大家可以看一下<a href="https://github.com/xiaoLit/HTTPSTool" target="_blank" rel="noopener">HTTPSTool</a>,精简了分析注释和代码。</p><h2 id="AFN-如何使用TLS证书验证"><a href="#AFN-如何使用TLS证书验证" class="headerlink" title="AFN 如何使用TLS证书验证"></a>AFN 如何使用TLS证书验证</h2><p>原理我们已经理清楚了,直接上代码说一下不同点就可以了。</p><h3 id="首先加载证书,略有区别"><a href="#首先加载证书,略有区别" class="headerlink" title="首先加载证书,略有区别"></a>首先加载证书,略有区别</h3><pre><code>//证书路径NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cerName" ofType:@".cer"];//创建证书setNSString *cerPath = [HTTPSTool cerPath];NSData *dataSou = [NSData dataWithContentsOfFile:cerPath];NSSet *set = [NSSet setWithObjects:dataSou, nil];</code></pre><h3 id="初始化证书策略"><a href="#初始化证书策略" class="headerlink" title="初始化证书策略"></a>初始化证书策略</h3><pre><code>AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];</code></pre><pre><code>AFSSLPinningMode:验证策略AFSSLPinningModeNone : 不必将证书跟你的 APP 一起打包,完全信任服务器的证书,也就是默认的策略AFSSLPinningModeCertificate : 对比服务器证书跟你的证书是否完全匹配。AFSSLPinningModePublicKey : 只对比服务器证书的 public key 跟你的证书的 public key 是否匹配。</code></pre><p><code>AFSSLPinningModeCertificate</code> 比较安全,也就是我们上面<code>NSURLSession</code>使用的验证方法。因為我们的证书是跟 <code>APP</code> 一起打包的,这也就代表说如果我们的证书过期了或是变动了,我们就得出一版新的 <code>APP</code> 而且旧版 <code>APP</code> 的证书就失效了。我们也可以在每次 <code>APP</code> 启动时,就自动连到某个服务器下载最新的证书,不过此时這个下载就会是有风险的。<br><code>AFSSLPinningModePublicKey</code> 则是只有比对证书里的 <code>public key</code>,所以即使服务器证书有所变动,只要 <code>public key</code> 不变,就能通过验证。<br>所以如果你能确保每个使用者总是使用最新版本的 <code>APP</code>(例如是公司企业內部专用的),那就可以考虑 <code>AFSSLPinningModeCertificate</code>,否则的话选择 <code>AFSSLPinningModePublicKey</code> 是更加良好的解决方案,末尾我会贴一下AFN是如何实现的。</p><pre><code>//是否允许无效证书(也就是自建的证书)默认为NO 如果是需要验证自建证书 需要设置为YESsecurityPolicy.allowInvalidCertificates = NO;</code></pre><pre><code>是否需要验证域名securityPolicy.validatesDomainName = YES;</code></pre><p>验证域名,默认为<code>YES</code>。如置为<code>NO</code>,建议自己添加对应域名的校验逻辑。<br>假如证书的域名与你请求的域名不一致,需把该项设置为<code>NO</code>。即服务器使用其他可信任机构颁发的证书,也可以建立连接。主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为<code>SSL</code>证书上的域名是独立的,假如证书上注册的域名是<code>www.google.com</code>,那么<code>mail.google.com</code>是无法验证通过的。当然,有钱可以注册通配符的域名<code>*.google.com</code>,但这个还是比较贵的。</p><h3 id="设置验证策略"><a href="#设置验证策略" class="headerlink" title="设置验证策略"></a>设置验证策略</h3><p>拿到网络请求的<code>AFHTTPSessionManager</code>类型的<code>manager</code></p><pre><code>//策略加入证书[securityPolicy setPinnedCertificates:set];//网络请求加入策略[manager setSecurityPolicy:securityPolicy];</code></pre><p>就此完工。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>再次说明,如果你只是想最简单使用后台提供的<code>HTTPS</code>接口(<strong>CA机构购买的证书</strong>),<strong>不需要做任何处理</strong>,因为<code>iOS</code>系统会帮我们自动验证<code>HTTPS</code>(包括域名验证等),不管是用的原生<code>NSURLSession</code>构建的网络库,还是使用<code>AFN</code>都不需要我们额外费心思。如果你是有安全方面的要求或者是自制证书才需要加入<code>TLS</code>证书验证。</p><h2 id="几个需要注意的地方"><a href="#几个需要注意的地方" class="headerlink" title="几个需要注意的地方"></a>几个需要注意的地方</h2><p>1.</p><pre><code>CFArrayRef policiesRef;SecTrustCopyPolicies(trust, &policiesRef);可以看到默认加入了 域名验证NSLog(@"%@",policiesRef);打印 policiesRef 后,你会发现默认的验证策略就包含了域名验证,即“服务器证书上的域名和请求域名是否匹配”。如果你的一个证书需要用来连接不同域名的主机,或者你直接用 IP 地址去连接,那么你可以重设验证策略以忽略域名验证:NSMutableArray *policies = [NSMutableArray array];BasicX509 不验证域名是否相同SecPolicyRef policy = SecPolicyCreateBasicX509();[policies addObject:(__bridge_transfer id)policy];修改trust中的验证策略SecTrustSetPolicies(trust, (__bridge CFArrayRef)policies);</code></pre><p>2.<br>见过不止一处地方提到这样的结论,我们来看一下。</p><blockquote><p>选择证书链的哪一节点作为锚点证书打包到App中?<br>很多开发者会直接选择叶子证书。其实对于自建证书来说,选择哪一节点都是可行的。而对于由CA颁发的证书,则建议导入颁发该证书的CA机构证书或者是更上一级CA机构的证书,甚至可以是根证书。这是因为:<br>1) 一般叶子证书的有效期都比较短,Google和Baidu官网证书的有效期也就几个月;而App由于是客户端,需要一定的向后兼容,稍疏于检查,今天发布,过两天证书就过期了。<br>2) 越往证书链的末端,证书越有可能变动;比如叶子证书由特定域名(aaa.bbb.com)改为通配域名(*.bbb.com)等等。短期内的变动,重新部署后,有可能旧版本App更新不及时而出现无法访问的问题。<br>因此使用CA机构证书是比较合适的,至于哪一级CA机构证书,并没有完全的定论,你可以自己评估选择。</p></blockquote><p>这里如果你打包的是比较上级的CA根证书,是解决了过期问题,那么你觉得这个根证书可能只给你一家公司签名吗?如果被有心之人签下来另一个证书,岂不是成了合法的中间人。那么这个TLS证书验证的意义又何在呢?</p><p>对于这个问题很多大神也给出过合理的思路:</p><ol><li>下载更新证书,会涉及到很多处理细节,比如长时间没上线证书处于更替间隙等。</li><li>在即将证书更替时,将新的证书一同打包发版。</li><li><p><code>AFN</code>的<code>AFSSLPinningModePublicKey</code>只是公钥对比并不是整个证书信息对比的思路也不错。续签证书时候用同样的<code>CSR</code>就可以保证公私钥不变。</p></li><li><p><code>AFN</code>的<code>AFSSLPinningModePublicKey</code>公钥验证策略是如何实现的?<br><code>AFN</code>源码</p></li></ol><p><img src="https://ws4.sinaimg.cn/large/006tKfTcgy1g06225rwqmj30wi0hbdk7.jpg" alt=""><br>在代理回调中我们看到关键验证方法中关于公钥验证的处理</p><p><img src="https://ws1.sinaimg.cn/large/006tKfTcgy1g062f98xl0j30oa08t40i.jpg" alt=""></p><p>获取服务器所返回证书链的公钥<br><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1g0626co4jej30rc0kc78a.jpg" alt=""></p><p>我们设置的锚点证书公钥<br><img src="https://ws2.sinaimg.cn/large/006tKfTcgy1g062cdmmy2j30ux0antay.jpg" alt=""></p><h2 id="备注"><a href="#备注" class="headerlink" title="备注"></a>备注</h2><pre><code>NSURLAuthenticationChallenge- (NSURLProtectionSpace *)protectionSpace; // 这个函数返回一个类NSURLProtectionSpace,类中描述服务器中希望的认证方式以及协议,主机端口号等信息。- (NSURLCredential *)proposedCredential; // 建议使用的证书- (NSInteger)previousFailureCount; // 用户密码输入失败的次数。- (NSURLResponse *)failureResponse; // 授权失败的响应头的详细信息- (NSError *)error; // 最后一次授权失败的错误信息</code></pre><pre><code>NSURLProtectionSpace- (NSString *)realm; // 用于定义保护的区域,在服务端可以通过 realm 将不同的资源分成不同的域,域的名称即为 realm 的值,每个域可能会有自己的权限鉴别方案。- (BOOL)receivesCredentialSecurely; // 这个空间内的证书是否能够安全的发送- (BOOL)isProxy; // 代理授权- (NSString *)host; // 服务端主机地址,如果是代理则代理服务器地址- (NSInteger)port; // 服务端端口地址,如果是代理则代理服务器的端口- (NSString *)proxyType; // 代理类型,只对代理授权,比如http代理,socket代理等。- (NSString *)protocol; // 使用的协议,比如http,https, ftp等,- (NSString *)authenticationMethod; // 指定授权方式,比如401,客户端认证,服务端信任,代理等。- (NSArray *)distinguishedNames; // 可接受的颁发机关客户端证书身份验证- (SecTrustRef)serverTrust; // 用于服务端信任,指定一个信任对象,可以用这个对象来建立一个凭证。</code></pre><pre><code>NSURLProtectionSpaceNSURLProtectionSpaceHTTP // http协议NSURLProtectionSpaceHTTPS // https协议NSURLProtectionSpaceFTP // ftp协议NSURLProtectionSpaceHTTPProxy // http代理NSURLProtectionSpaceHTTPSProxy // https代理NSURLProtectionSpaceFTPProxy // ftp代理NSURLProtectionSpaceSOCKSProxy // socks代理NSURLAuthenticationMethodDefault // 协议的默认身份认证NSURLAuthenticationMethodHTTPBasic // http的basic认证,等同于NSURLAuthenticationMethodDefaultNSURLAuthenticationMethodHTTPDigest // http的摘要认证NSURLAuthenticationMethodHTMLForm // html的表单认证适用于任何协议NSURLAuthenticationMethodNTLM // NTLM认证NSURLAuthenticationMethodNegotiate // Negotiate认证NSURLAuthenticationMethodClientCertificate // ssl证书认证,适用于任何协议NSURLAuthenticationMethodServerTrust // ServerTrust认证,适用于任何协议</code></pre><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html" target="_blank" rel="noopener">Overriding TLS Chain Validation Correctly</a>。</p><p><a href="https://www.jianshu.com/p/31bcddf44b8d" target="_blank" rel="noopener">iOS 中对 HTTPS 证书链的验证</a></p><p><a href="http://nelson.logdown.com/posts/2015/04/29/how-to-properly-setup-afnetworking-security-connection/" target="_blank" rel="noopener">如何正確設定 AFNetworking 的安全連線</a></p><p><a href="https://www.jianshu.com/p/88336eab2b8d" target="_blank" rel="noopener">iOS中HTTP/HTTPS授权访问一</a></p><p><a href="https://www.jianshu.com/p/ebee00c785bd" target="_blank" rel="noopener">iOS中HTTP/HTTPS授权访问二</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 安全 </tag>
<tag> 加密 </tag>
</tags>
</entry>
<entry>
<title>HTTPS的证书X.509</title>
<link href="/2019/02/01/X-509certificate/"/>
<url>/2019/02/01/X-509certificate/</url>
<content type="html"><![CDATA[<h3 id="数字证书"><a href="#数字证书" class="headerlink" title="数字证书"></a>数字证书</h3><p><code>HTTPS</code>中使用的证书<code>X.509</code>提到的数字证书其实包含很多东西,那么下面我们具体来看一下。</p><h3 id="证书是什么"><a href="#证书是什么" class="headerlink" title="证书是什么"></a>证书是什么</h3><p><a href="https://zh.wikipedia.org/wiki/X.509" target="_blank" rel="noopener">X.509</a> 是<a href="https://zh.wikipedia.org/wiki/國際電信聯盟電信標準化部門" target="_blank" rel="noopener">ITU-T</a>标准化部门基于他们之前的<a href="https://zh.wikipedia.org/wiki/ASN.1" target="_blank" rel="noopener">ASN.1</a>定义的一套证书标准,是密码学里公钥证书的格式标准,己应用在包括<code>TLS/SSL</code>在内的众多 <code>Intenet</code>协议里。<code>X.509</code>证书里含有公钥、身份信息(比如网络主机名,组织的名称或个体名称等)和签名信息(可以是证书签发机构<code>CA</code>的签名,也可以是自签名)。对于一份经由可信的证书签发机构签名或者可以通过其它方式验证的证书,证书的拥有者就可以用证书及相应的私钥来创建安全的通信,对文档进行<a href="https://zh.wikipedia.org/wiki/數位簽章" target="_blank" rel="noopener">数字签名</a>。另外除了证书本身功能,<code>X.509</code>还附带了<a href="https://zh.wikipedia.org/wiki/X.509" target="_blank" rel="noopener">证书吊销列表</a>和用于从最终对证书进行签名的证书签发机构直到最终可信点为止的证书合法性验证算法。</p><h3 id="证书的来源-签名过程"><a href="#证书的来源-签名过程" class="headerlink" title="证书的来源-签名过程"></a>证书的来源-签名过程</h3><p>在<code>X.509</code>里,一个组织机构或公司是通过发起证书签名请求<code>CSR</code>来得到一份签名的证书。首先需要生成一对钥匙对,然后用其中的私钥对<code>CSR</code>进行签名,并安全地保存私钥。<code>CSR</code>进而包含有请求发起者的身份信息、用来对此请求进行验真的的公钥以及所请求证书专有名称。<code>CSR</code>里还可能带有<code>CA</code>要求的其它有关身份证明的信息。然后<code>CA</code>对这个专有名称发布一份证书,并绑定一个公钥。组织机构可以把受信的根证书分发给所有的成员,也就是类似各大浏览器和系统都预装有早就确定的根证书列表,所以使用主流<code>CA</code>发布的证书<code>SSL/TLS</code>才可以直接正常使用。</p><p>可以看到数字证书的生成是分层级的,下一级的证书需要其上一级证书的私钥签名。<br>所以后者是前者的证书颁发者,也就是说上一级证书的<code>Subject Name</code>是其下一级证书的<code>Issuer Name</code>。<br>那么<code>CA</code>的根证书是什么呢?<br>其实<code>CA</code>根证书是自签名的,即用自己的私钥签名,不需要其他证书的私钥来生成签名。</p><h3 id="证书格式"><a href="#证书格式" class="headerlink" title="证书格式"></a>证书格式</h3><p><code>X.509</code>有多种常用的扩展名。不过其中的一些还用于其它用途,就是说具有这个扩展名的文件可能并不是证书,比如说可能只是保存了私钥。</p><p>我们必须要了解的第一件事是每种类型的文件扩展名是什么。有关<code>DER</code>,<code>PEM</code>,<code>CRT</code>和<code>CER</code>的内容存在很多困惑,许多人错误地说它们都是可以互相转换的。虽然在某些情况下可以,但最佳做法是确定证书的编码格式,然后正确区分不同格式的证书将更容易操作。</p><h4 id="编码格式"><a href="#编码格式" class="headerlink" title="编码格式"></a>编码格式</h4><ul><li><code>.DER</code> - <code>Privacy Enhanced Mail</code><br><code>.DER</code> = <code>DER</code>扩展用于二进制<code>DER</code>编码证书。打开看文本格式,以”—<code>BEGIN</code>…”开头, “—<code>END</code>…”结尾,内容是BASE64编码。这些文件也可能带有<code>CER</code>或<code>CRT</code>扩展名。正确的说法是“我有<code>DER</code>编码证书”而不是“我有<code>DER</code>证书”。<br>查看PEM编码证书:<pre><code>openssl x509 -in certificate.pem/cer/crt -text –noout</code></pre></li></ul><ul><li><code>.PEM</code> - <code>Distinguished Encoding Rules</code><br><code>.PEM</code> = <code>PEM</code>扩展用于不同类型的<code>X.509v3</code>文件,是以“ - <code>BEGIN</code> …”前缀的<code>ASCII</code>(<code>Base64</code>)数据。是二进制格式,不可读。<br>查看DER格式证书的信息:<pre><code>openssl x509 -in certificate.der/cer/crt -inform der -text -noout</code></pre></li></ul><h4 id="相关的文件扩展名"><a href="#相关的文件扩展名" class="headerlink" title="相关的文件扩展名"></a>相关的文件扩展名</h4><ul><li><code>.CRT</code> = <code>CRT</code>扩展用于证书。 证书可以被编码为二进制<code>DER</code>或<code>ASCII PEM</code>。 <code>CER</code>和<code>CRT</code>扩展几乎是同义词。 最常见的于<code>Unix</code> 或类<code>Unix</code>系统(<code>DER</code>编码的<code>.crt</code>,或<code>base64`</code>PEM<code>编码的</code>.crt`,这两种都有)。</li><li><code>.CER</code> =<code>.CRT</code>的替代形式(<code>Microsoft Convention</code>)可以在微软系统环境下将<code>.crt</code>转换为<code>.cer</code>(两种编码格式都有)。</li><li><code>.KEY</code>= <code>KEY</code>扩展用于公共和私有<a href="https://zh.wikipedia.org/wiki/公钥密码学标准" target="_blank" rel="noopener">PKCS #8</a>。密钥同样有两种编码格式。查看<code>KEY</code>的办法:<code>openssl rsa -in mykey.key -text -noout</code>。如果是DER格式的话,同理应该这样了:<code>openssl rsa -in mykey.key -text -noout -inform der</code></li><li><code>PFX/P12</code> - <a href="https://zh.wikipedia.org/wiki/公钥密码学标准" target="_blank" rel="noopener"><code>PKCS#12</code></a>(个人消息交换标准-<code>Personal Information Exchange Syntax Standard</code>)格式,包含证书的同时可能还有带密码保护的私钥。<code>PKCS #12</code> 是微软 <code>PFX</code> 文件的替代者,然而,<code>PKCS #12</code>文件和<code>PFX</code>文件这两个词有时被相互替代使用用。</li></ul><p><code>CRT</code>和<code>CER</code>可以安全地互换的唯一情况是编码类型可以相同。(即<code>PEM</code>编码<code>CRT</code> = <code>PEM</code>编码<code>CER</code>)</p><h4 id="转换证书编码格式"><a href="#转换证书编码格式" class="headerlink" title="转换证书编码格式"></a>转换证书编码格式</h4><p>PEM到DER</p><pre><code>openssl x509 -in cert.crt -outform der-out cert.der</code></pre><p>DER到PEM</p><pre><code>openssl x509 -in cert.crt -inform der -outform pem -out cert.pem</code></pre><p>(提示:要转换KEY文件也类似,只不过把上面的x509换成rsa,要转CSR的话,把x509换成req…)</p><h4 id="组合证书"><a href="#组合证书" class="headerlink" title="组合证书"></a>组合证书</h4><p>在某些情况下,将多个<code>X.509</code>基础设施组合到单个文件中是有利的。一个常见的例子是将私钥和公钥两者结合到相同的证书中。组合密钥和链的最简单的方法是将每个文件转换为PEM编码的证书,然后将每个文件的内容简单地复制到一个新文件中。这适用于组合文件以在Apache中使用的应用程序。</p><h3 id="获得证书"><a href="#获得证书" class="headerlink" title="获得证书"></a>获得证书</h3><h4 id="向权威证书颁发机构申请证书"><a href="#向权威证书颁发机构申请证书" class="headerlink" title="向权威证书颁发机构申请证书"></a>向权威证书颁发机构申请证书</h4><p>用这命令生成一个<code>csr</code>: </p><pre><code>openssl req -newkey rsa:2048 -new -nodes -keyout my.key -out my.csr-req 表明输入文件是一个"请求签发证书文件(CSR)",等待进行签发 -newkey rsa:bits 生成一个bits长度的RSA私钥文件,用于签发 -new 新的请求-nodes 生成的私有密钥文件将不会被加密-keyout file 指定生成的私钥文件名称-out file 处理结束后输出的证书文件</code></pre><p>把<code>csr</code>交给权威证书颁发机构,权威证书颁发机构对此进行签名,完成.保留好<code>csr</code>,当权威证书颁发机构颁发的证书过期的时候,你还可以用同样的<code>csr</code>来申请新的证书,<code>key</code>保持不变。</p><h4 id="或者生成自签名的证书"><a href="#或者生成自签名的证书" class="headerlink" title="或者生成自签名的证书"></a>或者生成自签名的证书</h4><pre><code>openssl req -newkey rsa:2048 -new -nodes -x509 -days 365 -keyout key.pem -out cert.pem-x509 签发X.509格式证书命令-days 365 表示有效天数,这里为365天</code></pre><p>在生成证书的过程中会要你填一堆的东西,其实真正要填的只有<code>Common Name,</code>通常填写你服务器的域名,如<code>"yourcompany.com"</code>,或者你服务器的IP地址,其它都可以留空的。</p><h4 id="获取到站点的证书"><a href="#获取到站点的证书" class="headerlink" title="获取到站点的证书"></a><strong>获取到站点的证书</strong></h4><p>如果是服务器已经配置了证书,直接找后台要公钥证书就好。如果暂时拿不到其实也可以我们自己动手。以google为例。</p><pre><code>openssl s_client -connect www.google.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > https.cer</code></pre><p>该条命令将会在当前路径下,形成<code>google.com</code>站点的公开二进制证书,命名为<code>https.cer</code>。就可以愉快的玩耍了。</p><p>注: <code>-nodes</code> 生成的私有密钥文件将不会被加密。也就是在每次<code>Apache</code>启动<code>Web</code>服务器时,都会要求输入密码,如果忘记了加上可以后期删除私钥中的密码,操作如下:</p><pre><code>cp server.key server.key.orgopenssl rsa -in server.key.org -out server.key</code></pre><h3 id="HTTPS中的验证"><a href="#HTTPS中的验证" class="headerlink" title="HTTPS中的验证"></a>HTTPS中的验证</h3><p>证书校验就是递归地从叶节点证书到根证书验证。需要验证证书本身的合法性(验证签名完整性,验证证书有效期等);验证证书颁发者的合法性(查找颁发者的证书并检查其合法性,这个过程是递归的)。而递归的终止条件是证书验证过程中遇到了<strong><em>锚点证书</em></strong>(锚点证书:通常是嵌入到操作系统中的根证书,这个根证书是权威证书颁发机构颁发的自签名证书。)(当然我们也能自定义)。</p><p>当客户端走<code>HTTPS</code>访问站点时,服务器会返回整个证书链。以下图百度的证书链为例:<br><img src="https://ws3.sinaimg.cn/large/006tNc79gy1g02cxtlmh8j30di07eq3x.jpg" alt=""></p><p>要验证 <code>baidu.com</code> 这个证书有没被篡改,就要用到<code>GlobalSign Organization Validation CA - SHA256 - G2</code> 提供的公钥解密前者的签名得到摘要 <code>Digest1</code>,我们的客户端也计算前者证书的内容得到摘要 <code>Digest2</code>。对比这两个摘要就能知道前者是否被篡改。后者同理,使用 <code>GlobalSign Root CA</code> 提供的公钥验证。当验证到到受信任的根证书时,就能确定 <code>baidu.com</code> 这个证书是可信的。</p><p>注:</p><ol><li><p>那么又是如何确定上面提到的根证书<code>GlobalSign Root CA</code>是受信任的呢?<br>上面’证书的来源-签名过程’-我们不仅提到过,<code>CA</code>根证书是自签名的,即用自己的私钥签名,签署和管理的<code>CA</code>根证书的同时会将证书纳入到各种浏览器和操作系统的可信证书列表中,并由这个列表判断根证书是否可信。所以不要随便导入其他的根证书到你的操作系统中。</p></li><li><p>那么这么说如果我们有这么个证书,也能使用类似<code>CA</code>的功能给其他人的<code>CSR</code>进行签名?<br>不一定。如下图,拓展字段里面有个叫基本约束的数据结构,里面有个字段叫路径长度限制<code>(Path Length Constraint)</code>,表明了该证书能继续签署 <code>CA</code> 子证书的深度,这里为0,说明这个 <code>GlobalSign Organization Validation CA - SHA256 - G2</code> 只能签署客户端证书,而客户端证书不能用于签署新的证书,<code>CA</code>子证书才能这么做。<br><img src="https://ws1.sinaimg.cn/large/006tNc79gy1g02d22g2u6j30gb0kgtbn.jpg" alt=""></p></li></ol><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://support.ssl.com/index.php?/Knowledgebase/Article/View/19/0/der-vs-crt-vs-cer-vs-pem-certificates-and-how-to-convert-them" target="_blank" rel="noopener">DER vs. CRT vs. CER vs. PEM Certificates and How To Convert Them</a></p><p><a href="https://www.cnblogs.com/guogangj/p/4118605.html" target="_blank" rel="noopener">那些证书相关的玩意儿(SSL,X.509,PEM,DER,CRT,CER,KEY,CSR,P12等)</a></p><p><a href="https://www.jianshu.com/p/115dac580bad" target="_blank" rel="noopener">OpenSSL命令使用指南</a></p><p><a href="https://blog.csdn.net/scuyxi/article/details/54884976" target="_blank" rel="noopener">OpenSSL命令详解</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 安全 </tag>
<tag> 加密 </tag>
</tags>
</entry>
<entry>
<title>iOS中的MD5摘要问题</title>
<link href="/2018/12/28/iOSMD5/"/>
<url>/2018/12/28/iOSMD5/</url>
<content type="html"><![CDATA[<p><strong>由汉字和 \0 引发的问题</strong></p><h4 id="分析问题"><a href="#分析问题" class="headerlink" title="分析问题"></a>分析问题</h4><p>首先我们通常使用的MD5加密一般是iOS提供的方法。<br><code>extern unsigned char *CC_MD5(const void *data, CC_LONG len, unsigned char *md)</code><br>对于字符串使用如下</p><pre><code>- (NSString *)md5String { const char *str = [(NSString *)self UTF8String]; unsigned char result[CC_MD5_DIGEST_LENGTH] = {0}; CC_MD5(str, (CC_LONG)strlen(str), result); NSMutableString *ret = [NSMutableString string]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [ret appendFormat:@"%02x", result[i]]; } return ret;}@end</code></pre><p>对于NSData使用如下</p><pre><code>- (NSString *)md5String { const char *str = [self bytes]; unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)self.length, result); NSMutableString *hash = [NSMutableString string]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02X", result[i]]; } return [hash lowercaseString];}</code></pre><h5 id="先看NSString的MD5方法"><a href="#先看NSString的MD5方法" class="headerlink" title="先看NSString的MD5方法"></a>先看NSString的MD5方法</h5><p>对于<code>NSString</code>在使用<code>CC_MD5</code>函数的时候,传入的参数是<code>strlen(str)</code>,对于<code>NSData</code>传入的参数是<code>self.length</code>,一个使用了<code>C</code>的方法,一个使用了<code>Objective-C</code>的方法。</p><p>由于<code>CC_MD5</code>是一个<code>C</code>的函数,但是在使用<code>NSString</code>的时候,<code>NSString</code>的<code>length</code>函数对字符转义进行过了处理,对于普通的字符并没有太大的区别,但是遇到中文的时候就要会出现问题。</p><pre><code>NSString *test = @"我";NSLog(@"%lu",(unsigned long)test.length);const char *cTest = [test UTF8String];NSLog(@"%lu",strlen(cTest));</code></pre><p>前者输出了 1 后者输出了 3, 这其实是很正常的现象,因为一个中文占了3个字节,苹果对<code>length</code>进行了处理,所以,在使用<code>length</code>的时候,你获取到的汉字的长度是1,让我们在看看这个例子:</p><pre><code>NSLog(@"%c",[test characterAtIndex:0]);NSLog(@"%@",[test substringWithRange:NSMakeRange(0, 1)]);</code></pre><p>前者输入了乱码,后者输出了汉字“我”<br>相信很多人也都踩过这个坑,苹果文档中还特意声明<br><code>Use with rangeOfComposedCharacterSequencesForRange: to avoid breaking up character sequences</code>使用这个方法可以避免字符串被中间切断。</p><p><strong>在使用<code>NSString</code>的时候,因为<code>CC_MD5</code>是一个C函数,如果使用<code>NSString</code>提供的<code>length</code>函数被处理过后,汉字或者一些其他鬼字符的长度和<code>strlen</code>计算出来的不一样了,于是导致了这样一个隐藏问题。</strong><br>长度的编码问题可以简单<a href="https://blog.csdn.net/yaomingyang/article/details/79374209" target="_blank" rel="noopener">看下这个</a></p><h5 id="再看NSData的MD5方法"><a href="#再看NSData的MD5方法" class="headerlink" title="再看NSData的MD5方法"></a>再看NSData的MD5方法</h5><p>在 NSData 中,我们计算 CC_MD5 的时候,传入的长度是 self.length,而不再是 strlen() 计算出来的, 让我们看看下面的例子:</p><pre><code>NSString *test = @"aaa\0bbb";NSLog(@"%lu",(unsigned long)test.length);const char *cTest = [test UTF8String];NSLog(@"%lu",strlen(cTest));</code></pre><p>前者输出了7 后者只输出了3,原因是<code>char</code>的数组在遇到’\0’的时候,认为这个字符串已经结束了,因此 将不在对<code>bbb</code>做处理了,而用<code>strlen</code>计算出来的长度只有3了。到这里 你甚至可能会和我一样疑惑,按照这样的说法,上述用<code>NSString</code>传入计算<code>MD5</code>的长度正确吗?我只能说 幸运的是在正常的<code>NSString</code>中我们不会出现<code>'\0'</code>这样的变态字符,除非是你自己刻意去拼出一个这样的字符.<br>但是对于<code>NSData</code>来说,会用<code>NSData</code>去计算<code>MD5</code>通常是通过文件或者音频、图片等转化过来的,因此,在<code>data</code>中什么都有可能出现,如果我没有记错的话,字符<code>'\0'</code>被转化成二进制应该是<code>0000 00000</code>之类的东西,如果你的<code>NSData</code>是通过压缩或者其他方式得到的,就很有可能出现一个这样的二进制<code>.....0000.....</code>(意思就是 二进制的一串中包含了一些特殊的字符,相当于转化成<code>String</code>被识别成了<code>'\0'</code>),于是你再用<code>strlen</code>计算,就只会计算<code>.....0000</code>这么多了,后面的就完全忽略了,于是 这样一个潜在的<code>bug</code>就出现了。 举个例子来说:我们分别利用<code>NSString</code>和将 <code>String</code> 转化为 <code>NSData</code> 的字符串@<code>'aaa'</code>去计算各自的<code>MD5</code>。</p><pre><code>NSString *test = @"aaa";NSLog(@"%@",[test md5StringStr]);NSData *data = [test dataUsingEncoding:NSUTF8StringEncoding];NSLog(@"%@",[data md5String]);</code></pre><p>计算出来的结果一样 都是 47bce5c74f589f4867dbd57e9ca9f808 </p><p>但是 当我们把字符串改成<code>@"aaa\0bbb"</code> (‘\0’)起到了决定性的因素</p><pre><code>NSString *test = @"aaa\0bbb";</code></pre><p>在看看结果<code>NSString</code>算出来的是 47bce5c74f589f4867dbd57e9ca9f808(和上面的一样), 但是<code>NSData</code>算出来的是 ea21d344ad21e7cc63e5d4480f76dc83。<br>由此可知<code>NSString`</code>MD5<code>方法是不足以应付这种情况的,但是</code>NSData`这种就真的对吗?</p><h4 id="并不是完美的解决方法"><a href="#并不是完美的解决方法" class="headerlink" title="并不是完美的解决方法"></a>并不是完美的解决方法</h4><p>筛选了大部分方案,举一个有代表性的也是绝大部分都是这样处理的,就是将<code>NSString</code>转成<code>NSData</code>再<code>MD5</code>。<br>本文引用并验证了<a href="https://scotty-ke.github.io/2017/09/21/MD5加密的两个方法区别/" target="_blank" rel="noopener">这里的解决方法</a>和<a href="http://qiufeng.me/md5" target="_blank" rel="noopener">这个</a>,以及1000Star的<a href="https://github.com/kelp404/CocoaSecurity" target="_blank" rel="noopener">CocoaSecurity</a>都是有问题的。</p><pre><code>#import "NSData+Md5.h"#import <CommonCrypto/CommonCrypto.h>@implementation NSData (Md5)- (NSString *)md5String { const char *str = [self bytes]; //此处打断点 unsigned char result[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)self.length, result); NSMutableString *hash = [NSMutableString string]; for (int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02X", result[i]]; } return [hash lowercaseString];}@end//NSString 的 MD5 转化为 NSData,通过 NSData 的 MD5 计算返回结果#import "NSString+Md5.h"#import "NSData+Md5.h"@implementation NSString (Md5)- (NSString *)md5String { NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; return [data md5String];}@end</code></pre><p>调用<code>NSData</code>MD5时候可以在断点处发现:<br><code>const char *str = [self bytes];</code>这个str也遵循了char的特性,也就是说当<br><code>NSString *test = @"aaa\0bbb";</code>在转成char的时候只会拿到<code>'aaa'</code>,虽然在上一个例子中两个MD5后的结果不一样,那不过是获取长度不一样了而已,<strong>仍然是有问题的</strong>。</p><h4 id="验证方法"><a href="#验证方法" class="headerlink" title="验证方法"></a>验证方法</h4><p>利用MAC终端</p><pre><code>echo -n "aaa\0bbb" |md5sumea21d344ad21e7cc63e5d4480f76dc83</code></pre><pre><code>echo -n "aaa\0bbb" | md5ea21d344ad21e7cc63e5d4480f76dc83</code></pre><p>很奇怪的和<code>NSData</code>相同,或许机制是一样的,存在某种问题,因为方法调用后内部做了什么转码等操作不得而知。<br>但是<code>aaa\0bbb</code>将此放到任意的(Google前两页的结果)MD5网站和3000Star的<a href="https://github.com/blueimp/JavaScript-MD5" target="_blank" rel="noopener">JavaScript-MD5</a>也是相同的这个结果<code>bbb28c3687f7dee991f638bbad6ef747</code>。<br>目前得到的信息只能推论出避免出现’\0’这种异常的字符,其他情况上述解决方式可以应对。</p><hr><p>题外话一:<br>有人说<code>md5sum</code>,<code>md5</code>区别在于<code>md5</code>不会默认识别’\0’和换行,我验证了一下发现并不是。</p><pre><code>➜ ~ echo "aaa\0bbb" | md5sum5edba15569e2da1e986f3933cbe0f271</code></pre><pre><code>➜ ~ echo -n "aaa\0bbb" | md5ea21d344ad21e7cc63e5d4480f76dc83</code></pre><pre><code>➜ ~ echo "aaa\0bbb" | md55edba15569e2da1e986f3933cbe0f271</code></pre><pre><code>➜ ~ echo -n "aaa\0bbb" | md5sumea21d344ad21e7cc63e5d4480f76dc83</code></pre><p>题外话二</p><pre><code>NSString *test = @"aaa\0123";</code></pre><p>NSData计算出来的结果一样都是 <code>e2382c7f3e1ddc2afc53d0857a9d7572</code><br>控制台输出的是<code>448325f9203a4adbf5e7152fa6b66ad0</code><br>网页输出的是<code>03a5080b1ed003e0e21da9b8b225f099</code><br>还是不太了解什么情况。</p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 安全 </tag>
<tag> 加密 </tag>
</tags>
</entry>
<entry>
<title>InjectionIII xcode10更新</title>
<link href="/2018/11/29/InjectionIII/"/>
<url>/2018/11/29/InjectionIII/</url>
<content type="html"><![CDATA[<p><a href="https://github.com/johnno1962/InjectionIII" target="_blank" rel="noopener">InjectionIII - overdue Swift4 rewrite of Injection</a><br>新的安装包 <a href="http://johnholdsworth.com/InjectionIII.app.zip" target="_blank" rel="noopener">InjectionIII 1.2</a><br>新的路径调用方式,其他和原来都没有变化。</p><pre><code>- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if DEBUG //Swift: // for iOS Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load() //for tvOS: Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle")?.load() //Or for macOS: Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle")?.load() //OC : // for iOS [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle"] load]; // for tvOS [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle"] load]; // for masOS [[NSBundle bundleWithPath:@"/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle"] load]; endif}</code></pre><p>注意:新的安装包有可能不会提示移动到应用程序里,不过大家一看调用路径的配置就应该知道了,我们手动把它放进应用程序目录就搞定。</p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 工具 </tag>
</tags>
</entry>
<entry>
<title>Bugly的日常</title>
<link href="/2018/11/22/Bugly-s-Daily/"/>
<url>/2018/11/22/Bugly-s-Daily/</url>
<content type="html"><![CDATA[<h2 id="最好不要看什么其他人写的关于buglg的具体操作,就连符号表的基本提取操作之前看了其他文章都让我绕了好多弯路,直接官方文档为准。"><a href="#最好不要看什么其他人写的关于buglg的具体操作,就连符号表的基本提取操作之前看了其他文章都让我绕了好多弯路,直接官方文档为准。" class="headerlink" title="最好不要看什么其他人写的关于buglg的具体操作,就连符号表的基本提取操作之前看了其他文章都让我绕了好多弯路,直接官方文档为准。"></a>最好不要看什么其他人写的关于buglg的具体操作,就连符号表的基本提取操作之前看了其他文章都让我绕了好多弯路,直接<a href="https://bugly.qq.com/docs/user-guide/symbol-configuration-ios/?v=20181014122344" target="_blank" rel="noopener">官方文档</a>为准。</h2><p>不记得哪位大佬发的图片了,引以为向前的目标<br><img src="https://ws1.sinaimg.cn/large/006tNc79gy1g02d2ifisbj30hv0b00v3.jpg" alt=""></p><pre><code>崩溃日志-[CFPrefsSearchListSource alreadylocked_copyDictionary] -[CFPrefsSearchListSource alreadylocked_copyValueForKey:] </code></pre><p>日历获取在9.x之后的系统使用 [NSCalendar currentCalendar] 会出异常。在8.0之后使用系统新API,做个分类用一下代码</p><pre><code>+ (NSCalendar *)Lit_currentCalendar { if ([NSCalendar respondsToSelector:@selector(calendarWithIdentifier:)]) { return [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; } return [NSCalendar currentCalendar];}</code></pre><hr><blockquote><p>崩溃日志</p><pre><code>libGPUSupportMercury.dylib`gpus_ReturnNotPermittedKillClient</code></pre></blockquote><p>UIWebView在app进入后台时,如果没有加载渲染完就仍然在进行绘制加载,绘制<br>而苹果规定:如果尝试在后台执行OpenGL ES命令,应用程序将被终止。<br>例如<a href="https://developer.apple.com/library/archive/qa/qa1766/_index.html" target="_blank" rel="noopener">How to fix OpenGL ES application crashes when moving to the background</a></p><p>通过通知来监控app是否进入后台和前台,在进入后台的时候禁止OpenGL,在进入前台后重新加载。</p><pre><code>[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];</code></pre><pre><code>- (void)appWillResignActive:(NSNotification *)notification { [self.webView stopLoading];}- (void)appDidBecomeActive:(NSNotification *)notification { [self.webView reload];}</code></pre><pre><code>- (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];}</code></pre><p>看到有人说iOS11修复了这个问题,但我用iOS12.1发现此问题仍在。</p><hr><p>发现一个老旧的bug,再次提醒我注意代码细节。(类似有if就要有else)<br>switch中break的重要性<br>如果没有break语句,则会从满足条件的地方(即与switch(表达式)括号中表达式匹配的case)开始执行,后面的条件都将不再判断直接执行条件内的代码直到switch结构结束。</p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> Bug </tag>
</tags>
</entry>
<entry>
<title>MAC翻越某知名墙体</title>
<link href="/2018/11/21/MAC-AND-GFW/"/>
<url>/2018/11/21/MAC-AND-GFW/</url>
<content type="html"><![CDATA[<h4 id="一个酸酸倒下去,一万个酸酸分支站起来。"><a href="#一个酸酸倒下去,一万个酸酸分支站起来。" class="headerlink" title="一个酸酸倒下去,一万个酸酸分支站起来。"></a>一个酸酸倒下去,一万个酸酸分支站起来。</h4><p>不多说,用了很久了分享一下,懂的都懂,不懂你进来干啥?<br>酸酸<a href="https://github.com/shadowsocks/ShadowsocksX-NG/releases" target="_blank" rel="noopener"><code>S_S_X-NG</code></a><br>酸酸乳<a href="https://github.com/qinyuhang/ShadowsocksX-NG-R/releases" target="_blank" rel="noopener"><code>S_S_X-NG-R</code></a></p>]]></content>
<categories>
<category> 技术杂文 </category>
</categories>
<tags>
<tag> 工具 </tag>
</tags>
</entry>
<entry>
<title>Xcode10删除的libstdc++</title>
<link href="/2018/11/14/Xcode10-And-Libstdc/"/>
<url>/2018/11/14/Xcode10-And-Libstdc/</url>
<content type="html"><![CDATA[<p>众所周知Xcode10中删除的libstdc++库,并且手动导入的话每一次Xcode10升级会自动再次删除。需要再次手动导入。所以找个简单点的方法,虽然还是要每次导入但只需要以下文件和一行代码。</p><p><a href="https://github.com/xiaoLit/libstdc-" target="_blank" rel="noopener">需要下载的库</a><br><code>cd</code>到路径<code>libstdc</code><br><code>sudo sh install.sh</code> </p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> Bug </tag>
</tags>
</entry>
<entry>
<title>iOS截屏和控件截取</title>
<link href="/2018/11/08/iOS-Screen-Shot/"/>
<url>/2018/11/08/iOS-Screen-Shot/</url>
<content type="html"><![CDATA[<h4 id="先上干货懒得看的直接跑Demo。"><a href="#先上干货懒得看的直接跑Demo。" class="headerlink" title="先上干货懒得看的直接跑Demo。"></a>先上干货懒得看的直接跑<a href="https://github.com/xiaoLit/LitShotScreen" target="_blank" rel="noopener">Demo</a>。</h4><hr><p>iOS7之后,苹果开放出一个通知:UIApplicationUserDidTakeScreenshotNotification,截屏时系统就会发出这个通知,需要你注册这个通知,就能捕捉到截屏图片。</p><pre><code> [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getScreenShot:) name:UIApplicationUserDidTakeScreenshotNotification object:nil];</code></pre><pre><code>/** 通知回调 */- (void)getScreenShot:(NSNotification *)notification{ //获取屏幕的截图 UIImage *image = [UIView lit_screenShotImage]; //展示图片 [self showScreenShotImage:image];}</code></pre><p><strong>获取截图的方法有很多</strong></p><h4 id="方法一、-用于全屏截图最稳妥的方法目前iOS12-1也可以使用不过效率低些"><a href="#方法一、-用于全屏截图最稳妥的方法目前iOS12-1也可以使用不过效率低些" class="headerlink" title="方法一、 用于全屏截图最稳妥的方法目前iOS12.1也可以使用不过效率低些"></a><strong>方法一、</strong> 用于全屏截图最稳妥的方法目前iOS12.1也可以使用不过效率低些</h4><pre><code>/** 获取截屏 */+ (UIImage *)lit_screenShotImage { /** 创建一个基于位图的上下文(context),并将其设置为当前上下文(context) @param size 参数size为新创建的位图上下文的大小。它同时是由UIGraphicsGetImageFromCurrentImageContext函数返回的图形大小 @param opaque 透明开关,如果图形完全不用透明,设置为YES以优化位图的存储,我们得到的图片背景将会是黑色,使用NO,表示透明,图片背景色正常 @param scale 缩放因子 iPhone 4是2.0,其他是1.0。虽然这里可以用[UIScreen mainScreen].scale来获取,但实际上设为0后,系统就会自动设置正确的比例了 */ UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, NO, 0); //获取当前上下文 CGContextRef context = UIGraphicsGetCurrentContext(); //遍历所有窗口 用于完善处理一些多层windows显示问题 for (UIWindow *window in [[UIApplication sharedApplication] windows]) { if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { [window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES]; } else { //layer是不能够直接绘制的.要用渲染的方法才能够让它绘制到上下文当中。 [window.layer renderInContext:context]; } } UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return image;}</code></pre><p><strong>注意:</strong>iOS 7上UIView上提供了drawViewHierarchyInRect:afterScreenUpdates:来截图,速度比renderInContext:快15倍</p><p><strong>配合剪裁图片我们可以获得更加合适截图区域</strong></p><pre><code>/** 剪裁图片 */- (UIImage *)lit_clipRect:(CGRect)rect { //将view的转换成图片 UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0); CGContextRef context =UIGraphicsGetCurrentContext(); if ([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) { [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES]; } else { [self.layer renderInContext:context]; } UIImage *targetImage =UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); //对这个图片进行裁剪。 CGImageRef imageRef = targetImage.CGImage; //计算截图区域时需要按比例来 CGFloat scale = [UIScreen mainScreen].scale; //这里可以设置想要截图的区域 CGRect tempRect = CGRectMake(rect.origin.x * scale, rect.origin.y * scale, rect.size.width * scale, rect.size.height * scale); //是C的函数,使用CGRect的坐标都是像素 CGImageRef imageRefRect = CGImageCreateWithImageInRect(imageRef, tempRect); UIImage *clipImage =[[UIImage alloc]initWithCGImage:imageRefRect scale:scale orientation:(UIImageOrientationUp)]; return clipImage;}</code></pre><h4 id="方法二、-适用于单个控件截图"><a href="#方法二、-适用于单个控件截图" class="headerlink" title="方法二、 适用于单个控件截图"></a><strong>方法二、</strong> 适用于单个控件截图</h4><pre><code>/** 截图控件 */- (UIView *)customSnapshotFromView:(UIView *)inputView { UIGraphicsBeginImageContextWithOptions(inputView.frame.size, NO, 0.0); //把控制器View的内容绘制到上下文当中. CGContextRef context =UIGraphicsGetCurrentContext(); //layer是不能够直接绘制的.要用渲染的方法才能够让它绘制到上下文当中。UIGraphicsGetCurrentContext() [inputView.layer renderInContext:context]; //从上下文当中生成一张图片 UIImage*targetImage =UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); UIImageView*imageView = [[UIImageView alloc]initWithImage:targetImage]; imageView.frame= inputView.frame; return imageView;}</code></pre><h4 id="方法三、-iOS7就支持非常便捷的截图方法snapshotViewAfterScreenUpdates"><a href="#方法三、-iOS7就支持非常便捷的截图方法snapshotViewAfterScreenUpdates" class="headerlink" title="方法三、 iOS7就支持非常便捷的截图方法snapshotViewAfterScreenUpdates"></a><strong>方法三、</strong> iOS7就支持非常便捷的截图方法snapshotViewAfterScreenUpdates</h4><pre><code>- (UIView *)customSnapshotFromView:(UIView *)inputView { //afterUpdates参数表示是否在所有效果应用在视图上了以后再获取快照。 //例如,如果该参数为NO,则立马获取该视图现在状态的快照,反之,以下代码只能得到一个空白快照: UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES]; snapshot.layer.masksToBounds = YES; return snapshot;}</code></pre><p>但是据说iOS10之后就不好使了,我用的iOS12测试发现和iOS9模拟器都无法使用,所以现在使用的话此方法有待考察。</p><hr><p>有可能有特殊的情况,会要求横屏等其他方向截屏变成正常竖屏样式展示,将截屏的图片进行仿射变换就可以了。Demo也有相关代码。</p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> UI </tag>
</tags>
</entry>
<entry>
<title>HEXO+GitHub搭建博客的一些心得</title>
<link href="/2018/10/09/Material/"/>
<url>/2018/10/09/Material/</url>
<content type="html"><![CDATA[<p><a href="https://material.io/tools/icons/?search=link&style=baseline" target="_blank" rel="noopener"><code>Material Design</code>图片库</a><br><a href="https://github.com/Vultur/hexo-theme-material" target="_blank" rel="noopener"><code>hexo-theme-material</code>配置文件详细描述</a><br>我使用的<code>HEXO+ GitHub page</code>的形式搭建自己的博客,<a href="https://hanyx1992.github.io/2018/05/02/helloworld/" target="_blank" rel="noopener">参考大佬</a>。<br>搭建好了采用的hexo-theme-material主题,然后还需要配置一些我们常用的功能。</p><h2 id="material-1-5-5和1-5-6版本使用过程中出现报错"><a href="#material-1-5-5和1-5-6版本使用过程中出现报错" class="headerlink" title="material-1.5.5和1.5.6版本使用过程中出现报错"></a>material-1.5.5和1.5.6版本使用过程中出现报错</h2><p>解决方案<a href="https://github.com/viosey/hexo-theme-material/issues/686" target="_blank" rel="noopener">issue#686</a>。<br>修改layout/_widget/dnsprefetch.ejs文件。修改内容如下:</p><pre><code><% } else if(theme.comment.use.startsWith("disqus")) { %>修改为<% } else if(theme.comment.use && theme.comment.use.startsWith("disqus")) { %></code></pre><h2 id="图床"><a href="#图床" class="headerlink" title="图床"></a>图床</h2><p>更新<br>现在七牛云需要绑定备案网站麻烦至极,可以改用微博图床。推荐<a href="https://toolinbox.net/iPic/" target="_blank" rel="noopener">IPic</a>。</p><hr><p>综合各种因素,七牛云足以。<br>纯界面操作这位大佬<a href="https://sspai.com/post/43598" target="_blank" rel="noopener">Jackier</a>图片贴的非常详细,看了就懂。</p><h2 id="独立页面"><a href="#独立页面" class="headerlink" title="独立页面"></a>独立页面</h2><h3 id="添加任意侧栏功能"><a href="#添加任意侧栏功能" class="headerlink" title="添加任意侧栏功能"></a>添加任意侧栏功能</h3><pre><code>pages: title: #可以填写任意标题 link: "http://example.com" #点击跳转的链接 icon: link #图标 可以到Material Design图片图找 divider: false #分割线</code></pre><h3 id="友情链接页面"><a href="#友情链接页面" class="headerlink" title="友情链接页面"></a>友情链接页面</h3><h4 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h4><ul><li>在<code>hexo</code>目录下的<code>source</code>文件夹内创建一个名为<code>links</code>(只是建议,可根据自己喜好修改)的文件夹。</li><li>然后在文件内创建一个名为<code>index.md</code>的<code>Markdown</code>文件。</li><li>在<code>index.md</code>文件内写入如下内容即可。</li></ul><pre><code>---title: linksdate:layout: links---</code></pre><p><code>title</code> 可修改,<code>layout</code> 不可修改。</p><h4 id="添加数据"><a href="#添加数据" class="headerlink" title="添加数据"></a>添加数据</h4><ul><li>同样在在<code>hexo</code>目录下的<code>source</code>文件夹内创建一个名为<code>_data</code>(禁止改名)的文件夹。</li><li>然后在文件内创建一个名为 <code>links.yml</code> 的文件。</li><li>单个友情链接的格式为:</li></ul><pre><code> Name: link: http://example.com avatar: http://example.com/avatar.png descr: "这是一个描述"</code></pre><ul><li>添加多个友情链接,只需要根据上面的格式重复填写即可。</li><li>将 <code>Name</code> 改为友情链接的名字,例如 Jack。</li><li><code>http://example.com</code>为友情链接的地址。</li><li><code>http://example.com/avatar.png</code> 为友情链接的头像。</li><li>这是一个描述 为友情链接描述。</li></ul><h4 id="开启"><a href="#开启" class="headerlink" title="开启"></a>开启</h4><p>在主题<code>_config.yml</code>文件下加入</p><pre><code>pages: 友情链接: "/links/"</code></pre><h3 id="时间线"><a href="#时间线" class="headerlink" title="时间线"></a>时间线</h3><h4 id="创建-1"><a href="#创建-1" class="headerlink" title="创建"></a>创建</h4><p>在 <code>hexo</code> 目录下的 <code>source</code> 文件夹内创建一个名为 <code>timeline</code>(只是建议,可根据自己喜好修改)的文件夹。<br>然后在文件内创建一个名为 <code>index.md</code> 的 <code>Markdown</code> 文件。<br>在 index.md 文件内写入如下内容即可。</p><pre><code>---title: timelinedate:layout: timeline---</code></pre><p> <code>title</code> 可修改,<code>layout</code> 不可修改。</p><h4 id="开启-1"><a href="#开启-1" class="headerlink" title="开启"></a>开启</h4><pre><code>pages: 时间轴: link: "/timeline" icon: timeline divider: false</code></pre><h2 id="评论系统"><a href="#评论系统" class="headerlink" title="评论系统"></a>评论系统</h2><p>material本身也集成了很多其他主流的评论系统大家可以自行选择。但我综合各种因素,例如使用是否方便,是否需要备案,是否需要翻越某个神秘的墙体,选择了<code>gitalk</code>。</p><h3 id="注册OAuth-Application"><a href="#注册OAuth-Application" class="headerlink" title="注册OAuth Application"></a>注册<code>OAuth Application</code></h3><p><a href="https://github.com/settings/applications/new" target="_blank" rel="noopener">点击此处</a> 来注册一个新的 <code>OAuth Application</code>。注意最后一个<code>Authorization callback URL</code>要填写自己博客的<code>url</code>。注册成功后得到一个<code>Client ID</code>和<code>Client Secret</code>。</p><p>###配置参数<br>在<code>material</code>的<code>_config.yml</code>中找到<code>Comment Systems</code>相关配置。</p><pre><code># Comment Systemscomment: use: "gitalk" gitalk_repo: gitalk #这里是因为我在第一步中注册 OAuth Application 的 repo 名字叫做 gitalk gitalk_owner: xiaoLit #这里当然是 github 用户名了 gitalk_client_id: ****** #这里是第一步的Client ID gitalk_client_secret: ****** #这里是第一步的Client Secret</code></pre><h3 id="部署"><a href="#部署" class="headerlink" title="部署"></a>部署</h3><p>配置好后部署就可以了。</p><p>注意: 有可能会出现<code>Error: Validation Failed</code>错误。这是由于创建<code>gitalk</code>时会将当前<code>post</code>的<code>url</code>作为<code>label</code>创建一个<code>issue</code>,而<code>issue</code>的<code>label</code>长度最大只支持50个字符,如果文章标题有过多中文或长度太长,都会产生这个问题。<br>解决方案:在该目录~/themes/material/layout/_widget/comment/gitalk/main.ejs</p><pre><code><script> var gitalk = new Gitalk({ clientID: '<%= theme.comment.gitalk_client_id %>', clientSecret: '<%= theme.comment.gitalk_client_secret %>', repo: '<%= theme.comment.gitalk_repo %>', owner: '<%= theme.comment.gitalk_owner %>', admin: ['<%= theme.comment.gitalk_owner %>'], // facebook-like distraction free mode distractionFreeMode: false, id: '<%= page.title.substr(0,48) %>' //额外加入了这句 }) gitalk.render('gitalk-container')</script></code></pre><p><a href="https://github.com/viosey/hexo-theme-material/pull/617/commits/a6863aa930599ddd6f6e69bbdc66fdfc89a4deb3" target="_blank" rel="noopener">#617</a></p><p>##topPost<br>使用该插件可以将指定文章置顶。<br> <code>npm install hexo-helper-post-top --save</code><br>使用在您需要置顶文章的 front-matter 中,添加 top: true 即可置顶。</p><h2 id="使用CDN加速"><a href="#使用CDN加速" class="headerlink" title="使用CDN加速"></a>使用CDN加速</h2><blockquote><p>目前没有必要使用</p></blockquote><p>修改文件:博客/themes/material/_config.yml<br>根据自己使用不同版本去寻找相应的链接,<a href="https://cdn.jsdelivr.net/gh/viosey/hexo-theme-material/source/" target="_blank" rel="noopener">查看对应版本链接</a>。</p><pre><code>materialcdn: https://cdn.jsdelivr.net/gh/viosey/[email protected]/source</code></pre><h2 id="搜索功能"><a href="#搜索功能" class="headerlink" title="搜索功能"></a>搜索功能</h2><p>Material 主题内置了 google swiftype local 三种搜索系统。<br>需要在文章内搜索的话使用本地搜索,需要安装<code>hexo-generator-search</code>插件。</p><h3 id="安装hexo-generator-search"><a href="#安装hexo-generator-search" class="headerlink" title="安装hexo-generator-search"></a>安装<code>hexo-generator-search</code></h3><p><code>$ npm install hexo-generator-search --save</code></p><h3 id="配置参数"><a href="#配置参数" class="headerlink" title="配置参数"></a>配置参数</h3><ul><li>修改文件:博客/themes/material/_config.yml</li></ul><pre><code>search: use: local #此处使用了本地搜索</code></pre><ul><li>修改文件:博客/_config.yml</li></ul><pre><code>search: path: search.xml field: post</code></pre><h2 id="新版prettify自动折行替换为不折行并添加滚动条"><a href="#新版prettify自动折行替换为不折行并添加滚动条" class="headerlink" title="新版prettify自动折行替换为不折行并添加滚动条"></a>新版prettify自动折行替换为不折行并添加滚动条</h2><h3 id="解决方案issues-616"><a href="#解决方案issues-616" class="headerlink" title="解决方案issues#616"></a>解决方案<a href="https://github.com/viosey/hexo-theme-material/issues/616" target="_blank" rel="noopener">issues#616</a></h3><ul><li>把<code>source/css/prettify.css</code>直接替换为</li></ul><pre><code>@charset "UTF-8";/* for color-themes-for-google-code-prettify *//* Automatically add a scroll bar instead of a newline */pre{ white-space: pre;}#post-content .prettyprint{ padding: 1.2em; border-radius: 0;}#post-content ol,#post-content ul{ padding-left: 32px; font-size: 1rem; margin: 0; overflow: initial;}#post-content pre code, #post-content pre tt { padding-right: 2em;}#post-content .prettyprint li.L0, #post-content .prettyprint li.L1,#post-content .prettyprint li.L2, #post-content .prettyprint li.L3,#post-content .prettyprint li.L4, #post-content .prettyprint li.L5,#post-content .prettyprint li.L6, #post-content .prettyprint li.L7,#post-content .prettyprint li.L8, #post-content .prettyprint li.L9 { padding-left: 0.5em;}</code></pre><ul><li><code>source/css/prettify.min.css</code>直接替换为:</li></ul><pre><code>pre{white-space:pre}#post-content .prettyprint{padding:1.2em;border-radius:0}#post-content ol,#post-content ul{padding-left:32px;font-size:1rem;margin:0;overflow:initial}#post-content pre code,#post-content pre tt{padding-right:2em}#post-content .prettyprint li.L0,#post-content .prettyprint li.L1,#post-content .prettyprint li.L2,#post-content .prettyprint li.L3,#post-content .prettyprint li.L4,#post-content .prettyprint li.L5,#post-content .prettyprint li.L6,#post-content .prettyprint li.L7,#post-content .prettyprint li.L8,#post-content .prettyprint li.L9{padding-left:.5em}</code></pre><h2 id="去代码掉行号"><a href="#去代码掉行号" class="headerlink" title="去代码掉行号"></a>去代码掉行号</h2><p>看代码还是一行舒服<br>默认hanabi是支持的,只需要</p><pre><code>hanabi: enable: true line_number: true</code></pre><p>可是material-1.5.5和1.5.6版本使用prettify不支持去掉行号,那么就只有暴力处理了。<br>修改<code>themes/material/layout/_partial</code> 下的文件<code>import_js.ejs</code></p><pre><code>$('pre').addClass('prettyprint linenums').attr('style', 'overflow:auto;');替换为:$('pre').addClass('prettyprint').attr('style', 'overflow:auto;');</code></pre><h2 id="使用-CDN加速"><a href="#使用-CDN加速" class="headerlink" title="使用 CDN加速"></a>使用 CDN加速</h2><p>定位到 主题配置文件 进行配置。<br>MaterialCDN<br>现在你可以使用 CDN 来加速 Material 主题引用的静态文件,只需要在 materialcdn 中填入你的 CDN 的 URL 路径即可。默认为空、从网站源站加载。</p><p>注意!填入的 URL 末尾不需要带 / !<br>例如,您可以这么配置:</p><p>vendors:<br> materialcdn: <a href="https://cdn.jsdelivr.net/gh/viosey/hexo-theme-material@latest/source" target="_blank" rel="noopener">https://cdn.jsdelivr.net/gh/viosey/hexo-theme-material@latest/source</a><br>您可以使用由 jsDelivr 提供的公共 CDN 加速您的博客。</p><h2 id="安装prettify的正确姿势"><a href="#安装prettify的正确姿势" class="headerlink" title="安装prettify的正确姿势"></a>安装prettify的正确姿势</h2><p><strong>更新</strong><br>分割线以下安装步骤为老版的内容,1.5.5和1.5.6版本无需再用此方法。</p><ul><li>直接改主题下<code>_config.yml</code>文件即可</li></ul><pre><code>prettify: enable: true theme: "vibrant-ink"</code></pre><p>集成了大部分prettify,可在目录<code>themes/material/source/css/prettify</code>查看具体名字,觉得挨个尝试麻烦的话可以直接到<a href="https://jmblog.github.io/color-themes-for-google-code-prettify/" target="_blank" rel="noopener">这里</a>看效果然后填写目录下的相应主题名字。</p><hr><hr><p>开始看了 <a href="https://hanyx1992.github.io/2018/05/02/helloworld/" target="_blank" rel="noopener">@旭丶Joy</a> 大佬的文章设置prettify发现代码块滚动条异常。于是去爬论坛,发现这个问题在<a href="https://github.com/viosey/hexo-theme-material/issues/383" target="_blank" rel="noopener">issue#383</a>中解决,并且在<a href="https://github.com/viosey/hexo-theme-material/issues/616" target="_blank" rel="noopener">issue#616</a>优化过,也就是新版本是包含的。所以猜想应该该主题支持的方式有可能不同,于是在<a href="https://github.com/viosey/hexo-theme-material/pull/395" target="_blank" rel="noopener">issue#395</a>找到了该姿势。</p><h3 id="安装code-prettify"><a href="#安装code-prettify" class="headerlink" title="安装code-prettify"></a>安装code-prettify</h3><ul><li><code>git clone https://github.com/google/code-prettify.git</code></li><li>将src重命名为prettify,然后目录prettify拷贝至你的 hexo 博客的 source/vendors/下, vendors是新建的目录</li></ul><h3 id="安装color-themes-for-google-code-prettify"><a href="#安装color-themes-for-google-code-prettify" class="headerlink" title="安装color-themes-for-google-code-prettify"></a>安装color-themes-for-google-code-prettify</h3><pre><code>$ cd yourproject$ npm i color-themes-for-google-code-prettify -S$ cd node_modules/color-themes-for-google-code-prettify/dist$ cp -r themes ../../../themes/material/source/vendors/prettify/</code></pre><p>其实就是安装<code>color-themes-for-google-code-prettify</code>, 将dist/themes拷贝到<code>themes/material/source/vendors/prettify/</code>下</p><h3 id="prettify用法"><a href="#prettify用法" class="headerlink" title="prettify用法"></a>prettify用法</h3><ul><li>主题下的<code>_config.template.yml</code>追加以下内容:</li></ul><pre><code>prettify: enable: true # if true, customcss.enable must be true theme: "vibrant-ink" # default value: "vibrant-ink" # "theme-name without .css"# custom csscustomcss: enable: true csspath: [ # css file path "/css/custom-prettify.min.css" ]</code></pre><ul><li>在主题下的<code>layout/_partial/head.ejs</code>追加下面内容:</li></ul><pre><code><% if (theme.prettify.enable){ %> <!-- prettify代码高亮主题css引入 --> <link href="/vendors/prettify/themes/<%= theme.prettify.theme %>.css" rel="stylesheet"> <% } %> <!-- 自定义css引入 --> <% if (theme.customcss.enable){ %> <% if (theme.customcss.csspath) { %> <% for (var i in theme.customcss.csspath) { %> <link href="<%= theme.customcss.csspath[i] %>" rel="stylesheet"> <% } %> <% } %> <% } %></code></pre><ul><li>在主题下的<code>layout/_partial/import_js.ejs</code>加入以下内容:</li></ul><pre><code><!-- prettify代码高亮js引入 --><% if (theme.prettify.enable){ %> <script src="/vendors/prettify/prettify.js" type="text/javascript"></script><%}%><!-- Window Load--><script type="text/ls-javascript" id="window-load"> $(window).on('load', function() { // Post_Toc parent position fixed $('.post-toc-wrap').parent('.mdl-menu__container').css('position', 'fixed'); }); $(function() { <!-- prettify代码高亮js引入 --> <% if (theme.prettify.enable){ %> $('pre').addClass('prettyprint linenums').attr('style', 'overflow:auto;'); prettyPrint(); <%}%> })</script></code></pre><ul><li>网站下的<code>_config.template.yml</code>文件修改以下内容:</li></ul><pre><code>highlight: enable: false</code></pre><h3 id="最后验收成果啦"><a href="#最后验收成果啦" class="headerlink" title="最后验收成果啦"></a>最后验收成果啦</h3><pre><code>$ hexo clean$ hexo g$ hexo s</code></pre><p>叮!搞定!</p>]]></content>
<categories>
<category> 技术杂文 </category>
</categories>
<tags>
<tag> 博客 </tag>
</tags>
</entry>
<entry>
<title>iOS应该了解的HTTPS</title>
<link href="/2018/07/15/iOS-s-HTTPS/"/>
<url>/2018/07/15/iOS-s-HTTPS/</url>
<content type="html"><![CDATA[<blockquote><p>问题来源:首先为什么要接入<code>HTTPS</code>,对比<code>HTTP</code>有什么好处?<br>并不是苹果爸爸要求我们(目前无限期延长),而是确实有其需要的重要意义。</p></blockquote><p>本文乃至博客会引用大量维基等需要翻的内容,有链接问题的请翻越。</p><h2 id="如何食用"><a href="#如何食用" class="headerlink" title="如何食用"></a>如何食用</h2><p>如果你只是想最简单使用后台提供的<code>HTTPS</code>接口(<strong>CA机构购买的证书</strong>),并不想了解更多内容,那么你可以直接不用看了,因为<code>iOS</code>系统会帮我们自动验证<code>HTTPS</code>,不管是用的原生NSURLSession构建的网络库,还是使用AFN<strong>都不需要我们额外做任何处理</strong>。如果你是有安全方面的要求(金融等相关-需要加入<code>TLS</code>证书验证)或者是<strong>自制证书</strong>和想了解更多关于<code>HTTPS</code>的相关内容可以继续往下看。</p><p>相关内容可以不用直接去看到时机了还会再出现。</p><p><a href="https://xiaolit.github.io/2019/02/01/X-509certificate/">HTTPS的证书X.509</a></p><p><a href="https://xiaolit.github.io/2019/02/12/iOS如何在HTTPS中使用TLS证书验证/">iOS如何在HTTPS中使用TLS证书验证</a></p><h2 id="为什么要用HTTPS"><a href="#为什么要用HTTPS" class="headerlink" title="为什么要用HTTPS"></a>为什么要用HTTPS</h2><p>国际惯例先放Demo。<br><a href="https://github.com/xiaoLit/HTTPSTool" target="_blank" rel="noopener">HTTPSTool</a></p><h3 id="中间人攻击"><a href="#中间人攻击" class="headerlink" title="中间人攻击"></a>中间人攻击</h3><p>大家应该听说过一个词叫做<a href="https://zh.wikipedia.org/wiki/中间人攻击" target="_blank" rel="noopener">中间人攻击</a>MITM(维基)。在HTTP</p><h2 id="HTTPS基本概念"><a href="#HTTPS基本概念" class="headerlink" title="HTTPS基本概念"></a>HTTPS基本概念</h2><h3 id="HTTPS是什么"><a href="#HTTPS是什么" class="headerlink" title="HTTPS是什么"></a>HTTPS是什么</h3><p>超文本传输安全协议(英语:<code>Hypertext Transfer Protocol Secure</code>,缩写:<code>HTTPS</code>,常称为<code>HTTP</code> <code>over TLS</code>,<code>HTTP over SSL</code>或<code>HTTP Secure</code>)是一种通过计算机网络进行安全通信的传输协议。<br><code>HTTPS</code> 容易让人误解,以为它是和 <code>HTTP</code> 相似的协议,其实并不是,它是个组合的概念。简单来说就是经由HTTP进行通信,但利用<code>SSL/TLS</code>来加密数据包,称为<code>HTTPS</code>。<br><code>HTTPS</code>报文中的任何东西都被加密,包括所有报头和荷载。除了可能的<a href="https://zh.wikipedia.org/wiki/选择密文攻击" target="_blank" rel="noopener">选择密文攻击</a>(在以后会谈到)之外,一个攻击者所能知道的只有在两者之间有一连接这一事实,最大化的保护交换数据的隐私与完整性。</p><h3 id="HTTPS-请求-和-HTTP-请求的异同"><a href="#HTTPS-请求-和-HTTP-请求的异同" class="headerlink" title="HTTPS 请求 和 HTTP 请求的异同"></a>HTTPS 请求 和 HTTP 请求的异同</h3><p>首先<code>HTTP</code>的<code>URL</code>是由<code>http://</code>起始与默认使用端口80,而<code>HTTPS</code>的URL则是由<code>https://</code>起始与默认使用端口443。普通 <code>HTTP</code> 请求直接基于 <code>TCP</code>,在互联网上明文传播,而且没有任何校验,链路上的每一个节点都可以对数据包进行篡改,使用手机网络访问 <code>HTTP</code> 网站被插入流量球甚至广告等运营商劫持行为就是最常见的例子。而 <code>HTTPS</code> 请求运行在 <code>TLS</code> 层之上,<code>TLS</code> 运行在 <code>TCP</code> 上,<code>TLS</code> 有独特的握手、建立连接、数据验证机制,让运行商劫持无处下手:只要任何一个数据包被篡改,数据校验就会失败,这个请求会客户端直接抛弃,网页不会显示。</p><h3 id="SSL-TLS是什么"><a href="#SSL-TLS是什么" class="headerlink" title="SSL/TLS是什么"></a>SSL/TLS是什么</h3><p><code>TLS</code> 全称为<code>Transport Layer Security</code>,中文名称为<a href="https://zh.wikipedia.org/wiki/傳輸層安全性協定" target="_blank" rel="noopener">传输层安全性协议</a>,及其前身 <code>SSL</code>全称为<code>Secure Sockets Layer</code>,中文名称为安全套接层(现不常用顾不详述),以下全部称为<code>TLS</code>。<code>TLS</code>的全称听名字就知道是在<a href="https://zh.wikipedia.org/wiki/OSI模型" target="_blank" rel="noopener">OSI模型</a>中属于传输层,目的是为互联网通信提供安全及数据完整性保障。简单的来说就是在客户端与服务端之间建立一个防窃听、防篡改的可信信息传递通道。<br>一些细节和以前的技术历史虽然不再需要我们仔细关注,有兴趣的可以深入了解一下。</p><ul><li><a href="http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html" target="_blank" rel="noopener">SSL/TLS协议运行机制的概述</a></li><li><a href="http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html" target="_blank" rel="noopener">图解SSL/TLS协议</a></li><li><a href="https://segmentfault.com/a/1190000002554673" target="_blank" rel="noopener">SSL/TLS原理详解</a></li></ul><p>简单的来说,<code>SSL/TSL</code>通过四次握手,主要交换三个信息:</p><ol><li><p><strong>数字证书:</strong>该证书包含了公钥等信息,一般是由服务器发给客户端,接收方通过验证这个证书是不是由信赖的CA签发,或者与本地的证书相对比,来判断证书是否可信。假如需要双向验证,则服务器和客户端都需要发送数字证书给对方验证。<br> <strong>数字证书的更加详细相关可以看我的另一篇博客</strong><a href="https://xiaolit.github.io/2019/02/01/X-509certificate/">HTTPS的证书X.509</a>。</p></li><li><p><strong>三个随机数:</strong>这三个随机数构成了后续通信过程中用来对数据进行对称加密解密的“对话密钥”。<br>首先客户端先发第一个随机数<code>N1</code>,然后服务器回了第二个随机数<code>N2</code>(这个过程同时把之前提到的证书发给客户端),这两个随机数都是明文的;而第三个随机数<code>N3</code>(这个随机数被称为<code>Premaster secret</code>),客户端用数字证书的公钥进行非对称加密,发给服务器;而服务器用只有自己知道的私钥来解密,获取第三个随机数。这样,服务端和客户端都有了三个随机数<code>N1+N2+N3</code>,然后两端就使用这三个随机数来生成“对话密钥”,在此之后的通信都是使用这个“对话密钥”来进行对称加密解密。因为这个过程中,服务端的私钥只用来解密第三个随机数,从来没有在网络中传输过,这样的话,只要私钥没有被泄露,那么数据就是安全的。</p></li><li><p><strong>加密通信协议:</strong>就是双方商量使用哪一种加密方式,假如两者支持的加密方式不匹配,则无法进行通信。</p></li></ol><p>有个常见的问题,关于随机数为什么要三个?只最后一个随机数N3不可以么?<br>这是由于<code>SSL/TLS</code>设计,就假设服务器不相信所有的客户端都能够提供完全随机数,假如某个客户端提供的随机数不随机的话,就大大增加了“对话密钥”被破解的风险,所以由三组随机数组成最后的随机数,保证了随机数的随机性,以此来保证每次生成的“对话密钥”安全性。</p><h2 id="HTTPS的局限"><a href="#HTTPS的局限" class="headerlink" title="HTTPS的局限"></a>HTTPS的局限</h2><p><code>TLS</code>有两种策略:简单策略和交互策略。交互策略更为安全,但需要用户在他们的浏览器中安装个人的证书来进行认证。<br>不管使用了哪种策略,协议所能提供的保护总强烈地依赖于浏览器的实现和服务器软件所支持的加密算法。</p><p>HTTPS并不能防止站点被<a href="https://zh.wikipedia.org/wiki/網路爬蟲" target="_blank" rel="noopener">网络爬虫</a>抓取。在某些情形中,被加密资源的<code>URL</code>可仅通过截获请求和响应的大小推得,这就可使攻击者同时知道明文(公开的静态内容)和密文(被加密过的明文),从而使<a href="https://zh.wikipedia.org/wiki/选择密文攻击" target="_blank" rel="noopener">选择密文攻击</a>成为可能。</p><p>因为HTTPS连接所用的公钥以明文传输,因此中国大陆的防火长城可以对特定网站按照匹配的黑名单证书,通过伪装成对方向连接两端的计算机发送RST包干扰两台计算机间正常的TCP通讯,以打断与特定IP地址之间的443端口握手,或者直接使握手的数据包丢弃,导致握手失败,从而导致TLS连接失败。这也是一种互联网信息审查和屏蔽的技术手段。</p><h3 id="TLS证书验证的作用"><a href="#TLS证书验证的作用" class="headerlink" title="TLS证书验证的作用"></a>TLS证书验证的作用</h3><p>如果有人通过一些手段通过了域名所有权认证(非常容易,域名邮箱、<code>DNS</code>指向甚至在根目录放一个文件都可以验证通过),拿到了一个合法的对应你的域名的 <code>HTTPS</code> 证书,这时候他在广场开放了一个没有密码的 <code>wifi</code>,命名为 <code>CMCC</code>,这样,几乎所有的开着 <code>wifi</code> 的手机都会自动连接,这时候他只需要做一个简单的 <code>DNS</code> 劫持,就可以把所有应该向你网站发送的需求劫持到他那里。<br>但是:如果你做了 <code>TLS</code> 证书验证,那么他的证书就不会被验证通过。他也没法用你的证书启动服务,因为私钥和证书的验证是 <code>SSL</code> 协议强制做的,他没有你的私钥,他的 <code>web server</code> 就没法启动。所以,私钥千万不能泄露。<br>所以这就有了<a href="https://xiaolit.github.io/2019/02/12/iOS如何在HTTPS中使用TLS证书验证/">《iOS如何在HTTPS中使用TLS证书验证》</a>。</p><h2 id="iOS在HTTPS中使用TLS证书验证相关的API和方法"><a href="#iOS在HTTPS中使用TLS证书验证相关的API和方法" class="headerlink" title="iOS在HTTPS中使用TLS证书验证相关的API和方法"></a>iOS在HTTPS中使用TLS证书验证相关的API和方法</h2><p>终于到了大家最喜闻乐见的代码环节了。<br>可以看我的另一篇博客<a href="https://xiaolit.github.io/2019/02/12/iOS如何在HTTPS中使用TLS证书验证/">《iOS如何在HTTPS中使用TLS证书验证》</a>。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><h3 id="如何才能知道一个HTTPS服务器是否符合ATS特性中的要求的呢?"><a href="#如何才能知道一个HTTPS服务器是否符合ATS特性中的要求的呢?" class="headerlink" title="如何才能知道一个HTTPS服务器是否符合ATS特性中的要求的呢?"></a>如何才能知道一个HTTPS服务器是否符合ATS特性中的要求的呢?</h3><p>使用<code>nscurl</code>命令:</p><pre><code>nscurl --ats-diagnostics --verbose https://example.com</code></pre><p>以百度为例</p><pre><code>TLSv1.2 with PFS disabled and insecure HTTP allowedATS Dictionary:{ NSExceptionDomains = { "www.baidu.com" = { NSExceptionAllowsInsecureHTTPLoads = true; NSExceptionMinimumTLSVersion = "TLSv1.2"; NSExceptionRequiresForwardSecrecy = false; }; };}Result : PASS</code></pre><p>可以看到服务器相应支持TLS版本和结果。</p><h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference"></a>Reference</h2><p><a href="https://lvwenhan.com/autolayout-club/478.html" target="_blank" rel="noopener">写给 iOS 开发者看的 HTTPS 指南 </a></p><p><a href="https://lvwenhan.com/操作系统/489.html" target="_blank" rel="noopener">软件工程师需要了解的网络知识:从铜线到HTTP(五)—— HTTP 和 HTTPS</a></p><p><a href="http://oncenote.com/2014/10/21/Security-1-HTTPS/" target="_blank" rel="noopener">iOS安全系列之一:HTTPS</a></p><p><a href="https://zhuanlan.zhihu.com/p/22749689" target="_blank" rel="noopener">iOS开发中的HTTPS</a></p><p><a href="https://www.jianshu.com/p/20d5fb4cd76d" target="_blank" rel="noopener">iOS 中 AFNetworking HTTPS 的使用</a></p>]]></content>
<categories>
<category> iOS </category>
</categories>
<tags>
<tag> 安全 </tag>
<tag> 加密 </tag>
</tags>
</entry>
</search>