看完就懂的HTTPS

译注:本文作者酷壳/陈皓

  我们先不了聊HTTP,HTTPS,我们先从一个聊天软件说起,我们要实现A能发一个hello消息给B:

  如果我们要实现这个聊天软件,本文只考虑安全性问题,要实现

A发给B的hello消息包,即使被中间人拦截到了,也无法得知消息的内容

如何做到真正的安全?

  这个问题,很多人马上就想到了各种加密算法,什么对称加密、非对称加密、DES、RSA、XX、噼里啪啦~
  而我想说,加密算法只是解决方案,我们首先要做的是理解我们的问题域——什么是安全?
  我个人的理解是:

A与B通信的内容,有且只有A和B有能力看到通信的真正内容

  好,问题域已经定义好了(现实中当然不止这一种定义)。对于解决方案,很容易就想到了对消息进行加密。
  题外话,但是只有这一种方法吗?我看未必,说不定在将来会出现一种物质打破当前世界的通信假设,实现真正意义上的保密。
  对于A与B这样的简单通信模型,我们很容易做出选择:

  这就是对称加密算法,其中图中的密钥S同时扮演加密和解密的角色。具体细节不是本文范畴。
  只要这个密钥S不公开给第三者,同时密钥S足够安全,我们就解决了我们一开始所定问题域了。因为世界上有且只有A与B知道如何加密和解密他们之间的消息。
  但是,在WWW环境下,我们的Web服务器的通信模型没有这么简单:

  如果服务器端对所有的客户端通信都使用同样的对称加密算法,无异于没有加密。那怎么办呢?即能使用对称加密算法,又不公开密钥?请读者思考21秒钟。

  答案是:Web服务器与每个客户端使用不同的对称加密算法:

如何确定对称加密算法

  慢着,另一个问题来了,我们的服务器端怎么告诉客户端该使用哪种对称加密算法?
  当然是通过协商。

  但是,你协商的过程是没有加密的,还是会被中间人拦截。那我们再对这个协商过程进行对称加密就好了,那你对协商过程加密的加密还是没有加密,怎么办?再加密不就好了……好吧,进行鸡生蛋蛋生鸡的问题了。

如何对协商过程进行加密

  新问题来了,如何对协商过程进行加密?密码学领域中,有一种称为“非对称加密”的加密算法,特点是私钥加密后的密文,只要是公钥,都可以解密,但是公钥加密后的密文,只有私钥可以解密。私钥只有一个人有,而公钥可以发给所有的人。

  虽然服务器端向A、B……的方向还是不安全的,但是至少A、B向服务器端方向是安全的。
  好了,如何协商加密算法的问题,我们解决了:使用非对称加密算法进行对称加密算法协商过程。
  这下,你明白为什么HTTPS同时需要对称加密算法和非对称加密算法了吧?

协商什么加密算法

  要达到Web服务器针对每个客户端使用不同的对称加密算法,同时,我们也不能让第三者知道这个对称加密算法是什么,怎么办?
  使用随机数,就是使用随机数来生成对称加密算法。这样就可以做到服务器和客户端每次交互都是新的加密算法、只有在交互的那一该才确定加密算法。
  这下,你明白为什么HTTPS协议握手阶段会有这么多的随机数了吧。

如何得到公钥?

  细心的人可能已经注意到了如果使用非对称加密算法,我们的客户端A,B需要一开始就持有公钥,要不没法开展加密行为啊。
  这下,我们又遇到新问题了,如何让A、B客户端安全地得到公钥?
  我能想到的方案只有这些:

  • 方案1. 服务器端将公钥发送给每一个客户端
  • 方案2. 服务器端将公钥放到一个远程服务器,客户端可以请求得到

  我们选择方案1,因为方案2又多了一次请求,还要另外处理公钥的放置问题。
  公钥被调包了怎么办?又是一个鸡生蛋蛋生鸡问题?
  但是方案1有个问题:如果服务器端发送公钥给客户端时,被中间人调包了,怎么办?

  我画了张图方便理解:

  显然,让每个客户端的每个浏览器默认保存所有网站的公钥是不现实的。

使用第三方机构的公钥解决鸡生蛋蛋生鸡问题

  公钥被调包的问题出现,是因为我们的客户端无法分辨返回公钥的人到底是中间人,还是真的服务器。这其实就是密码学中提的身份验证问题。
  如果让你来解决,你怎么解决?如果你了解过HTTPS,会知道使用数字证书来解决。但是你想过证书的本质是什么么?请放下你对HTTPS已有的知识,自己尝试找到解决方案。
  我是这样解决的。既然服务器需要将公钥传给客户端,这个过程本身是不安全,那么我们为什么不对这个过程本身再加密一次?可是,你是使用对称加密,还是非对称加密?这下好了,我感觉又进了鸡生蛋蛋生鸡问题了。
  问题的难点是如果我们选择直接将公钥传递给客户端的方案,我们始终无法解决公钥传递被中间人调包的问题。
  所以,我们不能直接将服务器的公钥传递给客户端,而是第三方机构使用它的私钥对我们的公钥进行加密后,再传给客户端。客户端再使用第三方机构的公钥进行解密。
  下图就是我们设计的第一版“数字证书”,证书中只有服务器交给第三方机构的公钥,而且这个公钥被第三方机构的私钥加密了:

  如果能解密,就说明这个公钥没有被中间人调包。因为如果中间人使用自己的私钥加密后的东西传给客户端,客户端是无法使用第三方的公钥进行解密的。

  话到此,我以为解决问题了。但是现实中HTTPS,还有一个数字签名的概念,我没法理解它的设计理由。
  原来,我漏掉了一个场景:第三方机构不可能只给你一家公司制作证书,它也可能会给中间人这样有坏心思的公司发放证书。这样的,中间人就有机会对你的证书进行调包,客户端在这种情况下是无法分辨出是接收的是你的证书,还是中间人的。因为不论中间人,还是你的证书,都能使用第三方机构的公钥进行解密。像下面这样:
  第三方机构向多家公司颁发证书的情况:

  客户端能解密同一家第三机构颁发的所有证书:

  最终导致其它持有同一家第三方机构证书的中间人可以进行调包:

数字签名,解决同一机构颁发的不同证书被篡改问题

  要解决这个问题,我们首先要想清楚一个问题,辨别同一机构下不同证书的这个职责,我们应该放在哪?
  只能放到客户端了。意思是,客户端在拿到证书后,自己就有能力分辨证书是否被篡改了。如何才能有这个能力呢?
  我们从现实中找灵感。比如你是HR,你手上拿到候选人的学历证书,证书上写了持证人,颁发机构,颁发时间等等,同时证书上,还写有一个最重要的:证书编号!我们怎么鉴别这张证书是的真伪呢?只要拿着这个证书编号上相关机构去查,如果证书上的持证人与现实的这个候选人一致,同时证书编号也能对应上,那么就说明这个证书是真实的。
  我们的客户端能不能采用这个机制呢?像这样:

  可是,这个“第三方机构”到底是在哪呢?是一个远端服务?不可能吧?如果是个远端服务,整个交互都会慢了。所以,这个第三方机构的验证功能只能放在客户端的本地了。

客户端本地怎么验证证书呢?

  客户端本地怎么验证证书呢?答案是证书本身就已经告诉客户端怎么验证证书的真伪。
  也就是证书上写着如何根据证书的内容生成证书编号。客户端拿到证书后根据证书上的方法自己生成一个证书编号,如果生成的证书编号与证书上的证书编号相同,那么说明这个证书是真实的。
  同时,为避免证书编号本身又被调包,所以使用第三方的私钥进行加密。
  这地方有些抽象,我们来个图帮助理解:
  证书的制作如图所示。证书中的“编号生成方法MD5”就是告诉客户端:你使用MD5对证书的内容求值就可以得到一个证书编号。

  当客户端拿到证书后,开始对证书中的内容进行验证,如果客户端计算出来的证书编号与证书中的证书编号相同,则验证通过:

  但是第三方机构的公钥怎么跑到了客户端的机器中呢?世界上这么多机器。
  其实呢,现实中,浏览器和操作系统都会维护一个权威的第三方机构列表(包括它们的公钥)。因为客户端接收到的证书中会写有颁发机构,客户端就根据这个颁发机构的值在本地找相应的公钥。

题外话:如果浏览器和操作系统这道防线被破了,就没办法。想想当年自己装过的非常规XP系统,都害怕。

  说到这里,想必大家已经知道上文所说的,证书就是HTTPS中数字证书,证书编号就是数字签名,而第三方机构就是指数字证书签发机构(CA)。

CA如何颁发数字证书给服务器端的?

  当我听到这个问题时,我误以为,我们的SERVER需要发网络请求到CA部门的服务器来拿这个证书。😭 到底是我理解能力问题,还是。。
  其实,问题应该是CA如何颁发给我们的网站管理员,而我们的管理员又如何将这个数字证书放到我们的服务器上。

  我们如何向CA申请呢?每个CA机构都大同小异,我在网上找了一个:

  拿到证书后,我们就可以将证书配置到自己的服务器上了。那么如何配置?这是具体细节了,留给大家google了。

也许我们需要整理一下思路

  我们通过推算的方式尝试还原HTTPS的设计过程。这样,我们也就明白了为什么HTTPS比HTTP多那么多次的交互,为什么HTTPS的性能会差,以及找到HTTPS的性能优化点。
  而上面一大堆工作都是为了让客户端与服务器端安全地协商出一个对称加密算法。这就是HTTPS中的SSL/TLS协议主要干的活。剩下的就是通信时双方使用这个对称加密算法进行加密解密。
  以下是一张HTTPS协议的真实交互图(从网上copy的,忘了从哪了,如果侵权麻烦告知):

能不能用一句话总结HTTPS?

  答案是不能,因为HTTPS本身实在太复杂。但是我还是尝试使用一段话来总结HTTPS:
  HTTPS要使客户端与服务器端的通信过程得到安全保证,必须使用的对称加密算法,但是协商对称加密算法的过程,需要使用非对称加密算法来保证安全,然而直接使用非对称加密的过程本身也不安全,会有中间人篡改公钥的可能性,所以客户端与服务器不直接使用公钥,而是使用数字证书签发机构颁发的证书来保证非对称加密过程本身的安全。这样通过这些机制协商出一个对称加密算法,就此双方使用该算法进行加密解密。从而解决了客户端与服务器端之间的通信安全问题。

如何成为一名卓越的前端工程师

译注:本文翻译自谷歌工程师 Philip Walton 的一篇博客。

  最近我收到一封读者来信让我陷入了思考,信是这么写的:
  Hi Philip,您是否介意我问,您是如何成为一名卓越 (great) 的前端工程师的?对此您有什么建议吗?
  不得不承认,被问这样的问题,我很惊讶,因为我从来不觉得自己是个很卓越的前端工程师。甚至我入行的头几年时并不认为自己可以做好这一行。我只确定自己比自己想象中还才疏学浅,而且大家面试我的时候都不知道从何问起。
  话虽这么说,我到现在做得还算不错,而且成为了团队中有价值的一员。但我最终离开 (去寻求新的挑战——即我还不能够胜任的工作) 的时候,我经常会被要求招聘我的继任者。现在回看这些面试,我不禁感叹当我刚开始的时候自己在这方面的知识是多么的匮乏。我现在或许不会按照我自己的模型进行招聘,即便我个人的这种经历也有可能成功。
  我在 web 领域工作越长时间,我就越意识到区分人才和顶尖人才的并不是他们的知识——而是他们思考问题的方式。很显然,知识在很多情况下是非常重要而且关键的——但是在一个快速发展的领域,你前进和获取知识的方式 (至少在相当长的一段时间里) 会比你已经掌握的知识显得更加重要。更重要的是:你是如何运用这些知识解决每天的问题的。
  这里有许许多多的文章谈论你工作中需要的语言、框架、工具等等。我希望给一些不一样的建议。在这篇文章里,我想谈一谈一个前端工程师的心态,希望可以帮助大家找到通往卓越的道路。

别光解决问题,想想究竟发生了什么

  很多人埋头写 CSS 和 JavaScript 直到程序工作起来了,然后就去做别的事情了。我通过 code review 发现这种事经常发生。我总会问大家:“为什么你会在这里添加 float: left?”或者“这里的 overflow: hidden 是必要的吗?”,他们往往答道:“我也不知道,可是我一删掉它们,页面就乱套了。”
  JavaScript 也是一样,我总会在一个条件竞争的地方看到一个 setTimeout,或者有些人无意中阻止了事件传播,却不知道它会影响到页面中其它的事件处理。我发现很多情况下,当你遇到问题的时候,你只是解决当下的问题罢了。但是如果你永远不花时间理解问题的本源,你将一次又一次的面对相同的问题。花一些时间找出为什么,这看上去费时费力,但是我保证它会节省你未来的时间。在完全理解整个系统之后,你就不需要总去猜测和论证了。

学会预见未来的浏览器发展趋势

  前后端开发的一个主要区别在于后端代码通常都运行在完全由你掌控的环境下。前端相对来说不那么在你的掌控之中。不同用户的平台或设备是前端永恒的话题,你的代码需要优雅掌控这一切。
  我记得自己 2011 年之前曾经阅读某主流 JavaScript 框架的时候看到过下面这样的代码 (简化过的):

1
var isIE6 = !isIE7 && !isIE8 && !isIE9;

  在这个例子中变量 IE6 为了判断 IE 浏览器版本是否是 6 或更低的版本。那么在 IE10 发布时,我们的程序判断还是会出问题。
  我理解在真实世界特性检测并不 100% 工作,而且有的时候你不得不依赖有 bug 的特性或根据浏览器特性检测的错误设计白名单。但你为此做的每一件事都非常关键,因为你预见到了不再有 bug 的未来。对于我们当中的很多人来说,我们今天写的代码都会比我们的工作周期要长。有些我写的代码已经过去 8 年多了还在产品线上运行。这让人很满足又很不安。

阅读规范文档

  浏览器有 bug 是很难免的事,但是当同一份代码在两个浏览器渲染出来的效果不一样,人们总会不假思索的推测,那个“广受好评”的浏览器是对的,而“不起眼”的浏览器是错的。但事实并不一定如此,当你的假设出现错误时,你选取的变通办法都会在未来遭遇问题。
   一个就近的例子是 flex 元素的默认最小尺寸问题。根据规范的描述,flex 元素初始化的 min-width 和 min-height 的值是 auto (而不是 0),也就是说它们默认应该收缩到自己内容的最小尺寸。但是在过去长达 8 个月的时间里,只有 Firefox 的实现是准确的。
   如果你遇到了这个浏览器兼容性的问题并且发现 Chrome、IE、Opera、Safari 的效果相同而 Firefox 和它们不同时,你很可能会认为是 Firefox 搞错了。事实上这种情况我见多了。很多我在自己 Flexbugs 项目上报的问题都是这样的。而且这些解决方案的问题会在两周之后 Chrome 44 修复之后被体现出来。和遵循标准的解决方案相比,这些方案都伤害到了正确的规范行为。当同一份代码在两个或更多浏览器的渲染结果不同时,你应该花些时间确定哪个效果是正确的,并且以此为标准写代码。你的解决方案应该是对未来友好的。额外的,所谓“卓越”的前端工程师是时刻感受变化,在某项技术成为主流之前就去适应它的,甚至在为这样的技术做着贡献。如果你锻炼自己看到规范就能在浏览器支持它之前想象出它如何工作的,那么你将成为谈论并影响其规范开发的那群人。

阅读别人的代码
  出于乐趣阅读别人的代码可能并不是你每周六晚上会想到的娱乐项目,但是这毫无疑问是你成为优秀工程师的最佳途径。自己独立解决问题绝对是个不错的方式,但是这不应该是你唯一的方式,因为它很快就会让你稳定在某个层次。阅读别人的代码会让你开阔思维,并且阅读和理解别人写的代码也是团队协作或开源贡献必须具备的能力。我着实认为很多公司在招聘新员工的时候犯的最大错误是他们只评估应聘者从轮廓开始写新代码的能力。我几乎没有见过一场面试会要求应聘者阅读现有的代码,找出其中的问题,并修复它们。缺少这样的面试流程真的非常不好,因为你作为工程师的很多时间都花费在了在现有的代码的基础上增加或改变上面,而不是搭建新的东西。

与比你聪明的人一起工作

  我印象中的很多前端开发者 (相比于全职工作来说) 都是自由职业者,有同类想法的后端开发者并没有那么多。可能是因为很多前端都是自学成才的而后端则多是学校里学出来的。不论是自我学习还是自我工作,我们都面对一个问题:你并没有机会从比你聪明的家伙那里学到什么。没有人帮你 review 代码,也没有人与你碰撞灵感。我强烈建议,最起码在你职业发展的前期,你要在一个团队里工作,尤其是一个普遍比你聪明而且有经验的团队里工作。如果你最终会在你职业发展的某个阶段选择独立工作,一定要让自己投身在开源社区当中。保持对开源项目的活跃贡献,这会给你团队工作相同甚至更多的益处。

“造轮子”
  造轮子在商业上是非常糟糕的,但是从学习的角度是非常好的。你可能很想把那些库和小工具直接从 npm 里拿下来用,但也可以想象一下你独立建造它们能够学到多少东西。我知道有些人读到这里是特别不赞成的。别误会,我并没有说你不应该使用第三方代码。那些经过充分测试的库具有多年的测试用例积累和已知问题积累,使用它们绝对是非常明智的选择。但在这里我想说的是如何从优秀到卓越。我觉得这个领域很多卓越的人都是我每天在用的非常流行的库的作者或维护者。
  你可能不曾打造过自己的 JavaScript 库也拥有一个成功的职业发展,但是你从不把自己手弄脏是几乎不可能淘到金子的。在这一行大家普遍会问的一个问题是:我接下来应该做点什么?如果你没有试着学一个新的工具创建一个新的应用,那不妨试着重新造一个你喜欢的 JavaScript 库或 CSS 框架。这样做的一个好消息是,在你遇到困难的时候,所有现成的库的源代码都会为你提供帮助。

把你学到的东西都记录下来

  最后,但丝毫不逊色的是,你应该把你学到的东西记录下来。这样做有很多原因,但也许最重要的原因是它强迫你更好的理解这件事。如果你无法讲清楚它的工作原理,在整个过程中它会推动你自己把并不真正理解的东西弄清楚。很多情况下你根本意识不到自己还不理解它们——直到自己动手写的时候。根据我的经验,写作、演讲、做 demo 是强迫自己完全深入理解一件事的最佳方式。就算你写的东西没有人看,整个过程也会让你受益匪浅。

滚动信息条的两种展示方法

  • 无间歇 直接滚动
  • 间歇式滚动

HTML

1
2
3
4
<div id="recoder_con">
<div id="recoder_ul1"></div>
<div id="recoder_ul2"></div>
</div>

CSS

1
2
3
4
5
6
7
8
.recoder-con{
display: inline-block;
width: 584px; //指定宽度
height: 280px; //指定高度
overflow: scroll;
overflow-x: hidden;
overflow-y: hidden;
}

无间歇JS

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
function  prizeScroll(listNum) {
var speed = 65,
wap = document.getElementById("recoder_con"),
ul1 = document.getElementById("recoder_ul1"),
ul2 = document.getElementById("recoder_ul2");

ul2.innerHTML = ul1.innerHTML;
var leftHeight;

if (listNum > 4) {
leftHeight = 0;
} else if (listNum == 4) {
leftHeight = 33;
}

function Marquee2() {
if (ul2.offsetTop - wap.scrollTop <= leftHeight) {
wap.scrollTop -= ul1.offsetHeight;
} else {
wap.scrollTop++;
}
}
var MyMar2 = setInterval(function () {
Marquee2();
}, speed);

}
prizeScroll(4)

有间歇JS

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
var wap = $('#recoder_con'),
ul1Html = $('#recoder_ul1').html(),
ul2 = $('#recoder_ul2');

var speed = 50,
listNum = 4,
delay = 1000,
liHeight = 21,
time,
scrollTop = 0;

ul2.html(ul1Html);

function startScroll() {
time = setInterval("scrollUp()", speed);
wap.scrollTop(scrollTop++);
}
function scrollUp(){
if(scrollTop % liHeight == 0){
clearInterval(time);
setTimeout(startScroll,delay);
}else{
wap.scrollTop(scrollTop++);
if(scrollTop >= (listNum -1) * liHeight){
scrollTop = 0;
wap.scrollTop(scrollTop);
}
}
}
setTimeout('startScroll()',delay);

解决IOS滑动不畅问题

[TOC]
@(welcome)[前端菜鸟|掘金中]

  • 在编写移动端页面时,遇到一个很奇怪的问题,IOS向来滑动通畅,体验度明显甩开安卓十条街,但是在添加了上拉刷新下拉加载加载的插件后,IOS瞬间滑动不畅,慢成一坨.
  • 那赶脚就像,拖着汽车往前走的老哞…
  • 起初以为是插件底层的滑动事件影响了IOS,去掉插件之后,依然卡顿如初,what?!
  • 仔细对比之后,终于找到罪魁祸首,竟然是两行熟悉到不行的css
1
2
3
4
body,html{
height: 100%;
overflow: auto;
}

通过查找,终于找到了解决问题的方法

1
-webkit-overflow-scrolling: touch;

这个属性只兼容移动版 Safari iOS 5.0+,而且它有两个值

1
2
-webkit-overflow-scrolling: touch; /* 当手指从触摸屏上移开,会保持一段时间的滚动 */
-webkit-overflow-scrolling: auto; /* 当手指从触摸屏上移开,滚动会立即停止 */

####auto

  • 使用普通滚动, 当手指从触摸屏上移开,滚动会立即停止。

####touch

  • 使用具有回弹效果的滚动, 当手指从触摸屏上移开,内容会继续保持一段时间的滚动效果。继续滚动的速度和持续的时间和滚动手势的强烈程度成正比。同时也会创建一个新的堆栈上下文。

``将这行代码放到我的根标签后,我的小苹果又再次欢快的跑起来了!正当我高兴的时候,又发现了一个问题,我写的固定定位导航条随着苹果的告诉滑动,出现了鬼畜情况!

``也就是说,我需要将我的固定定位排除在滑动之外!

``上代码

1
2
3
4
5
6
7
8
9
body,html{
height: 100%;
overflow: auto;
}
.scroll{
height: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
}

要滚动的部分给父元素加上class类scroll,需要注意的是设置的两个overflow形成了两个滚动条,很容易造成滚动问题,建议在苹果浏览器下调试好。

博主原创文