nginx ssl设置详解

SSL 协议

SSLv2是不安全的,绝对不能用,SSLv3 能不用则不用,正常情况下用不到的,推荐使用 TLSv1 TLSv1.1 TLSv1.2,附配置代码:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

加密算法

RC4不要用,再就是 OpenSSL 记得更新到最新版,可以避免很多麻烦。别的也没什么了,附代码:

ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";

ssl_prefer_server_ciphers on;

ECDHE+AESGCM 加密是首选的。它们是 TLS 1.2 加密算法,现在还没有广泛支持。当然也没有破解的方案。
PFS 加密套件好一些,首选 ECDHE,然后是 DHE
AES 128 要好于 AES 256AES 256 会造成更大的性能消耗,但是带来的安全提升是有限的。反之还能承受更大压力。
在向后兼容的加密套件里面,AES 要优于 3DES。在 TLS 1.1及其以上,减轻了针对 AES 的野兽攻击(BEAST)的威胁,而在 TLS 1.0上则难以实现该攻击。在非向后兼容的加密套件里面,不支持 3DES
RC4 整个不支持了。3DES 用来向后兼容。
强制丢弃的算法
aNULL 包含了非验证的 Diffie-Hellman 密钥交换,这会受到中间人(MITM)攻击
eNULL 包含了无加密的算法(明文)
EXPORT 是老旧的弱加密算法,是被美国法律标示为可出口的
RC4 包含使用了已弃用的 ARCFOUR 的加密算法
DES 包含使用了弃用的数据加密标准(DES)的加密算法
SSLv2 包含了定义在旧版本 SSL 标准中的所有算法,现已弃用
MD5 包含了使用已弃用的 MD5 的所有算法
但是 ECC 证书的部署不大一样,加密套件和 RSA 不一样,用错之后会影响向前安全性(Forward secrecy)。
加密套件应该这么改:

ssl_ciphers "EECDH+CHACHA20 EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";

而根据https://cipherli.st/的推荐,可以进一步改为:

ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";

前向安全性(Forward Secrecy)

首先 Nginx 配置上如下代码:

ssl_dhparam /your/path/to/dhparam.pem;

pem 文件是这样生成的:

openssl dhparam -out dhparam.pem 4096

然后会显示:

This is going to take a long time  
.......+...........................+............................+............................+............++*++*++*

这真的会”take a long time”.但根据每台机器的性能不同而花费的时间不同.

dhparam 算法是在 2^4096 个数字中找出两个质数,所以需要的时间挺长。….. 意思是有可能的质数,+ 是正在测试的质数,* 是已经找到的质数。

前向安全性(Forward Secrecy)的概念很简单:客户端和服务器协商一个永不重用的密钥,并在会话结束时销毁它。服务器上的 RSA 私钥用于客户端和服务器之间的 Diffie-Hellman 密钥交换签名。从 Diffie-Hellman 握手中获取的预主密钥会用于之后的编码。因为预主密钥是特定于客户端和服务器之间建立的某个连接,并且只用在一个限定的时间内,所以称作短暂模式(Ephemeral)。
如果使用前向安全机制,攻击者取得了一个服务器的私钥,他是不能解码之前的通讯信息的。这个私钥仅用于 Diffie Hellman 握手签名,并不会泄露预主密钥。Diffie Hellman 算法会确保预主密钥绝不会离开客户端和服务器,而且不能被中间人攻击所拦截。
所有版本的 nginx 都依赖于 OpenSSL 给 Diffie-Hellman 的输入参数。如果不特别声明,将使用 OpenSSL 的默认设置,1024 位密钥。这绝壁是不安全的,因为我们正在使用 2048 位证书,所以要有一个更强大的 DH

HTTP 严格传输安全(HSTS)(HTTP Strict Transport Security)

HTTP严格传输安全(英语:HTTP Strict Transport Security,缩写:HSTS)是一套由互联网工程任务组发布的互联网安全策略机制。网站可以选择使用HSTS策略,来让浏览器强制使用HTTPS与网站进行通信,以减少会话劫持风险。
HSTS的作用是强制客户端(如浏览器)使用HTTPS与服务器创建连接。服务器开启HSTS的方法是,当客户端通过HTTPS发出请求时,在服务器返回的超文本传输协议响应头中包含Strict-Transport-Security字段。非加密传输时设置的HSTS字段无效。

add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";

includeSubDomains是可选的,用来指定是否作用于子域名。支持HSTS的浏览器遇到这个响应头,会把当前网站加入HSTS列表,然后在max-age指定的秒数内,当前网站所有请求都会被重定向为https。即使用户主动输入http://或者不输入协议部分,都将重定向到https://地址。

X-Frame-Options 响应头

X-Frame-Options HTTP 响应头是用来给浏览器指示允许一个页面可否在 <frame>, <iframe> 或者 <object> 中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。
X-Frame-Options 有三个值:
DENY:表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。
SAMEORIGIN:表示该页面可以在相同域名页面的 frame 中展示。
ALLOW-FROM uri:表示该页面可以在指定来源的 frame 中展示。
换一句话说,如果设置为 DENY,不光在别人的网站 frame 嵌入时会无法加载,在同域名页面中同样会无法加载。另一方面,如果设置为 SAMEORIGIN,那么页面就可以在同域名页面的 frame 中嵌套。

X-Content-Type-Options 响应头

互联网上的资源有各种类型,通常浏览器会根据响应头的Content-Type字段来分辨它们的类型。例如:”text/html”代表html文档,”image/png”是PNG图片,”text/css”是CSS样式文档。然而,有些资源的Content-Type是错的或者未定义。这时,某些浏览器会启用MIME-sniffing来猜测该资源的类型,解析内容并执行。
例如,我们即使给一个html文档指定Content-Type为”text/plain”,在IE8-中这个文档依然会被当做html来解析。利用浏览器的这个特性,攻击者甚至可以让原本应该解析为图片的请求被解析为JavaScript。通过下面这个响应头可以禁用浏览器的类型猜测行为:

add_header X-Content-Type-Options nosniff;

这个响应头的值只能是nosniff,可用于IE8+和Chrome。

配置 OCSP 装订

ssl_stapling on;  
ssl_stapling_verify on;  
ssl_trusted_certificate /you/path/to/domain.chain.pem;  
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 10s;

domain.chain.pem 里面是域名证书到 ROOT 证书的一个链。
连接到一个服务器时,客户端应该使用证书吊销列表(CRL)或在线证书状态协议(OCSP)记录来校验服务器证书的有效性。CRL 存在一个问题,它已经增长的太快,永远也下载不完。
OCSP 更轻量一些,只需发一个请求。但是副作用是访问一个站点时必须对第三方 OCSP 响应服务器发起 OCSP 请求,这就增加了延迟带来的潜在隐患。事实上,CA 所运营的 OCSP 响应服务器非常不可靠,浏览器如果不能及时收到答复,就会静默失败。攻击者通过 DDoS 攻击一个 OCSP 响应服务器可以禁用其校验功能,这样就降低了安全性。
解决方法是允许服务器在 TLS 握手中发送缓存的 OCSP 记录,以绕开 OCSP 响应服务器。这个机制减少了客户端和 OCSP 响应服务器之间的通讯,称作 OCSP 装订。
客户端会在它的 CLIENT HELLO 中告知其支持 status_request TLS 扩展,服务器仅在客户端请求它的时候才发送缓存的 OCSP 响应。
大多数服务器最长会缓存 OCSP 48 小时。服务器会按照常规的间隔连接到 CA 的 OCSP 响应服务器来获取刷新的 OCSP 记录。OCSP 响应服务器的位置可以从签名的证书中 Authority Information Access 字段中获得。

nginx里边SSL的完整配置

    ssl on;
    #文件位置
    ssl_certificate /path/to/pem;
    ssl_certificate_key /path/to/key;
    ssl_dhparam /path/to/dhparams.pem;
    #会话进程设置
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_session_timeout 5m;
    #加密套件设置
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_prefer_server_ciphers on;
    ssl_ecdh_curve secp384r1;
    #OCSP配置
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 216.69.185.17 208.109.255.17 valid=300s;
    resolver_timeout 5s;
    #HTTP安全配置
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;

文章拼凑来源:
https://cipherli.st/
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/X-Frame-Options
https://okwoo.com/open-the-hsts-http-strict-transport-security-the-importance-of
https://www.pupboss.com/nginx-add-ssl/
https://imququ.com/post/web-security-and-response-header.html

用acme-tiny自动更新Let’s Encrypt HTTPS证书

Let’s Encrypt 证书不但免费,还非常简单,虽然每次只有 90 天的有效期,但可以通过脚本定期更新,配好之后一劳永逸。

ACME 全称是 Automated Certificate Management Environment,直译过来是自动化证书管理环境的意思,Let’s Encrypt 的证书签发过程使用的就是 ACME 协议。

acme-tiny是用python写的自动申请/更新Let’s Encrypt HTTPS的工具。

以下是记录Let’s Encrypt HTTPS证书申请部署的全部过程。

1.生成RSA私钥:

	    (umask 077;openssl genrsa 2048 > private.key)
	    (umask 077;openssl genrsa 4096 > private.key)

2.生成CSR请求文件:

        1).RSA 私钥(兼容性好)
	(umask 077; openssl genrsa 2048 > yoursite.key)
	(umask 077; openssl genrsa 4096 > yoursite.key)
        2).ECC 私钥(部分老旧操作系统、浏览器不支持。优点是证书体积小)
	(umask 077;openssl ecparam -genkey -name secp256r1 | openssl ec -out yoursite_ec.key)
	(umask 077;openssl ecparam -genkey -name secp384r1 | openssl ec -out yoursite_ec.key)

然后就可以生成 CSR 文件了。在 CSR 中推荐至少把域名带 www 和不带 www 的两种情况都加进去,其它子域可以根据需要添加,部分系统的openssl.cnf文件所有位置可能有所不同:

	openssl req -new -sha256 -key yoursite.key -subj "/" -reqexts SAN -config <(cat /etc/pki/tls/openssl.cnf <(printf "[SAN]\nsubjectAltName=DNS:yoursite.com,DNS:www.yoursite.com")) > yoursite.csr

3.配置自动认证目录:

	mkdir -pv /your/wwwroot/challenges

然后配置一个 HTTP 服务,以 Nginx 为例:

	server {
	    server_name yoursite.com www.yoursite.com;
	    location ^~ /.well-known/acme-challenge/ {
	        alias /your/wwwroot/challenges/;
	        try_files $uri =404;
	    }
	    location / {
	        rewrite ^/(.*)$ https://yoursite.com/$1 permanent;
	    }
	}

以上配置优先查找challenges目录下的文件,如果找不到就重定向到 HTTPS 地址。这个验证服务以后更新证书还要用到,建议一直保留。

4.脚本自动获取证书:
1).脚本下载与证书获取:

		wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py
		python acme_tiny.py --account-key ./private.key --csr ./yoursite.csr --acme-dir /your/wwwroot/challenges/ > ./signed.crt

2).Let’s Encrypt的”中间证书”与”网站证书”合在一起:

		wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
		cat signed.crt intermediate.pem > chained.pem

3).Let’s Encrypt的”根证书”和”中间证书”合在一起:

		wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
		cat intermediate.pem root.pem > full_chained.pem

5.网站nginx使用的证书

	ssl_certificate     /path/to/chained.pem;
	ssl_certificate_key /path/to/yoursite.key;

6.自动更新证书脚本
Let’s Encrypt 签发的证书只有 90 天有效期,推荐使用脚本定期更新。脚本范例如下:

cd /path/to/nginx/ssl
python acme_tiny.py --account-key ./private.key --csr ./yyfilmtechcom.csr --acme-dir /your/wwwroot/challenges/ > ./signed.crt || exit
wget -O - https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem > intermediate.pem
cat signed.crt intermediate.pem > chained.pem
wget -O - https://letsencrypt.org/certs/isrgrootx1.pem > root.pem
cat intermediate.pem root.pem > full_chained.pem
service nginx reload

把脚本放进crontab,每个月定时执行一次:

0 0 1 * * sh /path/to/renew.sh >/dev/null 2>&1

原谅地址:https://imququ.com/post/letsencrypt-certificate.html#toc-0
参考地址:https://github.com/diafygi/acme-tiny

基于OpenSSL自建CA和颁发SSL证书

关于SSL/TLS介绍见文章 SSL/TLS原理详解
关于证书授权中心CA以及数字证书等概念,请移步 OpenSSL 与 SSL 数字证书概念
openssl是一个开源程序的套件、这个套件有三个部分组成:一是libcryto,这是一个具有通用功能的加密库,里面实现了众多的加密库;二是libssl,这个是实现ssl机制的,它是用于实现TLS/SSL的功能;三是openssl,是个多功能命令行工具,它可以实现加密解密,甚至还可以当CA来用,可以让你创建证书、吊销证书。
默认情况ubuntu和CentOS上都已安装好openssl。CentOS 6.x 上有关ssl证书的目录结构:

/etc/pki/CA/
            newcerts    存放CA签署(颁发)过的数字证书(证书备份目录)
            private     用于存放CA的私钥
            crl         吊销的证书

/etc/pki/tls/
             cert.pem    软链接到certs/ca-bundle.crt
             certs/      该服务器上的证书存放目录,可以房子自己的证书和内置证书
                   ca-bundle.crt    内置信任的证书
             private    证书密钥存放目录
             openssl.cnf    openssl的CA主配置文件

1. 颁发证书
1.1 修改CA的一些配置文件
CA要给别人颁发证书,首先自己得有一个作为根证书,我们得在一切工作之前修改好CA的配置文件、序列号、索引等等。

vim /etc/pki/tls/openssl.cnf:
...
[ CA_default ]

dir             = /etc/pki/CA           # Where everything is kept
certs           = $dir/certs            # Where the issued certs are kept
crl_dir         = $dir/crl              # Where the issued crl are kept
database        = $dir/index.txt        # database index file.
#unique_subject = no                    # Set to 'no' to allow creation of
                                        # several ctificates with same subject.
new_certs_dir   = $dir/newcerts         # default place for new certs.

certificate     = $dir/cacert.pem       # The CA certificate
serial          = $dir/serial           # The current serial number
crlnumber       = $dir/crlnumber        # the current crl number
                                        # must be commented out to leave a V1 CRL
crl             = $dir/crl.pem          # The current CRL
private_key     = $dir/private/cakey.pem # The private key
RANDFILE        = $dir/private/.rand    # private random number file
...
default_days    = 3650                  # how long to certify for
...
# For the CA policy
[ policy_match ]
countryName             = match
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional
...
[ req_distinguished_name ]
countryName                     = Country Name (2 letter code)
countryName_default             = CN
countryName_min                 = 2
countryName_max                 = 2

stateOrProvinceName             = State or Province Name (full name)
stateOrProvinceName_default     = GD
...
[ req_distinguished_name ] 部分主要是颁证时一些默认的值,可以不动

一定要注意[ policy_match ]中的设定的匹配规则,是有可能因为证书使用的工具不一样,导致即使设置了csr中看起来有相同的countryName,stateOrProvinceName等,但在最终生成证书时依然报错:

Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
The stateOrProvinceName field needed to be the same in the
CA certificate (GuangDong) and the request (GuangDong)

在CA目录下创建两个初始文件:

# touch index.txt serial
# echo 01 > serial

1.2 生成根密钥

# cd /etc/pki/CA/
# openssl genrsa -out private/cakey.pem 2048

为了安全起见,修改cakey.pem私钥文件权限为600或400,也可以使用子shell生成( umask 077; openssl genrsa -out private/cakey.pem 2048 ),下面不再重复。
1.3 生成根证书
使用req命令生成自签证书:

# openssl req -new -x509 -key private/cakey.pem -out cacert.pem

会提示输入一些内容,因为是私有的,所以可以随便输入(之前修改的openssl.cnf会在这里呈现),最好记住能与后面保持一致。上面的自签证书cacert.pem应该生成在/etc/pki/CA下。
以上都是在CA服务器上做的操作,而且只需进行一次,现在转到nginx服务器上执行:

# cd /etc/nginx/ssl
# openssl genrsa -out nginx.key 2048

这里测试的时候CA中心与要申请证书的服务器是同一个。
1.4 为我们的nginx web服务器生成ssl密钥
以上都是在CA服务器上做的操作,而且只需进行一次,现在转到nginx服务器上执行:

# cd /etc/nginx/ssl
# openssl genrsa -out nginx.key 2048

这里测试的时候CA中心与要申请证书的服务器是同一个。
1.5 为nginx生成证书签署请求

# openssl req -new -key nginx.key -out nginx.csr
...
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:GD
Locality Name (eg, city) []:SZ
Organization Name (eg, company) [Internet Widgits Pty Ltd]:COMPANY
Organizational Unit Name (eg, section) []:IT_SECTION
Common Name (e.g. server FQDN or YOUR name) []:your.domain.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
同样会提示输入一些内容,其它随便,除了<code>Commone Name</code>一定要是你要授予证书的服务器域名或主机名,<code>challenge password</code>不填。
<strong>1.6 私有CA根据请求来签署证书</strong>
接下来要把上一步生成的证书请求csr文件,发到CA服务器上,在CA上执行:

# openssl ca -in nginx.csr -out nginx.crt
另外在极少数情况下,上面的命令生成的证书不能识别,试试下面的命令:
# openssl x509 -req -in server.csr -CA /etc/pki/CA/cacert.pem -CAkey /etc/pki/CA/private/cakey.pem -CAcreateserial -out server.crt

上面签发过程其实默认使用了-cert cacert.pem -keyfile cakey.pem,这两个文件就是前两步生成的位于/etc/pki/CA下的根密钥和根证书。将生成的crt证书发回nginx服务器使用。
到此我们已经拥有了建立ssl安全连接所需要的所有文件,并且服务器的crt和key都位于配置的目录下,剩下的是如何使用证书的问题。

2. 使用ssl证书

原文链接地址: http://seanlook.com/2015/01/18/openssl-self-sign-ca/

OpenSSL 与 SSL 数字证书概念

首先简单区分一下HTTPS、SSL、OpenSSL三者的关系:
SSL是在客户端和服务器之间建立一条SSL安全通道的安全协议,而OpenSSL是TLS/SSL协议的开源实现,提供开发库和命令行程序。常说的HTTPS是HTTP的加密版,底层使用的加密协议是SSL。
1. PKI、CA与证书
PKI 就是 Public Key Infrastructure 的缩写,翻译过来就是公开密钥基础设施。它是利用公开密钥技术所构建的,解决网络安全问题的,普遍适用的一种基础设施;是一种遵循既定标准的密钥管理平台,它能够为所有网络应用提供加密和数字签名等密码服务及所必需的密钥和证书管理体系。
PKI既不是一个协议,也不是一个软件,它是一个标准,在这个标准之下发展出的为了实现安全基础服务目的的技术统称为PKI。可以说CA(认证中心)是PKI的核心,而数字证书是PKI的最基本元素,还有如apache等服务器、浏览器等客户端、银行等应用,都是pki的组件。这篇文章可以帮助理解:PKI/CA 技术的介绍 。
1.1 CA
为保证用户之间在网上传递信息的安全性、真实性、可靠性、完整性和不可抵赖性CA 机构,又称为证书认证中心 (Certificate Authority) 中心,是一个负责发放和管理数字证书的第三方权威机构,它负责管理PKI结构下的所有用户(包括各种应用程序)的证书,把用户的公钥和用户的其他信息捆绑在一起,在网上验证用户的身份。CA机构的数字签名使得攻击者不能伪造和篡改证书。
认证中心主要有以下5个功能:
1) 证书的颁发:接收、验证用户(包括下级认证中心和最终用户)的数字证书的申请。可以受理或拒绝
2) 证书的更新:认证中心可以定期更新所有用户的证书,或者根据用户的请求来更新用户的证书
3) 证书的查询:查询当前用户证书申请处理过程;查询用户证书的颁发信息,这类查询由目录服务器ldap来完成
4) 证书的作废:由于用户私钥泄密等原因,需要向认证中心提出证书作废的请求;证书已经过了有效期,认证中心自动将该证书作废。认证中心通过维护证书作废列表 (Certificate Revocation List,CRL) 来完成上述功能。
5) 证书的归档:证书具有一定的有效期,证书过了有效期之后就将作废,但是我们不能将作废的证书简单地丢弃,因为有时我们可能需要验证以前的某个交易过程中产生的数字签名,这时我们就需要查询作废的证书。
1.2 Certificate
1.2.1 X.509标准
“SSL证书”这个词是一个相对较大的概念,整个PKI体系中有很多SSL证书格式标准。PKI的标准规定了PKI的设计、实施和运营,规定了PKI各种角色的”游戏规则”,提供数据语法和语义的共同约定。x.509是PKI中最重要的标准,它定义了公钥证书的基本结构,可以说PKI是在X.509标准基础上发展起来的:
 SSL公钥证书
 证书废除列表CRL(Certificate revocation lists 证书黑名单)
参考 http://en.wikipedia.org/wiki/X.509
1.2.2 ssl公钥证书格式

1. 证书版本号(VERSION)
版本号指明X.509证书的格式版本,现在的值可以为:
    1) 0: V1
    2) 1: V2
    3) 2: V3
也为将来的版本进行了预定义

2. 证书序列号(SERIAL NUMBER)
序列号指定由CA分配给证书的唯一的"数字型标识符"。当证书被取消时,实际上是将此证书的序列号放入由CA签发的CRL中,
这也是序列号唯一的原因。

3. 签名算法标识符(SIGNATURE ALGORITHM)
签名算法标识用来指定由CA签发证书时所使用的"签名算法"。算法标识符用来指定CA签发证书时所使用的:
    1) 公开密钥算法
    2) HASH算法
EXAMPLE: SHA256WITHRSAENCRYPTION
须向国际知名标准组织(如ISO)注册

4. 签发机构名(ISSUER)
此域用来标识签发证书的CA的X.500 DN(DN-DISTINGUISHED NAME)名字。包括:
    1) 国家(C)
    2) 省市(ST)
    3) 地区(L)
    4) 组织机构(O)
    5) 单位部门(OU)
    6) 通用名(CN)
    7) 邮箱地址

5. 有效期(VALIDITY)
指定证书的有效期,包括:
    1) 证书开始生效的日期时间
    2) 证书失效的日期和时间
每次使用证书时,需要检查证书是否在有效期内。

6. 证书用户名(SUBJECT)
指定证书持有者的X.500唯一名字。包括:
    1) 国家(C)
    2) 省市(ST)
    3) 地区(L)
    4) 组织机构(O)
    5) 单位部门(OU)
    6) 通用名(CN)
    7) 邮箱地址

7. 证书持有者公开密钥信息(SUBJECT PUBLIC KEY INFO)
证书持有者公开密钥信息域包含两个重要信息:
    1) 证书持有者的公开密钥的值
    2) 公开密钥使用的算法标识符。此标识符包含公开密钥算法和HASH算法。
8. 扩展项(EXTENSION)
X.509 V3证书是在V2的基础上一标准形式或普通形式增加了扩展项,以使证书能够附带额外信息。标准扩展是指
由X.509 V3版本定义的对V2版本增加的具有广泛应用前景的扩展项,任何人都可以向一些权威机构,如ISO,来
注册一些其他扩展,如果这些扩展项应用广泛,也许以后会成为标准扩展项。

9. 签发者唯一标识符(ISSUER UNIQUE IDENTIFIER)
签发者唯一标识符在第2版加入证书定义中。此域用在当同一个X.500名字用于多个认证机构时,用一比特字符串
来唯一标识签发者的X.500名字。可选。

10. 证书持有者唯一标识符(SUBJECT UNIQUE IDENTIFIER)
持有证书者唯一标识符在第2版的标准中加入X.509证书定义。此域用在当同一个X.500名字用于多个证书持有者时,
用一比特字符串来唯一标识证书持有者的X.500名字。可选。

11. 签名算法(SIGNATURE ALGORITHM)
证书签发机构对证书上述内容的签名算法
EXAMPLE: SHA256WITHRSAENCRYPTION

12. 签名值(ISSUER'S SIGNATURE)
证书签发机构对证书上述内容的签名值

example:

CERTIFICATE:
    DATA:
        VERSION: 3 (0X2)
        SERIAL NUMBER: 9 (0X9)
    SIGNATURE ALGORITHM: SHA256WITHRSAENCRYPTION
        ISSUER: C=CN, ST=GUANGDONG, L=SHENZHEN, O=COMPANY TECHNOLOGIES CO., LTD, OU=IT_SECTION, CN=registry.example.com.net/emailAddress=zhouxiao@example.com.net
        VALIDITY
            NOT BEFORE: FEB 11 06:04:56 2015 GMT
            NOT AFTER : FEB  8 06:04:56 2025 GMT
        SUBJECT: C=CN, ST=GUANGDONG, L=SHENZHEN, O=TP-LINK CO.,LTD., OU=NETWORK MANAGEMENT, CN=172.31.1.210
        SUBJECT PUBLIC KEY INFO:
            PUBLIC KEY ALGORITHM: RSAENCRYPTION
                PUBLIC-KEY: (2048 BIT)
                MODULUS:
                    00:A4:B0:DD:EB:C1:CF:5D:47:61:A6:EA:EF:8B:AA:
                    4B:F0:B4:2C:D8:96:C7:7C:AC:FA:C7:35:88:53:D0:
                    ...
                    8A:76:DC:8F:8C:44:C8:0B:3C:36:88:5F:01:F0:44:
                    4E:81:E6:7A:2B:FF:BA:DA:33:A5:27:11:C6:F0:08:
                    6E:F3
                EXPONENT: 65537 (0X10001)
        X509V3 EXTENSIONS:
            X509V3 BASIC CONSTRAINTS: 
                CA:FALSE
            NETSCAPE COMMENT: 
                OPENSSL GENERATED CERTIFICATE
            X509V3 SUBJECT KEY IDENTIFIER: 
                07:C6:87:B7:C1:1E:28:E8:96:3F:EB:40:1E:82:41:45:CA:81:B6:3D
            X509V3 AUTHORITY KEY IDENTIFIER: 
                KEYID:A4:C2:14:6A:39:D1:95:1E:BD:DF:3B:92:4A:5C:12:42:1B:BC:53:B8

    SIGNATURE ALGORITHM: SHA256WITHRSAENCRYPTION
         0C:C6:81:70:CD:0A:2D:94:4F:CB:A4:1D:EF:9E:8E:E4:73:AE:
         50:62:A8:9C:64:EF:56:0F:41:FE:6B:B4:D3:07:37:39:2C:ED:
         ...
         6F:62:61:B8:03:D7:97:31:AB:05:44:20:07:65:8B:AD:E2:CC:
         AD:65:73:F6:82:0F:9E:65:D0:AE:B7:1E:FD:9F:C1:D7:41:6C:
         0F:06:95:EE
-----BEGIN CERTIFICATE-----
MIIEMDCCAXIGAWIBAGIBCTANBGKQHKIG9W0BAQSFADCBTTELMAKGA1UEBHMCQ04X
EJAQBGNVBAGMCUD1YW5NRG9UZZERMA8GA1UEBWWIU2HLBLPOZW4XJJAKBGNVBAOM
...
UJWWRAR6PPZUSO95WUS93HSNML2ZFZ63DS4LCW9IYBGD15CXQWVEIADLI63IZK1L
C/ACD55L0K63HV2FWDDBBA8GLE4=
-----END CERTIFICATE-----

2. 附:数据加密的基础知识
 对称密钥加密
对称密钥加密(一个密钥),也叫做共享密钥加密或机密密钥加密,使用发件人和收件人共同拥有的单个密钥。这种密钥既用于加密,也用于解密,叫做机密密钥。对称密钥加密是加密大量数据的一种行之有效的方法。
对称密钥加密有许多种算法如DES,RC4,IDEA等,但所有这些算法都有一个共同的目的:以可还原的方式将明文 (未加密的数据转换为暗文。暗文使用加密密钥编码,对于没有解密密钥的任何人来说它都是没有意义的。由于对称密钥加密在加密和解密时使用相同的密钥,所以这种加密过程的安全性取决于是否有未经授权的人获得了对称密钥。
衡量对称算法优劣的主要尺度是其密钥的长度。密钥越长,在找到解密数据所需的正确密钥之前必须测试的密钥数量就越多。需要测试的密钥越多,破解这种算法就越困难。
 公钥加密
公钥加密使用两个密钥:一个公钥和一个私钥,这两个密钥在数学上是相关的。为了与对称密钥加密相对照,公钥加密有时也叫做不对称密钥加密。在公钥加密中,公钥可在通信双方之间公开传递,或在公用储备库中发布,但相关的私钥是保密的。只有使用私钥才能解密用公钥加密的数据。使用私钥加密的数据只能用公钥解密。下图中,发件人拥有收件人的公钥,并用它加密了一封邮件,但只有收件人掌握解密该邮件的有关私钥。

公钥算法的主要局限在于,这种加密形式的速度相对较低。实际上,通常仅在关键时刻才使用公钥算法,如在实体之间交换对称密钥时,或者在签署一封邮件的散列时(散列是通过应用一种单向数学函数获得的一个定长结果,对于数据而言,叫做散列算法)。将公钥加密与其它加密形式(如对称密钥加密)结合使用,可以优化性能,如数字签名和密钥交换。
常用公钥算法:
RSA:适用于数字签名和密钥交换。 是目前应用最广泛的公钥加密算法,特别适用于通过 Internet 传送的数据,RSA算法以它的三位发明者的名字命名。
DSA:仅适用于数字签名。 数字签名算法 (Digital Signature Algorithm, DSA) 由美国国家安全署 (United States National Security Agency, NSA) 发明,已作为数字签名的标准。DSA 算法的安全性取决于自计算离散算法的困难。这种算法,不适用于数据加密。
Diffie-Hellman:仅适用于密钥交换。 Diffie-Hellman 是发明的第一个公钥算法,以其发明者 Whitfield Diffie 和 Martin Hellman 的名字命名。Diffie-Hellman 算法的安全性取决于在一个有限字段中计算离散算法的困难。
单向散列算法:
散列,也称为散列值或消息摘要 ,是一种与基于密钥(对称密钥或公钥)的加密不同的数据转换类型。散列就是通过把一个叫做散列算法的单向数学函数应用于数据,将任意长度的一块数据转换为一个定长的、不可逆转的数字,其长度通常在128~256位之间。所产生的散列值的长度应足够长,因此使找到两块具有相同散列值的数据的机会很少。如发件人生成邮件的散列值并加密它,然后将它与邮件本身一起发送。而收件人同时解密邮件和散列值,并由接收到的邮件产生另外一个散列值,然后将两个散列值进行比较。如果两者相同,邮件极有可能在传输期间没有发生任何改变。
下面是几个常用的散列函数:
MD5:是RSA数据安全公司开发的一种单向散列算法,MD5被广泛使用,可以用来把不同长度的数据块进行暗码运算成一个128位的数值。
SHA-1:与 DSA 公钥算法相似,安全散列算法1(SHA-1)也是由 NSA 设计的,并由 NIST 将其收录到 FIPS 中,作为散列数据的标准。它可产生一个 160 位的散列值。SHA-1 是流行的用于创建数字签名的单向散列算法。
MAC(Message Authentication Code):消息认证代码,是一种使用密钥的单向函数,可以用它们在系统上或用户之间认证文件或消息,常见的是HMAC(用于消息认证的密钥散列算法)。
CRC(Cyclic Redundancy Check):循环冗余校验码,CRC校验由于实现简单,检错能力强,被广泛使用在各种数据校验应用中。占用系统资源少,用软硬件均能实现,是进行数据传输差错检测地一种很好的手段(CRC 并不是严格意义上的散列算法,但它的作用与散列算法大致相同,所以归于此类)。
数字签名:结合使用公钥与散列算法
数字签名是邮件、文件或其它数字编码信息的发件人将他们的身份与信息绑定在一起(即为信息提供签名)的方法。对信息进行数字签名的过程,需要将信息与由发件人掌握的秘密信息一起转换(使用私钥)为叫做签名的标记。数字签名用于公钥环境(任何人都可以拥有)中,它通过验证发件人确实是他或她所声明的那个人,并确认收到的邮件与发送的邮件完全相同。
散列算法处理数据的速度比公钥算法快得多。散列数据还缩短了要签名的数据的长度,因而加快了签名过程。
密钥交换:结合使用对称密钥与公钥
对称密钥算法非常适合于快速并安全地加密数据。但其缺点是,发件人和收件人必须在交换数据之前先交换机密密钥。结合使用加密数据的对称密钥算法与交换机密密钥的公钥算法可产生一种既快速又灵活的解决方案。
3.参考
openSSL命令、PKI、CA、SSL证书原理
X.509 wikipeida
PKI 基础知识
数字证书及CA的扫盲介绍

原文链接地址: http://seanlook.com/2015/01/15/openssl-certificate-encryption/

SSL/TLS原理详解

1.SSL/TLS概览

  •     1.1整体结构

SSL是一个介于HTTP协议与TCP之间的一个可选层,其位置大致如下:

SSL:(Secure Socket Layer,安全套接字层),为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。当前版本为3.0。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。
SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层: SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。 SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
TLS:(Transport Layer Security,传输层安全协议),用于两个应用程序之间提供保密性和数据完整性。
TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC 的。该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。较低的层为 TLS 记录协议,位于某个可靠的传输协议(例如 TCP)上面。
SSL/TLS协议提供的服务主要有:
a)认证用户和服务器,确保数据发送到正确的客户机和服务器;
b)加密数据以防止数据中途被窃取;
c)维护数据的完整性,确保数据在传输过程中不被改变。

  •     1.2TLS与SSL的差异

1)版本号:TLS记录格式与SSL记录格式相同,但版本号的值不同,TLS的版本1.0使用的版本号为SSLv3.1。
2)报文鉴别码:SSLv3.0和TLS的MAC算法及MAC计算的范围不同。TLS使用了RFC-2104定义的HMAC算法。SSLv3.0使用了相似的算法,两者差别在于SSLv3.0中,填充字节与密钥之间采用的是连接运算,而HMAC算法采用的是异或运算。但是两者的安全程度是相同的。
3)伪随机函数:TLS使用了称为PRF的伪随机函数来将密钥扩展成数据块,是更安全的方式。
4)报警代码:TLS支持几乎所有的SSLv3.0报警代码,而且TLS还补充定义了很多报警代码,如解密失败(decryption_failed)、记录溢出(record_overflow)、未知CA(unknown_ca)、拒绝访问(access_denied)等。
5)密文族和客户证书:SSLv3.0和TLS存在少量差别,即TLS不支持Fortezza密钥交换、加密算法和客户证书。
6)certificate_verify和finished消息:SSLv3.0和TLS在用certificate_verify和finished消息计算MD5和SHA-1散列码时,计算的输入有少许差别,但安全性相当。
7)加密计算:TLS与SSLv3.0在计算主密值(master secret)时采用的方式不同。
8)填充:用户数据加密之前需要增加的填充字节。在SSL中,填充后的数据长度要达到密文块长度的最小整数倍。而在TLS中,填充后的数据长度可以是密文块长度的任意整数倍(但填充的最大长度为255字节),这种方式可以防止基于对报文长度进行分析的攻击。
TLS的主要增强内容
TLS的主要目标是使SSL更安全,并使协议的规范更精确和完善。TLS 在SSL v3.0 的基础上,提供了以下增强内容:
a)更安全的MAC算法
b)更严密的警报
c)“灰色区域”规范的更明确的定义
d)TLS对于安全性的改进
TLS对于安全性的改进
a)对于消息认证使用密钥散列法:TLS 使用“消息认证代码的密钥散列法”(HMAC),当记录在开放的网络(如因特网)上传送时,该代码确保记录不会被变更。SSLv3.0还提供键控消息认证,但HMAC比SSLv3.0使用的(消息认证代码)MAC 功能更安全。
b)增强的伪随机功能(PRF):PRF生成密钥数据。在TLS中,HMAC定义PRF。PRF使用两种散列算法保证其安全性。如果任一算法暴露了,只要第二种算法未暴露,则数据仍然是安全的。
c)改进的已完成消息验证:TLS和SSLv3.0都对两个端点提供已完成的消息,该消息认证交换的消息没有被变更。然而,TLS将此已完成消息基于PRF和HMAC值之上,这也比SSLv3.0更安全。
d)一致证书处理:与SSLv3.0不同,TLS试图指定必须在TLS之间实现交换的证书类型。
e)特定警报消息:TLS提供更多的特定和附加警报,以指示任一会话端点检测到的问题。TLS还对何时应该发送某些警报进行记录。

    2.密钥协商过程——TLS握手
SSL协议分为两部分:Handshake Protocol和Record Protocol。其中Handshake Protocol用来协商密钥,协议的大部分内容就是通信双方如何利用它来安全的协商出一份密钥。 Record Protocol则定义了传输的格式。
由于非对称加密的速度比较慢,所以它一般用于密钥交换,双方通过公钥算法协商出一份密钥,然后通过对称加密来通信,当然,为了保证数据的完整性,在加密前要先经过HMAC的处理。
SSL缺省只进行server端的认证,客户端的认证是可选的。以下是其流程图(摘自TLS协议)。

2.1 客户端发出请求(ClientHello)
由于客户端(如浏览器)对一些加解密算法的支持程度不一样,但是在TLS协议传输过程中必须使用同一套加解密算法才能保证数据能够正常的加解密。在TLS握手阶段,客户端首先要告知服务端,自己支持哪些加密算法,所以客户端需要将本地支持的加密套件(Cipher Suite)的列表传送给服务端。除此之外,客户端还要产生一个随机数,这个随机数一方面需要在客户端保存,另一方面需要传送给服务端,客户端的随机数需要跟服务端产生的随机数结合起来产生后面要讲到的 Master Secret 。
综上,在这一步,客户端主要向服务器提供以下信息:
1) 支持的协议版本,比如TLS 1.0版
2) 一个客户端生成的随机数,稍后用于生成”对话密钥”
3) 支持的加密方法,比如RSA公钥加密
4) 支持的压缩方法
2.2 服务器回应(SeverHello)
上图中,从Server Hello到Server Done,有些服务端的实现是每条单独发送,有服务端实现是合并到一起发送。Sever Hello和Server Done都是只有头没有内容的数据。
服务端在接收到客户端的Client Hello之后,服务端需要将自己的证书发送给客户端。这个证书是对于服务端的一种认证。例如,客户端收到了一个来自于称自己是www.alipay.com的数据,但是如何证明对方是合法的alipay支付宝呢?这就是证书的作用,支付宝的证书可以证明它是alipay,而不是财付通。证书是需要申请,并由专门的数字证书认证机构(CA)通过非常严格的审核之后颁发的电子证书。颁发证书的同时会产生一个私钥和公钥。私钥由服务端自己保存,不可泄漏。公钥则是附带在证书的信息中,可以公开的。证书本身也附带一个证书电子签名,这个签名用来验证证书的完整性和真实性,可以防止证书被串改。另外,证书还有个有效期。
在服务端向客户端发送的证书中没有提供足够的信息(证书公钥)的时候,还可以向客户端发送一个 Server Key Exchange,
此外,对于非常重要的保密数据,服务端还需要对客户端进行验证,以保证数据传送给了安全的合法的客户端。服务端可以向客户端发出 Cerficate Request 消息,要求客户端发送证书对客户端的合法性进行验证。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
跟客户端一样,服务端也需要产生一个随机数发送给客户端。客户端和服务端都需要使用这两个随机数来产生Master Secret。
最后服务端会发送一个Server Hello Done消息给客户端,表示Server Hello消息结束了。
综上,在这一步,服务器的回应包含以下内容:
1) 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信
2) 一个服务器生成的随机数,稍后用于生成”对话密钥”
3) 确认使用的加密方法,比如RSA公钥加密
4) 服务器证书
2.3 客户端回应(Certificate Verify)
Client Key Exchange
如果服务端需要对客户端进行验证,在客户端收到服务端的 Server Hello 消息之后,首先需要向服务端发送客户端的证书,让服务端来验证客户端的合法性。
Certificate Verify
接着,客户端需要对服务端的证书进行检查,如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。如果证书没有问题,客户端就会从服务器证书中取出服务器的公钥。然后,向服务器发送下面三项信息:
1) 一个随机数。该随机数用服务器公钥加密,防止被窃听
2) 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
3) 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验
上面第一项的随机数,是整个握手阶段出现的第三个随机数,它是客户端使用一些加密算法(例如:RSA, Diffie-Hellman)产生一个48个字节的Key,这个Key叫 PreMaster Secret,很多材料上也被称作 PreMaster Key。
ChangeCipherSpec
ChangeCipherSpec是一个独立的协议,体现在数据包中就是一个字节的数据,用于告知服务端,客户端已经切换到之前协商好的加密套件(Cipher Suite)的状态,准备使用之前协商好的加密套件加密数据并传输了。
在ChangecipherSpec传输完毕之后,客户端会使用之前协商好的加密套件和Session Secret加密一段 Finish 的数据传送给服务端,此数据是为了在正式传输应用数据之前对刚刚握手建立起来的加解密通道进行验证。
2.4 服务器的最后回应(Server Finish)
服务端在接收到客户端传过来的 PreMaster 加密数据之后,使用私钥对这段加密数据进行解密,并对数据进行验证,也会使用跟客户端同样的方式生成 Session Secret,一切准备好之后,会给客户端发送一个 ChangeCipherSpec,告知客户端已经切换到协商过的加密套件状态,准备使用加密套件和 Session Secret加密数据了。之后,服务端也会使用 Session Secret 加密一段 Finish 消息发送给客户端,以验证之前通过握手建立起来的加解密通道是否成功。
根据之前的握手信息,如果客户端和服务端都能对Finish信息进行正常加解密且消息正确的被验证,则说明握手通道已经建立成功,接下来,双方可以使用上面产生的Session Secret对数据进行加密传输了。
2.5 几个secret
Secret Keys
上面的分析和讲解主要是为了突出握手的过程,所以PreMaster secret,Master secret,session secret都是一代而过,但是对于Https,SSL/TLS深入的理解和掌握,这些Secret Keys是非常重要的部分。所以,准备把这些Secret Keys抽出来单独分析和讲解。
我们先来看看这些Secret Keys的生成过程以及作用流程图:

PreMaster secret
PreMaster Secret是在客户端使用RSA或者Diffie-Hellman等加密算法生成的。它将用来跟服务端和客户端在Hello阶段产生的随机数结合在一起生成 Master Secret。在客户端使用服务端的公钥对PreMaster Secret进行加密之后传送给服务端,服务端将使用私钥进行解密得到PreMaster secret。也就是说服务端和客户端都有一份相同的PreMaster secret和随机数。
PreMaster secret前两个字节是TLS的版本号,这是一个比较重要的用来核对握手数据的版本号,因为在Client Hello阶段,客户端会发送一份加密套件列表和当前支持的SSL/TLS的版本号给服务端,而且是使用明文传送的,如果握手的数据包被破解之后,攻击者很有可能串改数据包,选择一个安全性较低的加密套件和版本给服务端,从而对数据进行破解。所以,服务端需要对密文中解密出来对的PreMaster版本号跟之前Client Hello阶段的版本号进行对比,如果版本号变低,则说明被串改,则立即停止发送任何消息。
关于PreMaster Secret(Key)的计算请参考 Https SSL/TLS PreMaster/Master Secret(Key)计算。
Master secret
上面已经提到,由于服务端和客户端都有一份相同的PreMaster secret和随机数,这个随机数将作为后面产生Master secret的种子,结合PreMaster secret,客户端和服务端将计算出同样的Master secret。
Master secret是有系列的hash值组成的,它将作为数据加解密相关的secret的 Key Material 的一部分。Key Material最终解析出来的数据如下:

其中,write MAC key,就是session secret或者说是session key。Client write MAC key是客户端发数据的session secret,Server write MAC secret是服务端发送数据的session key。MAC(Message Authentication Code),是一个数字签名,用来验证数据的完整性,可以检测到数据是否被串改。
关于Session Secret(Key)的计算请参考 Https SSL/TLS Session Secret(Key)计算
    2.6 应用数据传输
在所有的握手阶段都完成之后,就可以开始传送应用数据了。应用数据在传输之前,首先要附加上MAC secret,然后再对这个数据包使用write encryption key进行加密。在服务端收到密文之后,使用Client write encryption key进行解密,客户端收到服务端的数据之后使用Server write encryption key进行解密,然后使用各自的write MAC key对数据的完整性包括是否被串改进行验证。
    2.7 总结
SSL客户端(也是TCP的客户端)在TCP链接建立之后,发出一个ClientHello来发起握手,这个消息里面包含了自己可实现的算法列表和其它一些需要的消息,SSL的服务器端会回应一个ServerHello,这里面确定了这次通信所需要的算法,然后发过去自己的证书(里面包含了身份和自己的公钥)。Client在收到这个消息后会生成一个秘密消息,用SSL服务器的公钥加密后传过去,SSL服务器端用自己的私钥解密后,会话密钥协商成功,双方可以用同一份会话密钥来通信了。
3. 附:密钥协商的形象化比喻
如果上面的说明不够清晰,这里我们用个形象的比喻,我们假设A与B通信,A是SSL客户端,B是SSL服务器端,加密后的消息放在方括号[]里,以突出明文消息的区别。双方的处理动作的说明用圆括号()括起。
A:我想和你安全的通话,我这里的对称加密算法有DES,RC5,密钥交换算法有RSA和DH,摘要算法有MD5和SHA。

B:我们用DES-RSA-SHA这对组合好了。
这是我的证书,里面有我的名字和公钥,你拿去验证一下我的身份(把证书发给A)。
目前没有别的可说的了。

A:(查看证书上B的名字是否无误,并通过手头早已有的CA的证书验证了B的证书的真实性,如果其中一项有误,发出警告并断开连接,这一步保证了B的公钥的真实性)
(产生一份秘密消息,这份秘密消息处理后将用作加密密钥,加密初始化向量(IV)和hmac的密钥。将这份秘密消息-协议中称为per_master_secret-用B的公钥加密,封装成称作ClientKeyExchange的消息。由于用了B的公钥,保证了第三方无法窃听)
我生成了一份秘密消息,并用你的公钥加密了,给你(把ClientKeyExchange发给B)
注意,下面我就要用加密的办法给你发消息了!
(将秘密消息进行处理,生成加密密钥,加密初始化向量和hmac的密钥)
[我说完了]

B:(用自己的私钥将ClientKeyExchange中的秘密消息解密出来,然后将秘密消息进行处理,生成加密密钥,加密初始化向量和hmac的密钥,这时双方已经安全的协商出一套加密办法了)
注意,我也要开始用加密的办法给你发消息了!
[我说完了]

A: [我的秘密是…]

B: [其它人不会听到的…]
4. SSL安全性
SecurityPortal在2000年底有一份文章《The End of SSL and SSH?》激起了很多的讨论, 目前也有一些成熟的工具如dsniff(http://www.monkey.org/~dugsong/dsniff/)可以通过man in the middle攻击来截获https的消息。
从上面的原理可知,SSL的结构是严谨的,问题一般出现在实际不严谨的应用中。常见的攻击就是middle in the middle攻击,它是指在A和B通信的同时,有第三方C处于信道的中间,可以完全听到A与B通信的消息,并可拦截,替换和添加这些消息。
1) SSL可以允许多种密钥交换算法,而有些算法,如DH,没有证书的概念,这样A便无法验证B的公钥和身份的真实性,从而C可以轻易的冒充,用自己的密钥与双方通信,从而窃听到别人谈话的内容。
而为了防止middle in the middle攻击,应该采用有证书的密钥交换算法。
2) 有了证书以后,如果C用自己的证书替换掉原有的证书之后,A的浏览器会弹出一个警告框进行警告,但又有多少人会注意这个警告呢?
3) 由于美国密码出口的限制,IE,netscape等浏览器所支持的加密强度是很弱的,如果只采用浏览器自带的加密功能的话,理论上存在被破解可能。
5. 代理
下面探讨一下SSL的代理是怎样工作的
当在浏览器里设置了https的代理,而且里输入了https://www.example.com之后,浏览器会与proxy建立tcp链接,然后向其发出这么一段消息:

CONNECT SERVER.EXAMPLE.COM:443 HTTP/1.1
HOST: SERVER.EXAMPLE.COM:443

然后proxy会向webserver端建立tcp连接,之后,这个代理便完全成了个内容转发装置。浏览器与web server会建立一个安全通道,因此这个安全通道是端到端的,尽管所有的信息流过了proxy,但其内容proxy是无法解密和改动的(当然要由证书的支持,否则这个地方便是个man in the middle攻击的好场所,见上面的安全部分)。
CA证书以及如何使用OpenSSL自签署,见文章OpenSSL自签署证书 。
6. 参考
Https(SSL/TLS)原理详解
SSL与TLS的区别以及介绍
SSL/TLS协议运行机制的概述
SSL/TLS/WTLS原理
Transport Layer Security (TLS)
传输层安全协议
Survival guides – TLS/SSL and SSL (X.509) Certificates

原文链接地址:https://segmentfault.com/a/1190000002554673

CA认证原理

1.什么是CA
认证中心(CA─Certificate Authority)作为权威的、可信赖的、公正的第三方机构,专门负责发放并管理所有参与网上交易的实体所需的数字证书。它作为一个权威机构,对密钥进行有效地管理,颁发证书证明密钥的有效性,并将公开密钥同某一个实体(消费者、商户、银行)联系在一起。
随着认证中心(或称CA中心)的出现,使得开放网络的安全问题得以迎刃而解。利用数字证书、PKI、对称加密算法、数字签名、数字信封等加密技术,可以建立起安全程度极高的加解密和身份认证系统,确保电子交易有效、安全地进行,从而使信息除发送方和接收方外,不被其他方知悉(保密性);保证传输过程中不被篡改(完整性和一致性);发送方确信接收方不是假冒的(身份的真实性和不可伪装性);发送方不能否认自己的发送行为(不可抵赖性)。
2.CA的架构
CA认证的主要工具是CA中心为网上作业主体颁发的数字证书。CA架构包括PKI结构、高强度抗攻击的公开加解密算法、数字签名技术、身份认证技术、运行安全管理技术、可靠的信任责任体系等等。从业务流程涉及的角色看, 包括认证机构、数字证书库和黑名单库、密钥托管处理系统、证书目录服务、证书审批和作废处理系统。从CA的层次结构来看, 可以分为认证中心(根CA)、密钥管理中心(KM)、认证下级中心(子CA)、证书审批中心(RA中心)、证书审批受理点(RAT)等。 CA中心一般要发布认证体系声明书,向服务的对象郑重声明CA的政策、保证安全的措施、服务的范围、服务的质量、承担的责任、操作流程等条款。
根据PKI的结构, 身份认证的实体需要有一对密钥, 分别为私钥和公钥。 其中的私钥是保密的, 公钥是公开的。从原理上讲, 不能从公钥推导出私钥,穷举法来求私钥则由于目前的技术、运算工具和时间的限制而不可能。每个实体的密钥总是成对出现,即一个公钥必定对应一个私钥。公钥加密的信息必须由对应的私钥才能解密,同样,私钥做出的签名, 也只有配对的公钥才能解密。 公钥有时用来传输对称密钥,这就是数字信封技术。 密钥的管理政策是把公钥和实体绑定, 由CA中心把实体的信息和实体的公钥制作成数字证书, 证书的尾部必须有CA中心的数字签名。 由于CA中心的数字签名是不可伪造的, 因此实体的数字证书不可伪造。CA中心对实体的物理身份资格审查通过后,才对申请者颁发数字证书, 将实体的身份与数字证书对应起来。由于实体都信任提供第三方服务的CA中心, 因此, 实体可以信任由CA中心颁发数字证书的其他实体, 放心地在网上进行作业和交易。
3.CA的职责
CA中心主要职责是颁发和管理数字证书。 其中心任务是颁发数字证书,并履行用户身份认证的责任。CA中心在安全责任分散、运行安全管理、系统安全、物理安全、数据库安全、人员安全、密钥管理等方面, 需要十分严格的政策和规程,要有完善的安全机制。另外要有完善的安全审计、运行监控、容灾备份、事故快速反应等实施措施, 对身份认证、访问控制、防病毒防攻击等方面也要有强大的工具支撑。CA中心的证书审批业务部门则负责对证书申请者进行资格审查, 并决定是否同意给该申请者发放证书,并承担因审核错误引起的、为不满足资格的证书申请者发放证书所引起的一切后果, 因此, 它应是能够承担这些责任的机构担任;证书操作部门(Certificate Processor,简称CP)负责为已授权的申请者制作、发放和管理证书, 并承担因操作运营错误所产生的一切后果, 包括失密和为没有授权者发放证书等, 它可以由审核业务部门自己担任, 也可委托给第三方担任。
4.RA
RA(Registration Authority),数字证书注册审批机构。RA系统是CA的证书发放、管理的延伸。它负责证书申请者的信息录入、审核以及证书发放等工作;同时,对发放的证书完成相应的管理功能。发放的数字证书可以存放于IC卡、硬盘或软盘等介质中。RA系统是整个CA中心得以正常运营不可缺少的一部分。
5.数字证书
数字安全证书就是标志网络用户身份信息的一系列数据,用来在网络通讯中识别通讯各方的身份,即要在Internet上解决”我是谁”的问题,就如同现实中我们每一个人都要拥有一张证明个人身份的身份证或驾驶执照一样,以表明我们的身份或某种资格。
在网上电子交易中, 商户需要确认持卡人是信用卡或借记卡的合法持有者,同时持卡人也必须能够鉴别商户是否是合法商户,是否被授权接受某种品牌的信用卡或借记卡支付。为处理这些关键问题,必须有一个大家都信赖的机构来发放数字安全证书。数字安全证书就是参与网上交易活动的各方(如持卡人、商家、支付网关) 身份的代表,每次交易时,都要通过数字安全证书对各方的身份进行验证。数字安全证书是由权威公正的第三方机构即CA中心签发的,它在证书申请被认证中心批准后,通过登记服务机构将证书发放给申请者。
数字安全证书是一个经证书授权中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。最简单的证书包含一个公开密钥、名称以及证书授权中心的数字签名。一般情况下证书中还包括密钥的有效时间,发证机关(证书授权中心)的名称,该证书的序列号等信息,证书的格式遵循ITUT X.509国际标准。
一个标准的X.509数字安全证书包含以下一些内容:
证书的版本信息;
证书的序列号,每个证书都有一个唯一的证书序列号;
证书所使用的签名算法;
证书的发行机构名称,命名规则一般采用X.500格式;
证书的有效期,现在通用的证书一般采用UTC时间格式;
证书所有人的名称,命名规则一般采用X.500格式;
证书所有人的公开密钥;
证书发行者对证书的签名。

Nginx 路由杂谈 – Rewrite 指令与重定向

在书写 nginx 路由规则的时候, 得保证规则配置的规范, 否则维护起来成本很高。 本文简要地讨论了在 rewrite 模块的基础实现的简单路由规则,并解释了常用指令的使用细节。

rewrite 与 location

首先,这是一个不完整 nginx 虚拟主机配置:

server {

    root /service/http;

    index index.php;


    rewrite ^/api/(.*)+ /index.php?app=api&amp;method=$1 break;

    rewrite ^/index.php/(.*)+ /old_api_warning.html break;


    location / {

        return 200 "index";

    }


    location = /index.php {

        return 200 "index.php";

    }

}

上面的例子中, 指定的类型可以分为两种。

  • server,root, index, location 是 ngx_http_core_module 提供的指令
  • 而 rewrite 则是 ngx_http_rewrite_module 提供的指令之一, 同类的还有常用的 if

简单地概括, nginx 的处理配置流程如下

  1. 进行 rewrite 规则匹配,根据命中规则改写 location。
  2. 如果没有被其他指令中断并退出, 则进入 location 匹配。
  3. 执行匹配成功的 location 区块中的指令。

而对于 rewrite 系列指令和 location 指令的安排, 在书写配置的时候, 应该先定义好 location , 因为 location 是和 nginx 和后端服务沟通的桥梁。 再根据 location 的需要, 使用 rewrite 将各种各样用户输入的 url 正确地映射到 对应 locaton , 由 location 中的 proxy 指令转发给后端。

比如一个常规的 php 站点,有以下类似的 location 配置。

# 静态资源

location /assets {

    root /service/http/assets/;

}

# 入口 php 脚本 位置

location /index.php {

    root /service/http/phpsrc/webroot/;

# ... 若干 fast-cgi 代理规则

}

#注意, 这份配置省略了不必要的指令,能够满足大部分的 php 应用的需要。

有时候,为了优化 url 的展示,或者兼容旧应用的 url 规则, 只是这么简单的两条 location 配置自然是不够用的。这时候我们可以通过 rewrite 规则进行弥补。

rewrite /index.html$ /app/page-default; //兼容旧 url

rewrite /app/page-(.*) /index.php?app=api&page=$1 break;

请注意这两条 rewrite 规则区别的在最后一个 flag 参数 break,可用的 flag 参数还有 `permanent`, `redirect`, `last` 等, 后面分析它们之间的差别。

多出来的 break 参数作用会使得 rewirte 指令的跳转方式发生变化:

  • 没有 flag 的 rewrite 指令完成 location 改写之后,继续往下寻找其他 rewrite 规则,看看有没有符合要求的。如果没有,那么进入 location 匹配。
  • 带 break,跳过其他所有 rewrite 规则,进入 location 匹配

这里简单总结一下:

  • 如果某一条 rewrite 规则命中直接转发给后端应用,那么应该加上一个 break 标记。
  • 如果 rewrite 规则起补充作用,还需要其他规则配合完成,那么不带第三个的 flag 参数。

以上说的 rewrite 规则属于 nginx 的内部重定向规则, 也就是说, 用户外部看到的 url 依然是他输入的 url , 而转给后端应用的 $uri , 则已经是 nginx 改写之后的结果。

如果需要进行显式地外部重定向, 需要借助 redirect , permanent 这两个 flag 进行 302 和 301 重定向. 它的行为和 break 类似, 区别在于 nginx 会中断流程, 通过 http 请求告诉用户端进行重定向, 也就是这次请求不需要进过后端服务, 由 nginx 全职负责。

rewrite /error.html$ /error2.html redirect;

break 和 last 区别

这两个 flag 都会中断当前 rewrite 流程, 不再继续匹配后续的 rewrite 指令。

wiki 上是这么写的

stops processing the current set of ngx_http_rewrite_module directives and starts a search for a new location matching the changed URI

如果是在 server 的顶级部分, 那么它们的作用是相同, 跳过剩下的 rewrite 指定, 进入 location 匹配。 如果 rewrite 是在 server 区块顶级 if 内部, 和直接放在 server 下级的 rewrite 行为是一致的。

server {

    rewrite /error1.html /error.html break;

    rewrite /error2.html /error.html last;

&nbsp;

    if ( $arg_version = "1.1" ) {

        rewrite /error3.html /error.html break;

    }

&nbsp;

    location = error.html {

    }

}

以上的 rewrite 的跳转行为是相同的, 进入 location 匹配流程。

而两者的区别, 在于当 rewrite 指令存在于 location 区块中时, 见例子

location = index.php {

    if ( $arg_q  = "" ) {

      rewrite /index.php /error.html last;

    }

}
location = error.html {

    if ( $arg_test ~= "" ) {

        rewrite /error.html /error-test.html break;

    }

}

第一个 location , 如果 q 参数为空, 那么将展示错误页面。

第二个 location , 如果 test 参数不为空, 通过 rewrite 规则,使用另外一个 error-test.html, 但 location 不变

注意, 这里并没有一个 `location = error2.html` 的匹配规则

从这个 case 的结果, 可以明显得区分两个指令之间的细微差别

  • last 跳出 location 块,重新进行 location 匹配
  • break 跳过 location 下的后续 rewrite 规则,执行其他指令。

所以 last 的特殊在于重新进行 location 匹配, 这也就是为什么会从”locaton = index.php”转向”locaton = error.html”

所以一般情况下, 使用 break 指令会相对安全, 不会造成循环重定向。

比如:

location = error.html {

    rewrite /error.html /error.html  break;

&nbsp;

    if ($arg_q = "") {

      return 404 "not q";

    }

&nbsp;

    fastcgi_pass 127.0.0.1:9000;

    #其他 fastcgi 配置

}

 

location = error.html {

    rewrite /error.html /error.html last;

    fastcgi_pass 127.0.0.1:9000;

}

第一个例子,完成了 rewrite 指令之后,break 指令使得后续的其他 rewrite 规则失效, 接着进行 proxy. 第二个例子会导致不断地进行 location 匹配, 最终导致 nginx 返回 500.

返回结果是这样的:

HTTP/1.1 500 Internal Server Error

Server: nginx/1.6.3

Date: Sat, 18 Apr 2015 12:10:49 GMT

Content-Type: text/html

Content-Length: 192

Connection: close

注:nginx 会记录同一条 rewrite 规则的执行次数,如果超过10次,将自动触发 500 进行自我保护。

小结

这里构造了两组例子用来说明 last 和 break 两个 flag 参数行为上的不一致。 并不是说 rewrite 规则在 server 和 location 上下文中的行为不一致, 而是他们的行为特征很容易造成误解。

两者的本质区别在于是否重新进行 location 匹配,所以当在 location 上下文 进行 last rewrite时。 对于不熟悉 rewrite 指定的其他人容易造成误解。

所以还是前文所提到的观点, 尽可能地将 rewrite 和 location 离开来。 在 location 中进行 rewrite, 容易造成重定向问题。

由于 rewrite 模块的 `rewrite` 和 `if` 指令会使得 nginx

的路由规则出现较多的逻辑和分支跳转, 在维护性上是比较糟糕的,

并不推荐过多地进行使用, 本文只是从行为和特性上分析了这些指令,

并不代表支持过度使用 rewrite 指令。

return 指令的应用

在 rewrite 模块中, 有一条非常有用的指令 return, 用于直接返回客户端指定的状态码。 甚至支持指定文本内容和url, 相比起使用 rewrite 指令302进行曲线救国,要简便地多。

location = /index.php {

    if ( $arg_q = "" ) {

        return 302 /page_not_found.html;

    }

&nbsp;

    if ( $arg_id = "" ) {

        return 404 "page not found";

    }

    return 200 "hello";

}

对于常规的基于 url 提供服务的应用, 基础的 rewrite 指令配合已经足够完成大部分任务。

 

 

Nginx 关于 location 的匹配规则详解

有些童鞋的误区

1 location 的匹配顺序是“先匹配正则,再匹配普通”。

矫正: location 的匹配顺序其实是“先匹配普通,再匹配正则”。我这么说,大家一定会反驳我,因为按“先匹配普通,再匹配正则”解释不了大家平时习惯的按“先匹配正则,再匹配普通”的实践经验。这里我只能暂时解释下,造成这种误解的原因是:正则匹配会覆盖普通匹配(实际的规则,比这复杂,后面会详细解释)。

2 location 的执行逻辑跟 location 的编辑顺序无关。

矫正:这句话不全对,“普通 location ”的匹配规则是“最大前缀”,因此“普通 location ”的确与 location 编辑顺序无关;但是“正则 location ”的匹配规则是“顺序匹配,且只要匹配到第一个就停止后面的匹配”;“普通 location ”与“正则 location ”之间的匹配顺序是?先匹配普通 location ,再“考虑”匹配正则 location 。注意这里的“考虑”是“可能”的意思,也就是说匹配完“普通 location ”后,有的时候需要继续匹配“正则 location ”,有的时候则不需要继续匹配“正则 location ”。两种情况下,不需要继续匹配正则 location :( 1 )当普通 location 前面指定了“ ^~ ”,特别告诉 Nginx 本条普通 location 一旦匹配上,则不需要继续正则匹配;( 2 )当普通 location 恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则。

总结一句话:“正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果”

官方文档解释

REFER:  http://wiki.nginx.org/NginxHttpCoreModule#location

location

syntax: location [=|~|~*|^~|@] /uri/ { … }

default: no

context: server

This directive allows different configurations depending on the URI.

(译者注:1 、different configurations depending on the URI 说的就是语法格式:location [=|~|~*|^~|@] /uri/ { … } ,依据不同的前缀“= ”,“^~ ”,“~ ”,“~* ”和不带任何前缀的(因为[A] 表示可选,可以不要的),表达不同的含义, 简单的说尽管location 的/uri/ 配置一样,但前缀不一样,表达的是不同的指令含义。2 、查询字符串不在URI 范围内。例如:/films.htm?fid=123 的URI 是/films.htm 。)

It can be configured using both literal strings and regular expressions. To use regular expressions, you must use a prefix:

1.”~” for case sensitive matching

2.”~*” for case insensitive matching

译文:上文讲到location /uri/ 可通过使用不同的前缀,表达不同的含义。对这些不同前缀,分下类,就2 大类:正则location ,英文说法是location using regular expressions 和普通location ,英文说法是location using literal strings 。那么其中“~ ”和“~* ”前缀表示正则location ,“~ ”区分大小写,“~* ”不区分大小写;其他前缀(包括:“= ”,“^~ ”和“@ ”)和无任何前缀的都属于普通location 。

To determine which location directive matches a particular query, the literal strings are checked first.

译文:对于一个特定的 HTTP 请求( a particular query ), nginx 应该匹配哪个 location 块的指令呢(注意:我们在 nginx.conf 配置文件里面一般会定义多个 location 的)?匹配 规则是:先匹配普通location (再匹配正则表达式)。注意:官方文档这句话就明确说了,先普通location ,而不是有些同学的误区“先匹配正则location ”。

Literal strings match the beginning portion of the query – the most specific match will be used.

前面说了“普通location ”与“正则location ”之间的匹配规则是:先匹配普通location ,再匹配正则location 。那么,“普通location ”内部(普通location 与普通location )是如何匹配的呢?简单的说:最大前缀匹配。原文:1 、match the beginning portion of the query (说的是匹配URI 的前缀部分beginning portion ); 2 、the most specific match will be used (因为location 不是“严格匹配”,而是“前缀匹配”,就会产生一个HTTP 请求,可以“前缀匹配”到多个普通location ,例如:location /prefix/mid/ {} 和location /prefix/ {} ,对于HTTP 请求 /prefix/mid/t.html ,前缀匹配的话两个location 都满足,选哪个?原则是:the most specific match ,于是选的是location /prefix/mid/ {} )。

Afterwards, regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search.

这段话说了两层意思,第一层是:“Afterwards, regular expressions are checked ”, 意思是普通location 先匹配,而且选择了最大前缀匹配后,不能就停止后面的匹配,最大前缀匹配只是一个临时的结果,nginx 还需要继续检查正则location (但至于最终才能普通location 的最大前缀匹配,还是正则location 的匹配,截止当前的内容还没讲,但后面会讲)。第二层是“regular expressions are checked in the order defined in the configuration file. The first regular expression to match the query will stop the search. ”,意思是说“正则location ”与“正则location ”内部的匹配规则是:按照正则location 在配置文件中的物理顺序(编辑顺序)匹配的(这句话就说明location 并不是一定跟顺序无关,只是普通location 与顺序无关,正则location 还是与顺序有关的),并且只要匹配到一条正则location ,就不再考虑后面的(这与“普通location ”与“正则location ”之间的规则不一样,“普通location ”与“正则location ”之间的规则是:选择出“普通location ”的最大前缀匹配结果后,还需要继续搜索正则location )。

If no regular expression matches are found, the result from the literal string search is used.

这句话回答了“普通location ”的最大前缀匹配结果与继续搜索的“正则location ”匹配结果的决策关系。如果继续搜索的“正则location ”也有匹配上的,那么“正则location ”覆盖 “普通location ”的最大前缀匹配(因为有这个覆盖关系,所以造成有些同学以为正则location 先于普通location 执行的错误理解);但是如果“正则location ”没有能匹配上,那么就用“普通location ”的最大前缀匹配结果。

For case insensitive operating systems, like Mac OS X or Windows with Cygwin, literal string matching is done in a case insensitive way (0.7.7). However, comparison is limited to single-byte locale’s only.

Regular expression may contain captures (0.7.40), which can then be used in other directives.

It is possible to disable regular expression checks after literal string matching by using “^~” prefix. If the most specific match literal location has this prefix: regular expressions aren’t checked.

通常的规则是,匹配完了“普通location ”指令,还需要继续匹配“正则location ”,但是你也可以告诉Nginx :匹配到了“普通location ”后,不再需要继续匹配“正则location ”了,要做到这一点只要在“普通location ”前面加上“^~ ”符号(^ 表示“非”,~ 表示“正则”,字符意思是:不要继续匹配正则)。

By using the “=” prefix we define the exact match between request URI and location. When matched search stops immediately. E.g., if the request “/” occurs frequently, using “location = /” will speed up processing of this request a bit as search will stop after first comparison.

除了上文的“^~ ”可以阻止继续搜索正则location 外,你还可以加“= ”。那么如果“^~ ”和“= ”都能阻止继续搜索正则location 的话,那它们之间有什么区别呢?区别很简单,共同点是它们都能阻止继续搜索正则location ,不同点是“^~ ”依然遵守“最大前缀”匹配规则,然而“= ”不是“最大前缀”,而是必须是严格匹配(exact match )。

这里顺便讲下“location / {} ”和“location = / {} ”的区别,“location / {} ”遵守普通location 的最大前缀匹配,由于任何 URI 都必然以“/ ”根开头,所以对于一个URI ,如果有更specific 的匹配,那自然是选这个更specific 的,如果没有,“/ ”一定能为这个URI 垫背(至少能匹配到“/ ”),也就是说“location / {} ”有点默认配置的味道,其他更specific 的配置能覆盖overwrite 这个默认配置(这也是为什么我们总能看到location / {} 这个配置的一个很重要的原因)。而“location = / {} ”遵守的是“严格精确匹配exact match ”,也就是只能匹配 http://host:port/ 请求,同时会禁止继续搜索正则location 。因此如果我们只想对“GET / ”请求配置作用指令,那么我们可以选“location = / {} ”这样能减少正则location 的搜索,因此效率比“location / {}” 高(注:前提是我们的目的仅仅只想对“GET / ”起作用)。

On exact match with literal location without “=” or “^~” prefixes search is also immediately terminated.

前面我们说了,普通location 匹配完后,还会继续匹配正则location ;但是nginx 允许你阻止这种行为,方法很简单,只需要在普通location 前加“^~ ”或“= ”。但其实还有一种“隐含”的方式来阻止正则location 的搜索,这种隐含的方式就是:当“最大前缀”匹配恰好就是一个“严格精确(exact match )”匹配,照样会停止后面的搜索。原文字面意思是:只要遇到“精确匹配exact match ”,即使普通location 没有带“= ”或“^~ ”前缀,也一样会终止后面的匹配。

先举例解释下,后面例题会用实践告诉大家。假设当前配置是:location /exact/match/test.html { 配置指令块1} ,location /prefix/ { 配置指令块2} 和 location ~ \.html$ { 配置指令块3} ,如果我们请求 GET /prefix/index.html ,则会被匹配到指令块3 ,因为普通location /prefix/ 依据最大匹配原则能匹配当前请求,但是会被后面的正则location 覆盖;当请求GET /exact/match/test.html ,会匹配到指令块1 ,因为这个是普通location 的exact match ,会禁止继续搜索正则location 。

To summarize, the order in which directives are checked is as follows:

1.Directives with the “=” prefix that match the query exactly. If found, searching stops.

2.All remaining directives with conventional strings. If this match used the “^~” prefix, searching stops.

3.Regular expressions, in the order they are defined in the configuration file.

4.If #3 yielded a match, that result is used. Otherwise, the match from #2 is used.

这个顺序没必要再过多解释了。但我想用自己的话概括下上面的意思“正则 location 匹配让步普通 location 的严格精确匹配结果;但覆盖普通 location 的最大前缀匹配结果”。

It is important to know that nginx does the comparison against decoded URIs. For example, if you wish to match “/images/%20/test”, then you must use “/images/ /test” to determine the location.

在浏览器上显示的URL 一般都会进行URLEncode ,例如“空格”会被编码为%20 ,但是Nginx 的URL 的匹配都是针对URLDecode 之后的。也就是说,如果你要匹配“/images/%20/test ”,你写location 的时候匹配目标应该是:“/images/ /test ”。

Example:

location   = / {

# matches the query / only.

[ configuration A ]

}

location   / {

# matches any query, since all queries begin with /, but regular

# expressions and any longer conventional blocks will be

# matched first.

[ configuration B ]

}

location ^~ /images/ {

# matches any query beginning with /images/ and halts searching,

# so regular expressions will not be checked.

[ configuration C ]

}

location ~* \.(gif|jpg|jpeg)$ {

# matches any request ending in gif, jpg, or jpeg. However, all

# requests to the /images/ directory will be handled by

# Configuration C.

[ configuration D ]

}

上述这4 个location 的配置,没什么好解释的,唯一需要说明的是location / {[configuration B]} ,原文的注释严格来说是错误的,但我相信原文作者是了解规则的,只是文字描述上简化了下,但这个简化容易给读者造成“误解:先检查正则location ,再检查普通location ”。原文:“matches any query, since all queries begin with /, but regular expressions and any longer conventional blocks will be matched first. ”大意是说:“location / {} 能够匹配所有HTTP 请求,因为任何HTTP 请求都必然是以‘/ ’开始的(这半句没有错误)。但是,正则location 和其他任何比‘/ ’更长的普通location (location / {} 是普通location 里面最短的,因此其他任何普通location 都会比它更长,当然location = / {} 和 location ^~ / {} 是一样长的)会优先匹配(matched first )。” 原文作者说“ but regular expressions will be matched first. ”应该只是想说正则 location 会覆盖这里的 location / {} ,但依然是普通 location / {} 先于正则 location 匹配,接着再正则 location 匹配;但其他更长的普通 location ( any longer conventional blocks )的确会先于 location / {} 匹配。

Example requests:

/ -> configuration A

/documents/document.html -> configuration B

/images/1.gif -> configuration C

/documents/1.jpg -> configuration D

Note that you could define these 4 configurations in any order and the results would remain the same.

需要提醒下:这里说“in any order ”和“… remain the same ”是因为上面只有一个正则location 。文章前面已经说了正则location 的匹配是跟编辑顺序有关系的。

While nested locations are allowed by the configuration file parser, their use is discouraged and may produce unexpected results.

实际上 nginx 的配置文件解析程序是允许 location 嵌套定义的( location / { location /uri/ {} } )。但是我们平时却很少看见这样的配置,那是因为 nginx 官方并不建议大家这么做,因为这样会导致很多意想不到的后果。

The prefix “@” specifies a named location. Such locations are not used during normal processing of requests, they are intended only to process internally redirected requests (see error_page , try_files ).

文章开始说了location 的语法中,可以有“= ”,“^~ ”,“~ ”和“~* ”前缀,或者干脆没有任何前缀,还有“@ ”前缀,但是后面的分析我们始终没有谈到“@ ”前缀。文章最后点内容,介绍了“@”的用途:“@ ”是用来定义“Named Location ”的(你可以理解为独立于“普通location (location using literal strings )”和“正则location (location using regular expressions )”之外的第三种类型),这种“Named Location ”不是用来处理普通的HTTP 请求的,它是专门用来处理“内部重定向(internally redirected )”请求的。注意:这里说的“内部重定向(internally redirected )”或许说成“forward ”会好点,以为内internally redirected 是不需要跟浏览器交互的,纯粹是服务端的一个转发行为。

location 实例练习

Nginx 的语法形式是: location [=|~|~*|^~|@] /uri/ { … } ,意思是可以以“ = ”或“ ~* ”或“ ~ ”或“ ^~ ”或“ @ ”符号为前缀,当然也可以没有前缀(因为 [A] 是表示可选的 A ; A|B 表示 A 和 B 选一个),紧接着是 /uri/ ,再接着是 {…} 指令块,整个意思是对于满足这样条件的 /uri/ 适用指令块 {…} 的指令。

上述各种 location 可分两大类,分别是:“普通 location ”,官方英文说法是 location using   literal strings 和“正则 location ”,英文说法是 location using regular expressions 。其中“普通 location ”是以“ = ”或“ ^~ ”为前缀或者没有任何前缀的 /uri/ ;“正则 location ”是以“ ~ ”或“ ~* ”为前缀的 /uri/ 。

那么,当我们在一个 server 上下文编写了多个 location 的时候, Nginx 对于一个 HTTP 请求,是如何匹配到一个 location 做处理呢?用一句话简单概括 Nginx 的 location 匹配规则是:“正则 location ”让步 “普通 location ”的严格精确匹配结果;但覆盖 “普通 location ”的最大前缀匹配结果。理解这句话,我想通过下面的实例来说明。

#1 先普通 location ,再正则 location

周边不少童鞋告诉我, nginx 是“先匹配正则 location 再匹配普通 location ”,其实这是一个误区, nginx 其实是“先匹配普通 location ,再匹配正则 location ”,但是普通 location 的匹配结果又分两种:一种是“严格精确匹配”,官方英文说法是“ exact match ”;另一种是“最大前缀匹配”,官方英文说法是“ Literal strings match the beginning portion of the query – the most specific match will be used. ”。我们做个实验:

 

例题 1 :假设 nginx 的配置如下

server {

listen       9090;

server_name  localhost;

location / {

root   html;

index  index.html index.htm;

deny all;

}

location ~ \.html$ {

allow all;

}

}

附录 nginx 的目录结构是: nginx->html->index.html

上述配置的意思是: location / {… deny all;} 普通 location 以“ / ”开始的 URI 请求(注意任何 HTTP 请求都必然以“ / ”开始,所以“ / ”的意思是所有的请求都能被匹配上),都拒绝访问; location ~\.html$ {allow all;} 正则 location 以 .html 结尾的 URI 请求,都允许访问。

测试结果:

[root@web108 ~]# curl http://localhost:9090/

<html>

<head><title>403 Forbidden</title></head>

<body bgcolor=”white”>

<center><h1>403 Forbidden</h1></center>

<hr><center>nginx/1.1.0</center>

</body>

</html>

[root@web108 ~]# curl http://localhost:9090/index.html

<html>

<head>

<title>Welcome to nginx!</title>

</head>

<body bgcolor=”white” text=”black”>

<center><h1>Welcome to nginx!</h1></center>

</body>

</html>

[root@web108 ~]# curl http://localhost:9090/index_notfound.html

<html>

<head><title>404 Not Found</title></head>

<body bgcolor=”white”>

<center><h1>404 Not Found</h1></center>

<hr><center>nginx/1.1.0</center>

</body>

</html>

[root@web108 ~]#

测试结果如下:

URI 请求 HTTP 响应
curl http://localhost:9090/ 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx!
curl http://localhost:9090/index_notfound.html 404 Not Found

curl http://localhost:9090/ 的结果是“ 403 Forbidden ”,说明被匹配到“ location / {..deny all;} ”了,原因很简单 HTTP 请求 GET / 被“严格精确”匹配到了普通 location / {} ,则会停止搜索正则 location ;

curl http://localhost:9090/index.html 结果是“ Welcome to nginx! ”,说明没有被“ location / {…deny all;} ”匹配,否则会 403 Forbidden ,但 /index.html 的确也是以“ / ”开头的,只不过此时的普通 location / 的匹配结果是“最大前缀”匹配,所以 Nginx 会继续搜索正则 location , location ~ \.html$ 表达了以 .html 结尾的都 allow all; 于是接着就访问到了实际存在的 index.html 页面。

curl http://localhost:9090/index_notfound.html   同样的道理先匹配 location / {} ,但属于“普通 location 的最大前缀匹配”,于是后面被“正则 location ” location ~ \.html$ {} 覆盖了,最终 allow all ; 但的确目录下不存在 index_notfound.html 页面,于是 404 Not Found 。

如果此时我们访问 http://localhost:9090/index.txt 会是什么结果呢?显然是 deny all ;因为先匹配上了 location / {..deny all;} 尽管属于“普通 location ”的最大前缀匹配结果,继续搜索正则 location ,但是 /index.txt 不是以 .html 结尾的,正则 location 失败,最终采纳普通 location 的最大前缀匹配结果,于是 deny all 了。

[root@web108 ~]# curl http://localhost:9090/index.txt

<html>

<head><title>403 Forbidden</title></head>

<body bgcolor=”white”>

<center><h1>403 Forbidden</h1></center>

<hr><center>nginx/1.1.0</center>

</body>

</html>

[root@web108 ~]#

#2 普通 location 的“隐式”严格匹配

例题 2 :我们在例题 1 的基础上增加精确配置

server {

listen       9090;

server_name  localhost;

location /exact/match.html {

allow all;

}

location / {

root   html;

index  index.html index.htm;

deny all;

}

location ~ \.html$ {

allow all;

}

}

测试请求:

[root@web108 ~]# curl http://localhost:9090/exact/match.html

<html>

<head><title>404 Not Found</title></head>

<body bgcolor=”white”>

<center><h1>404 Not Found</h1></center>

<hr><center>nginx/1.1.0</center>

</body>

</html>

[root@web108 ~]#

结果进一步验证了“普通 location ”的“严格精确”匹配会终止对正则 location 的搜索。这里我们小结下“普通 location ”与“正则 location ”的匹配规则:先匹配普通 location ,再匹配正则 location ,但是如果普通 location 的匹配结果恰好是“严格精确( exact match )”的,则 nginx 不再尝试后面的正则 location ;如果普通 location 的匹配结果是“最大前缀”,则正则 location 的匹配覆盖普通 location 的匹配。也就是前面说的“正则 location 让步普通 location 的严格精确匹配结果,但覆盖普通 location 的最大前缀匹配结果”。

#3 普通 location 的“显式”严格匹配和“ ^~ 前缀

上面我们演示的普通 location 都是不加任何前缀的,其实普通 location 也可以加前缀:“ ^~ ”和“ = ”。其中“ ^~ ”的意思是“非正则,不需要继续正则匹配”,也就是通常我们的普通 location ,还会继续搜索正则 location (恰好严格精确匹配除外),但是 nginx 很人性化允许配置人员告诉 nginx 某条普通 location ,无论最大前缀匹配,还是严格精确匹配都终止继续搜索正则 location ;而“ = ”则表达的是普通 location 不允许“最大前缀”匹配结果,必须严格等于,严格精确匹配。

例题 3 :“ ^~ ”前缀的使用

server {

listen       9090;

server_name  localhost;

location /exact/match.html {

allow all;

}

location ^~ / {

root   html;

index  index.html index.htm;

deny all;

}

location ~ \.html$ {

allow all;

}

}

把例题 2 中的 location / {} 修改成 location ^~ / {} ,再看看测试结果:

URI 请求 修改前 修改后
curl http://localhost:9090/ 403 Forbidden 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx! 403 Forbidden
curl http://localhost:9090/index_notfound.html 404 Not Found 403 Forbidden
curl http://localhost:9090/exact/match.html 404 Not Found 404 Not Found

除了 GET /exact/match.html 是 404 Not Found ,其余都是 403 Forbidden ,原因很简单所有请求都是以“ / ”开头,所以所有请求都能匹配上“ / ”普通 location ,但普通 location 的匹配原则是“最大前缀”,所以只有 /exact/match.html 匹配到 location /exact/match.html {allow all;} ,其余都 location ^~ / {deny all;} 并终止正则搜索。

例题 4 :“ = ”前缀的使用

server {

listen       9090;

server_name  localhost;

location /exact/match.html {

allow all;

}

location = / {

root   html;

index  index.html index.htm;

deny all;

}

location ~ \.html$ {

allow all;

}

}

例题 4 相对例题 2 把 location / {} 修改成了 location = / {} ,再次测试结果:

URI 请求 修改前 修改后
curl http://localhost:9090/ 403 Forbidden 403 Forbidden
curl http://localhost:9090/index.html Welcome to nginx! Welcome to nginx!
curl http://localhost:9090/index_notfound.html 404 Not Found 404 Not Found
curl http://localhost:9090/exact/match.html 404 Not Found 404 Not Found
curl http://localhost:9090/test.jsp 403 Forbidden 404 Not Found

最能说明问题的测试是 GET /test.jsp ,实际上 /test.jsp 没有匹配正则 location ( location ~\.html$ ),也没有匹配 location = / {} ,如果按照 location / {} 的话,会“最大前缀”匹配到普通 location / {} ,结果是 deny all 。

#4 正则 location 与编辑顺序

location 的指令与编辑顺序无关,这句话不全对。对于普通 location 指令,匹配规则是:最大前缀匹配(与顺序无关),如果恰好是严格精确匹配结果或者加有前缀“ ^~ ”或“ = ”(符号“ = ”只能严格匹配,不能前缀匹配),则停止搜索正则 location ;但对于正则 location 的匹配规则是:按编辑顺序逐个匹配(与顺序有关),只要匹配上,就立即停止后面的搜索。

配置 3.1

server {

listen       9090;

server_name  localhost;

location ~ \.html$ {

allow all;

}

location ~ ^/prefix/.*\.html$ {

deny all;

}

}

配置 3.2

server {

listen       9090;

server_name  localhost;

location ~ ^/prefix/.*\.html$ {

deny all;

}

location ~ \.html$ {

allow all;

}

}

测试结果:

URI 请求 配置 3.1 配置 3.2
curl http://localhost:9090/regextest.html 404 Not Found 404 Not Found
curl http://localhost:9090/prefix/regextest.html 404 Not Found 403 Forbidden

解释:

Location ~ ^/prefix/.*\.html$ {deny all;} 表示正则 location 对于以 /prefix/ 开头, .html 结尾的所有 URI 请求,都拒绝访问;   location ~\.html${allow all;} 表示正则 location 对于以 .html 结尾的 URI 请求,都允许访问。 实际上, prefix 的是 ~\.html$ 的子集。

在“配置 3.1 ”下,两个请求都匹配上 location ~\.html$ {allow all;} ,并且停止后面的搜索,于是都允许访问, 404 Not Found ;在“配置 3.2 ”下, /regextest.html 无法匹配 prefix ,于是继续搜索 ~\.html$ ,允许访问,于是 404 Not Found ;然而 /prefix/regextest.html 匹配到 prefix ,于是 deny all , 403 Forbidden 。

配置 3.3

server {

listen       9090;

server_name  localhost;

location  /prefix/ {

deny all;

}

location  /prefix/mid/ {

allow all;

}

}

配置 3.4

server {

listen       9090;

server_name  localhost;

location  /prefix/mid/ {

allow all;

}

location  /prefix/ {

deny all;

}

}

测试结果:

URI 请求 配置 3.1 配置 3.2
curl http://localhost:9090/prefix/t.html 403 Forbidden 403 Forbidden
curl http://localhost:9090/prefix/mid/t.html 404 Not Found 404 Not Found

测试结果表明:普通 location 的匹配规则是“最大前缀”匹配,而且与编辑顺序无关。

#5 “@ 前缀 Named Location 使用

REFER:  http://wiki.nginx.org/HttpCoreModule#error_page

假设配置如下:

server {

listen       9090;

server_name  localhost;

location  / {

root   html;

index  index.html index.htm;

allow all;

}

#error_page 404 http://www.baidu.com # 直接这样是不允许的

error_page 404 = @fallback;

location @fallback {

proxy_pass http://www.baidu.com;

}

}

上述配置文件的意思是:如果请求的 URI 存在,则本 nginx 返回对应的页面;如果不存在,则把请求代理到 baidu.com 上去做个弥补(注: nginx 当发现 URI 对应的页面不存在, HTTP_StatusCode 会是 404 ,此时 error_page 404 指令能捕获它)。

测试一:

[root@web108 ~]# curl http://localhost:9090/nofound.html -i

HTTP/1.1 302 Found

Server: nginx/1.1.0

Date: Sat, 06 Aug 2011 08:17:21 GMT

Content-Type: text/html; charset=iso-8859-1

Location: http://localhost:9090/search/error.html

Connection: keep-alive

Cache-Control: max-age=86400

Expires: Sun, 07 Aug 2011 08:17:21 GMT

Content-Length: 222

 

<!DOCTYPE HTML PUBLIC “-//IETF//DTD HTML 2.0//EN”>

<html><head>

<title>302 Found</title>

</head><body>

<h1>Found</h1>

<p>The document has moved <a href=”http://www.baidu.com/search/error.html“>here</a>.</p>

</body></html>

[root@web108 ~]#

当我们 GET /nofound.html 发送给本 nginx , nginx 找不到对应的页面,于是 error_page 404 = @fallback ,请求被代理到 http://www.baidu.com ,于是 nginx 给 http://www.baidu.com 发送了 GET /nofound.html ,但 /nofound.html 页面在百度也不存在,百度 302 跳转到错误页。

直接访问 http://www.baidu.com/nofound.html 结果:

[root@web108 ~]# curl http://www.baidu.com/nofound.html -i

HTTP/1.1 302 Found

Date: Sat, 06 Aug 2011 08:20:05 GMT

Server: Apache

Location: http://www.baidu.com/search/error.html

Cache-Control: max-age=86400

Expires: Sun, 07 Aug 2011 08:20:05 GMT

Content-Length: 222

Connection: Keep-Alive

Content-Type: text/html; charset=iso-8859-1

 

<!DOCTYPE HTML PUBLIC “-//IETF//DTD HTML 2.0//EN”>

<html><head>

<title>302 Found</title>

</head><body>

<h1>Found</h1>

<p>The document has moved <a href=”http://www.baidu.com/search/error.html“>here</a>.</p>

</body></html>

[root@web108 ~]#

 

测试二:访问一个 nginx 不存在,但 baidu 存在的页面

[root@web108 ~]# curl http://www.baidu.com/duty/ -i

HTTP/1.1 200 OK

Date: Sat, 06 Aug 2011 08:21:56 GMT

Server: Apache

P3P: CP=” OTI DSP COR IVA OUR IND COM ”

P3P: CP=” OTI DSP COR IVA OUR IND COM ”

Set-Cookie: BAIDUID=5C5D2B2FD083737A0C88CA7075A6601A:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Set-Cookie: BAIDUID=5C5D2B2FD083737A2337F78F909CCB90:FG=1; expires=Sun, 05-Aug-12 08:21:56 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT

ETag: “d66-49913b8efe340”

Accept-Ranges: bytes

Content-Length: 3430

Cache-Control: max-age=86400

Expires: Sun, 07 Aug 2011 08:21:56 GMT

Vary: Accept-Encoding,User-Agent

Connection: Keep-Alive

Content-Type: text/html

 

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”

http://www.w3.org/TR/html4/loose.dtd“>

。。。。

</body>

</html>

显示,的确百度这个页面是存在的。

[root@web108 ~]# curl http://localhost:9090/duty/ -i

HTTP/1.1 200 OK

Server: nginx/1.1.0

Date: Sat, 06 Aug 2011 08:23:23 GMT

Content-Type: text/html

Connection: keep-alive

P3P: CP=” OTI DSP COR IVA OUR IND COM ”

P3P: CP=” OTI DSP COR IVA OUR IND COM ”

Set-Cookie: BAIDUID=8FEF0A3A2C31D277DCB4CC5F80B7F457:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Set-Cookie: BAIDUID=8FEF0A3A2C31D277B1F87691AFFD7440:FG=1; expires=Sun, 05-Aug-12 08:23:23 GMT; max-age=31536000; path=/; domain=.baidu.com; version=1

Last-Modified: Wed, 05 Jan 2011 06:44:53 GMT

ETag: “d66-49913b8efe340”

Accept-Ranges: bytes

Content-Length: 3430

Cache-Control: max-age=86400

Expires: Sun, 07 Aug 2011 08:23:23 GMT

Vary: Accept-Encoding,User-Agent

 

<!DOCTYPE HTML PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN”

http://www.w3.org/TR/html4/loose.dtd“>

<html>

。。。

</body>

</html>

当 curl http://localhost:9090/duty/ -i 时, nginx 没找到对应的页面,于是 error_page = @fallback ,把请求代理到 baidu.com 。注意这里的 error_page = @fallback 不是靠重定向实现的,而是所说的“ internally redirected ( forward )”。

数据库关系模式的理解

第一范式(1NF):

数据库系统概论引用:

  1. 列是同质的(Homogeneous),即每一列中的分量是同一类型的数据,来自同一个域.
  2. 不同的列可出自同一个域,称其中的每一列为一个属性,不同的属性要给予不同的属性名.
  3. 列的顺序无所谓,即列的次序可以任意交换.
  4. 任意两个元组不能完全相同.
  5. 行的顺序无所谓,即行的次序可以任意交换.
  6. 分量必须取原子值,即每一个分量都必须是不可分的数据项.

所谓第一范式(1NF)是指在关系模型中,对域添加的一个规范要求,所有的域都应该是原子性的,即数据库表的每一列都是不可分割的原子数据项,而不能是集合,数组,记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中的每个域值只能是实体的一个属性或一个属性的一部分。简而言之,第一范式就是无重复的域。
说明:在任何一个关系数据库中,第一范式(1NF)是对关系模式的设计基本要求,一般设计中都必须满足第一范式(1NF)。不过有些关系模型中突破了1NF的限制,这种称为非1NF的关系模型。换句话说,是否必须满足1NF的最低要求,主要依赖于所使用的关系模型。

第二范式(2NF):

数据库系统概论引用:
若R<U,F>∈1NF,并且每一个非主属性都完全函数依赖于码,则R<U,F>∈2NF。

在1NF的基础上,非码属性必须完全依赖于码[在1NF基础上消除非主属性对主码的部分函数依赖]
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或记录必须可以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。例如在员工表中的身份证号码即可实现每个一员工的区分,该身份证号码即为候选键,任何一个候选键都可以被选作主键。在找不到候选键时,可额外增加属性以实现区分,如果在员工关系中,没有对其身份证号进行存储,而姓名可能会在数据库运行的某个时间重复,无法区分出实体时,设计辟如ID等不重复的编号以实现区分,被添加的编号或ID选作主键。(该主键的添加是在ER设计时添加,不是建库时随意添加)
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。

第三范式(3NF):

数据库系统概论引用:
传递依赖:在R<U,F>中,如果X → Y,(Y ⊈ X),Y !→ X,Y → Z,则称Z对X传递函数依赖.

3NF:关系模式R<U,F>中若不存在这样的码X,属性组Y及非主属性Z(Z⊈Y), 使得X → Y,Y → Z成立,Y !→ X,则称R<U,F>∈3NF。

简单的说,就是没有传递的第二范式。

第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性,也就是在满足2NF的基础上,任何非主属性不得传递依赖于主属性。

数据库系统概论引用:
设关系模式R<U,F>∈1NF,若X → Y且Y ⊈ X时X必含码,则R<U,F>∈BCNF。

在1NF基础上,任何非主属性不能对主键子集依赖[在3NF基础上消除对主码子集的依赖]
巴斯-科德范式(BCNF)是第三范式(3NF)的一个子集,即满足巴斯-科德范式(BCNF)必须满足第三范式(3NF)。通常情况下,巴斯-科德范式被认为没有新的设计规范加入,只是对第二范式与第三范式中设计规范要求更强,因而被认为是修正第三范式,也就是说,它事实上是对第三范式的修正,使数据库冗余度更小。这也是BCNF不被称为第四范式的原因。某些书上,根据范式要求的递增性将其称之为第四范式是不规范,也是更让人不容易理解的地方。而真正的第四范式,则是在设计规范中添加了对多值及依赖的要求。
对于BCNF,在主码的任何一个真子集都不能决定于主属性。关系中U主码,若U中的任何一个真子集X都不能决定于主属性Y,则该设计规范属性BCNF。例如:在关系R中,U为主码,A属性是主码中的一个属性,若存在A->Y,Y为主属性,则该关系不属于BCNF。
一般关系型数据库设计中,达到BCNF就可以了!