理解 HTTPS

简介

HTTPS 中的 S 代表 Secure,所以 HTTPS 自然就是更安全的 HTTP 的意思。互联网诞生之初 SSL(Secure Sockets Layer 安全套接层)是由 Netscape 这家最早的浏览器公司设计的,主要是用于 Web 的安全传输的协议,这种协议在早期 Web 上获得了广泛的应用。后来被 IETF 标准化形成了 TLS(Transport Layer Security 传输层安全)标准,其历史如下:

  • 1994: SSL1.0,因为存在严重的安全漏洞,未发布。
  • 1995: SSL2.0,这个版本由于设计缺陷,很快被发现有严重漏洞,被废弃。
  • 1996: SSL3.0,重新设计并开始流行,SSL 前三个版本都是由 Netscape 设计实现,有已知漏洞,建议弃用。
  • 1999: TLS1.0,IETF 将 SSL 标准化,即 RFC 2246,也存在部分安全漏洞,比如 RC4 和 BEAST 攻击。
  • 2006: TLS1.1,作为 RFC 4346 发布。
  • 2008: TLS1.2,作为 RFC 5246 发布 。
  • 2015: TLS1.3,尚在制定中,处于草案阶段。

如上,现在互联网世界使用最广泛的应该是 TLS1.2 标准。

##目标

SSL/TLS 最初的设计目标就是为了实现下面三个目的:

  1. 保密:第三方无法窃听。
  2. 完整:无法篡改。
  3. 认证:防止身份冒充。

结构

整个互联网构建于 TCP/IP 协议栈基础之上,SSL/TLS 协议处于该分层协议栈结构中的会话层位置:

流程和步骤

1. 客户端发起 HTTPS 请求

客户端向服务端传送:

  • SSL Version : 自己支持的最高协议版本,比如 TLS 1.2
  • Ciphers : 支持的加密套件,比如: RSA 非对称加密算法,AES 对称加密算法
  • 随机数 (A) RandomC

2. 服务端的配置

服务器的数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面。

这套证书其实就是一对公钥和私钥。可以想象成一把钥匙和一个锁头,全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。

关于数字证书可以查看另一篇:数字签名是什么?

3. 传送证书

服务端向客户端传送证书,包含以下信息:

  • 服务器公钥(明文)
  • 申请者的组织信息和个人信息
  • 签发机构 CA 的信息
  • 有效时间、证书序列号等信息(明文)
  • 发行者的数字签名:使用散列函数计算公开的明文信息的摘要,然后用 CA 的私钥对信息摘要进行加密,密文即签名

同时会一并做传送以下信息:

  • 确认使用的 SSL 协议的版本号
  • 加密套件 Cipher Suite
  • 随机数 (B) RandomS
  • CertificateRequest 可选,Server 端需要认证 Client 端身份的请求时发送。 比如,银行提供的各类 U 盾,其实就是一种 Client 证书,一般在线使用专业版网银时才需要。
  • ServerHelloDone 表示 Server 响应结束。

4. 客户端验证证书

客户端的 TLS 来验证证书的合法性:

  • 证书是否过期
  • 签发机构 CA 是否可靠
  • 签发机构 CA 的公钥来验证服务器证书的发行者数字签名
  • 证书上的域名和服务器的实际域名是否一致

如果证书没有问题,那么就生成一个随机数 (C)。然后把三个随机数 A+B+C 拼接成一个新的随机数 (D),用服务端公钥(证书里解析得来)对随机数 (D) 进行加密,形成行动暗号

如果发现异常,浏览器则会弹出一个警告框,提示证书存在问题。如果这时用户仍然选择『继续』,就表示不验证证书合法性,直接把证书里的 服务端公钥拿来用就是了。

5. 传送暗号

客户端把行动暗号发送给服务端,以后两边的通信就通过这个暗号来进行对称加、解密了。

同时向服务端传送以下信息:

  • Certificate 如果在ServerHello 时服务端要求客户端 U 盾验证,那么 Client 端也会在这一步响应 Server 的 CertificateRequest 请求。
  • CertificateVerify 用于对客户端 U 盾证书提供证明,对于特定的证书需要可选发送。
  • ChangeCipherSpec 告知 Server,Client 已经切换到之前协商好的加密套件(Cipher Suite)的状态,准备加密数据并传输了。
  • ClientFinished Client 会用协商好的算法先加密一小段 Finished 的数据传送给 Server,为了在正式传输前做最后验证。

6. 服务端获取暗号

服务端用自己的私钥解密得到了客户端传过来的行动暗号。同时服务端回应以下信息:

  • NewSessionTicket 表示新建了一个会话票据,传递给 Client。若连接意外中断 Client 需要重建会话时,可以复用该票据,加速握手过程。
  • ChangeCipherSpec 告知 Client,Server 已经切换到协商过的加密套件状态,准备加密数据并传输了。
  • ServerFinished Server 会用协商好的算法加密一小段 Finished 的数据传送给 Client,为了在正式传输前做最后验证。

至此,整个 SSL 握手阶段全部结束。接下来 Client 和 Server 进入使用会话密钥的加密通信过程。

7. 服务端返回加密信息

服务端用行动暗号把数据加密后发回给客户端。

8. 客户端解密信息

客户端用行动暗号解密服务端发过来的信息。

完成!

FAQ

Q: 应用层数据为什么不用非对称加解密?性能开销太大,无法承受。

A: CPU 计算资源消耗非常大。一次完全 TLS 握手,密钥交换时的非对称解密计算量占整个握手过程的 90% 以上。而对称加密的计算量只相当于非对称加密的 0.1%。 非对称加密算法对加密内容的长度有限制,不能超过公钥长度。比如现在常用的公钥长度是 2048 位,意味着待加密内容不能超过 256 个字节。

所以公钥加密目前只能用来作密钥交换或者内容签名,不适合用来做应用层传输内容的加解密。

Q: 交互过程中到底有几个随机数?

A: 一共有三个随机数。

  • 第1个 RandomC 由 clientHello 时生成发送给服务端
  • 第2个 RandomS 由 serverHello 时生成发送给客户端(随着 CA 证书一起发回)
  • 第3个 PreMaster 由客户端生成

根据三个随机数,计算得到行动暗号

PreMasterSecret = crypt(RandomC, RandomS, PreMaster)

至于为什么要用三个随机数,是为了增加更多随机因素,防止被猜出。

参考文章:

Nginx 配置 SSL 证书支持 Https

确保机器上安装了 openssl 和 openssl-devel

yum install openssl
yum install openssl-devel

注意:如果 Nginx 以后要开启 HTTP2,那么 OpenSSL 库必须是 1.0.2 以上,CentOS 通过 yum 安装的版本太旧,只有 1.0.1e。我们需要去官网下载最新版,并且在重新编译升级 Nginx 时指定 OpenSSL 的源码目录(--with-openssl='path/to/openssl')。如果不带这个参数,Nginx 只会去读系统自带的 OpenSSL 库。

所以也可以用更彻底的方法:升级系统全局 OpenSSL 版本,那么 Nginx 时就不用指定 OpenSSL 源码目录了。

确保 Nginx 支持 SSL 模块,编译时带 --with-http_ssl_module 参数(可通过 nginx -V 查看 configure 时的参数),否则会报错

[emerg] 10464#0: unknown directive "ssl" in /usr/local/nginx/conf/nginx.conf:74”

创建服务端私钥(第三方 SSL 证书签发机构都要求起码 2048 位的 RSA 加密的私钥)

cd /usr/local/nginx/
mkdir ssl && cd ssl
openssl genrsa -des3 -out hicrew.key 2048

创建证书请求文件(CSR = SSL Certificate Signing Request)

openssl req -new -nodes -sha256 -key hicrew.key -out hicrew.csr

依次输入密码、国家代码、省份城市、邮箱等信息即可。

Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:Shanghai
Locality Name (eg, city) [Default City]:Shanghai
Organization Name (eg, company) [Default Company Ltd]:Morecruit
Organizational Unit Name (eg, section) []:RD
Common Name (eg, your name or your server's hostname) []:api.hicrew.cn
Email Address []:support@morecruit.cn

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:hicrew
An optional company name []:qa.api.hicrew.cn

特别注意

Common Name 和 An optional company name 是证书的生效域名。 如果只是同一个站点下面分出很多个子域名,那么可以直接申请通配证书,将证书的 Common Name 填写为 *.hicrew.cn,但 这种写法只能匹配二级域名,根域名 hicrew.cn 和三级域名 qa.api.hicrew.cn 都无法匹配,所以如果有更多的域名,可以填入“使用者可选名称”。参考《SSL多域名绑定证书的解决方案》

去除私钥里的密码信息(否则以SSL启动Nginx时会提示必须输入密钥)

openssl rsa -in hicrew.key -out hicrew_nopwd.key

使用刚生成的私钥和CSR进行证书签名(10年有效期)

或者到 StartSSL 上传 CSR 后获得经 CA 机构签名后的证书,如:1_study.hicrew.cn_bundle.crt

openssl x509 -req -days 3650 -sha256 -in hicrew.csr -signkey hicrew_nopwd.key -out hicrew.crt

如果需要用 pfx 可以用以下命令生成

openssl pkcs12 -export -inkey hicrew.key -in hicrew.crt -out hicrew.pfx

修改 Nginx 配置文件,让其包含新标记的证书和私钥:

server
{
    listen 443;
    server_name api.hicrew.cn;

    ssl on;
    ssl_certificate /usr/local/nginx/ssl/hicrew.crt;
    ssl_certificate_key /usr/local/nginx/ssl/hicrew_nopwd.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
    ssl_prefer_server_ciphers on;

    # HSTS (HTTP Strict Transport Security, ngx_http_headers_module is required)
    # 让浏览器访问 HTTP 时强制 307 内部跳转到 HTTPS
    add_header Strict-Transport-Security max-age=15768000;
}

全部 CipherSuite 可以在 IANA 的 TLS Cipher Suite Registry 页面查看。

协商算法配置务必参考权威 Mozilla 的推荐配置 或者 CloudFlare 使用的配置

另外还可以实现访问 80 端口直接重定向到 443(其实 HSTS 307 Internal Redirect 已经可以实现这个效果)但为了双保险,再加一个 vhost 做 302 重定向:

server
{
    listen 80;
    server_name api.hicrew.cn;
    rewrite ^(.*) https://$server_name$1 permanent;
}

重启 nginx 就可以通过以下方式访问了

https://api.hicrew.cn

Chrome 浏览器可以安装 HTTP/2 and SPDY indicator 插件看到『蓝色闪电』表示本站启用了 HTTP2。

或者在线测试:https://tools.keycdn.com/http2-test

FAQ

问:如何让浏览器信任自己办法的证书,以IE为例:

答:

IE:控制面板 -> Internet选项 -> 内容 -> 发行者 -> `受信任的根证书颁发机构` -> 导入 -> 选择 hicrew.crt
Chrome:设置 -> 显示高级设置 -> HTTPS/SSL 管理证书 -> `受信任的根证书颁发机构` -> 导入 -> 选择 hicrew.crt

问:什么是针对企业的 EV SSL

答:EV SSL,是 Extended Validation 的简称,更注重于对企业网站的安全保护以及严格的认证。最明显的区别就是,通常 EV SSL 显示都是绿色的条

参考文章:

数字签名是什么?(HTTPS 原理)

转帖自:阮一峰:http://www.ruanyifeng.com/blog/2011/08/what_is_a_digital_signature.html

原文链接:David Youd:http://www.youdzone.com/signature.html

今天,我读到一篇好文章。它用图片通俗易懂地解释了,”数字签名”(digital signature)和”数字证书”(digital certificate)到底是什么。

我对这些问题的理解,一直是模模糊糊的,很多细节搞不清楚。读完这篇文章后,发现思路一下子就理清了。为了加深记忆,我把文字和图片都翻译出来了。

==============================

1.鲍勃有两把钥匙,一把是公钥,另一把是私钥。

2.鲍勃把公钥送给他的朋友们—-帕蒂、道格、苏珊—-每人一把。

3.苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密,就可以达到保密的效果。

4.鲍勃收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。

5.鲍勃给苏珊回信,决定采用”数字签名”。他写完后先用Hash函数,生成信件的摘要(digest)。

6.然后,鲍勃使用私钥,对这个摘要加密,生成”数字签名”(signature)。

7.鲍勃将这个签名,附在信件下面,一起发给苏珊。

8.苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。

9.苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。

10.复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成”数字签名”,写信给苏珊,让苏珊用假的鲍勃公钥进行解密。

11.后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找”证书中心”(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成”数字证书”(Digital Certificate)。

12.鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。

13.苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明”数字签名”是否真的是鲍勃签的。

14.下面,我们看一个应用”数字证书”的实例:HTTPS 协议。这个协议主要用于网页加密。

15.首先,客户端向服务器发出加密请求。

16.服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。证书包含以下信息:

  • 服务器公钥
  • 申请者的组织信息和个人信息
  • 签发机构 CA 的信息
  • 有效时间、证书序列号等信息的明文
  • 发行者的数字签名:使用散列函数计算公开的明文信息的信息摘要,然后采用 CA 的私钥对信息摘要进行加密,密文即签名

17.客户端(浏览器)的”证书管理器”,有”受信任的根证书颁发机构”列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。同时验证证书合法性:

  • 证书是否过期
  • 签发机构 CA 是否可靠
  • 签发机构 CA 的公钥能否正确解开服务器证书的“发行者的数字签名”
  • 证书上的域名和服务器的实际域名是否一致

18.如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。

19.如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告。

20.如果数字证书是可靠的,客户端就可以使用证书中的服务器公钥,对信息进行加密,然后与服务器交换加密信息。

Javascript 模块化开发

转贴自:https://www.markdream.com/technologies/programs/understanding-of-javascript-module-development.shtml

小A是某个创业团队的前端工程师,负责编写项目的Javascript程序。

全局变量冲突

根据自己的经验,小A先把一些常用的功能抽出来,写成函数放到一个公用文件base.js中:

var _ = {
    $: function(id) { return document.getElementById(id); },
    getCookie: function(key) { ... },
    setCookie: function(key, value) { ... }
};

小A把这些函数都放在_对象内,以防过多的全局变量造成冲突。他告诉团队的其他成员,如果谁想使用这些函数,只要引入base.js就可以了。

小C是小A的同事,他向小A反映:自己的页面引入了一个叫做underscore.js的类库,而且,这个类库也会占用_这个全局变量,这样一来就会跟base.js中的_冲突了。小A心想,underscore.js是第三方类库,估计不好改,但是base.js已经在很多页面铺开,不可能改。最后小A只好无奈地把underscore.js占用的全局变量改了。

此时,小A发现,把函数都放在一个名字空间内,可以减少全局变量冲突的概率,却没有解决全局变量冲突这个问题。

依赖

随着业务的发展,小A又编写了一系列的函数库和UI组件,比方说标签切换组件tabs.js,此组件需调用base.js以及util.js中的函数。

有一天,新同事小D跟小A反映,自己已经在页面中引用了tabs.js,功能却不正常。小A一看就发现问题了,原来小D不知道tabs.js依赖于base.js以及util.js,他并没有添加这两个文件的引用。于是,他马上进行修改:

<script src="tabs.js"></script>
<script src="base.js"></script>
<script src="util.js"></script>

然而,功能还是不正常,此时小A教训小D说:“都说是依赖,那被依赖方肯定要放在依赖方之前啊”。原来小D把base.js和util.js放到tabs.js之后了。

小A心想,他是作者,自然知道组件的依赖情况,但是别人就难说了,特别是新人。

过了一段时间,小A给标签切换组件增加了功能,为了实现这个功能,tabs.js还需要调用ui.js中的函数。这时,小A发现了一个严重的问题,他需要在所有调用了tabs.js的页面上增加ui.js的引用!!!

又过了一段时间,小A优化了tabs.js,这个组件已经不再依赖于util.js,所以他在所有用到tabs.js的页面中移除了util.js的引用,以提高性能。他这一修改,出大事了,测试组MM告诉他,有些页面不正常了。小A一看,恍然大悟,原来某些页面的其他功能用到了util.js中的函数,他把这个文件的引用去掉导致出错了。为了保证功能正常,他又把代码恢复了。

小A又想,有没有办法在修改依赖的同时不用逐一修改页面,也不影响其他功能呢?

模块化

小A逛互联网的时候,无意中发现了一种新奇的模块化编码方式,可以把它之前遇到的问题全部解决。

在模块化编程方式下,每个文件都是一个模块。每个模块都由一个名为define的函数创建。例如,把base.js改造成一个模块后,代码会变成这样:

define(function(require, exports, module) {
    exports.$ = function(id) { return document.getElementById(id); };
    exports.getCookie = function(key) { ... };
    exports.setCookie = function(key, value) { ... };
});

base.js向外提供的接口都被添加到exports这个对象。而exports是一个局部变量,整个模块的代码都没有占用半个全局变量。

那如何调用某个模块提供的接口呢?以tabs.js为例,它要依赖于base.js和util.js:

define(function(require, exports, module) {
    var _ = require('base.js'), util = require('util.js');
    var div_tabs = _.$('tabs');
    // .... 其他代码
});

一个模块可以通过局部函数require获取其他模块的接口。此时,变量_和util都是局部变量,并且,变量名完全是受开发者控制的,如果你不喜欢_,那也可以用base:

define(function(require, exports, module) {
    var base = require('base.js'), util = require('util.js');
    var div_tabs = base.$('tabs');
    // .... 其他代码
});

一旦要移除util.js、添加ui.js,那只要修改tabs.js就可以了:

define(function(require, exports, module) {
    var base = require('base.js'), ui = require('ui.js');
    var div_tabs = base.$('tabs');
    // .... 其他代码
});

加载器

由于缺乏浏览器的原生支持,如果我们要用模块化的方式编码,就必须借助于一个叫做加载器(loader)的东西。

目前加载器的实现有很多,比如 RequireJs、SeaJs、LABJs

参考文章:

从七牛同步备份资源到阿里云OSS

先下载并解压

wget http://oss.aliyuncs.com/import-service-package/ossimport4linux.zip
unzip ./ossimport4linux.zip -d /root/aliyun-oss

修改配置文件

vim /root/aliyun-oss/conf/sys.properties
workingDir=/root/aliyun-oss
slaveUserName=
slavePassword=
privateKeyFile=
slaveTaskThreadNum=60
slaveMaxThroughput(KB/s)=100000000
slaveAbortWhenUncatchedException=false
dispatcherThreadNum=5

新增任务配置文件(按69发布机上的样例填写)

vi /root/aliyun-oss/stay-user.cfg
vi /root/aliyun-oss/stay-event.cfg

开始守护进程

nohup java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties start > /root/aliyun-oss/logs/ossimport2.log 2>&1 &

提交新的任务

java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties submit /root/aliyun-oss/stay-event.cfg
java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties submit /root/aliyun-oss/stay-user.cfg

重置任务进度

java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties clean stay-event
java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties clean stay-user

查看任务执行状态

java -jar /root/aliyun-oss/bin/ossimport2.jar -c /root/aliyun-oss/conf/sys.properties stat detail

如需要开启OSS的图片处理服务,需要开通CDN域名,否则浏览器打开会直接下载。

官方文档:

MySQL 中 trim 处理字段多余字符

删除两侧空格

SELECT trim(`path`) as paths FROM `ts_back_pic`

删除左侧斜杠(头部)

SELECT trim(LEADING '/' FROM `path`) as paths FROM `ts_back_pic`

删除右侧斜杠(尾部)

SELECT trim(TRAILING '/' FROM `path`) as paths FROM `ts_back_pic`

删除两侧斜杠

SELECT trim(BOTH '/' FROM `path`) as paths FROM `ts_back_pic`

删除两侧空格+回车

SELECT trim(BOTH '\r\n' FROM trim(`path`)) as paths FROM `ts_back_pic`