赞
踩
本博客主要介绍HTTPS协议的详细知识,HTTPS协议基于TLS协议,当前TLS协议的主要版本是1.2, 最新版本是1.3。
TLS协议的主要基础设施是各种加密算法,以及用于验证的公共CA机构以及客户端验证证书的机制。
本博客参考以下链接:
加密传输的两种方式:PSK或者Certificate(即公共证书)。
加密的作用:加密前的数据称为明文(plaintext),加密的作用是使得加密后的数据看上去符合随机分布。
公共证书加密,是非对称加密。
证书会分为两个部分:公钥和私钥。公钥加密的数据,只有私钥能够解密;私钥加密的数据,只有公钥能够解密。
通过将公钥释放出去,而保留私钥,能够确保:1.只有一个人能够使用私钥加密数据,并用公钥解密出来;因此,接收方如果成功使用公钥解密数据,意味着它只能来自于唯一的发送方。2.能够有多个发送方使用公钥加密数据,但只有一个接收方能够解密数据;因此,发送方能够确保数据只被目标接受者解密。
RSA和Diffie-Hellman使用非对称加密。
在通常的HTTPs服务器中,服务器持有私钥,访问者持有公钥。
即证书颁发机构认证证书的使用方式。
证书机构分为三级:IPRA=根证书颁发机构,PCA=证书颁发机构,CA=某个域的证书颁发机构
CA证书可分为三类:交叉证书,自颁发证书和自签名证书。
交叉证书:颁发者和主体不同,描述的是CA机构之间的相互信任关系
自颁发证书:颁发者和主体相同,用于支持策略变化
自签名证书:颁发者和主体相同,且该证书的数字签名可以被绑定在该证书上公钥(pubkey)验证
每个证书都有时间限制,当证书的有效性因为外部条件变化而失效时,证书即失效。比如名称改变。
每个CA机构都会定期生成一个证书撤回列表(CRL,包含证书的流水号),该列表可被公开读取。当使用者需要验证某个证书时,需要检查该证书是否在最新的CRL列表中,如果存在,则该证书失效。
加密组件,即将多种加密方式的组合。
命名方式:TLS_{交换密钥的方法}_{加密算法}_{消息摘要算法}
映射表:
CipherSuite | Key Exchange | Cipher | MAC |
---|---|---|---|
TLS_NULL_WITH_NULL_NULL | NULL | NULL | NULL |
TLS_RSA_WITH_NULL_MD5 | RSA | NULL | MD5 |
TLS_PSK_WITH_RC4_128_SHA | PSK | RC4_128 | SHA |
TLS_PSK_WITH_3DES_EDE_CBC_SHA | PSK | 3DES_EDE_CBC | SHA |
TLS_PSK_WITH_AES_128_CBC_SHA | PSK | AES_128_CBC | SHA |
TLS_PSK_WITH_AES_256_CBC_SHA | PSK | AES_256_CBC | SHA |
TLS_DHE_PSK_WITH_RC4_128_SHA | DHE_PSK | RC4_128 | SHA |
TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA | DHE_PSK | 3DES_EDE_CBC | SHA |
TLS_DHE_PSK_WITH_AES_128_CBC_SHA | DHE_PSK | AES_128_CBC | SHA |
TLS_DHE_PSK_WITH_AES_256_CBC_SHA | DHE_PSK | AES_256_CBC | SHA |
TLS_RSA_PSK_WITH_RC4_128_SHA | RSA_PSK | RC4_128 | SHA |
TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA | RSA_PSK | 3DES_EDE_CBC | SHA |
TLS_RSA_PSK_WITH_AES_128_CBC_SHA | RSA_PSK | AES_128_CBC | SHA |
TLS_RSA_PSK_WITH_AES_256_CBC_SHA | RSA_PSK | AES_256_CBC | SHA |
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 | DHE_RSA | AES_128_CBC | SHA256 |
其中,TLS_RSA_WITH_AES_128_CBC_SHA
是强制支持的。
实践中使用比较多key交换算法是ECDHE_RSA
,这种算法是Diffie-Hellman算法的改进,安全性有所提升,同时能够减少能耗。
一个简单的SSL客户端可以选择支持最简单的AES加密算法(Java名称: AES/ECB/PKCS5Padding
).
加密算法类型:
Cipher | Key Type | IV Material | Block Size | Size |
---|---|---|---|---|
NULL | Stream | 0 | 0 | N/A |
RC4_128 | Stream | 16 | 0 | N/A |
3DES_EDE_CBC | Block | 24 | 8 | 8 |
AES_128_CBC | Block | 16 | 16 | 16 |
AES_256_CBC | Block | 32 | 16 | 16 |
注:IV = Initializing Vector |
MAC算法:
MAC | Algorithm | mac_length | mac_key_length |
---|---|---|---|
NULL | N/A | 0 | 0 |
MD5 | HMAC-MD5 | 16 | 16 |
SHA | HMAC-SHA1 | 20 | 20 |
SHA256 | HMAC-SHA256 | 32 | 32 |
即对称加密密钥,旧称:Symmetric Key. 全称:Pre-Shared Key.优点:1.依赖当前支持的cipher suite 2.无需公共证书。
RFC文档:https://tools.ietf.org/html/rfc4279
通常用在小型私有网络中,PSK的存储方式通常由:密码明文,密钥(指纹),16进制字符串
TLSv1.2的消息类型包括:握手协议,alert协议,加密方式变更协议,应用层数据协议。
每一个TLS协议都工作在一个上下文中,该上下文定义了:1.压缩算法 2.加密算法 3.MAC(消息认证code)算法
为了对数据进行加密,通信双方首先需要约定好加密算法和加密使用的密钥(称为master key)。
由于直接传输密钥不安全,所以需要约定好交换key的算法,比如:RSA或者Diffie-Hellman。
TLS协议使用TCP协议传递数据,每个TCP数据报可以包含多个TLS Record结构体。
ClientHello是客户端向服务器发送的第一个消息。它包含:
PRF
;服务器需要从列表中选择一个算法,如果没有,则服务器响应failure alert
然后关闭连接;当客户端发出ClientHello之后,它等待响应ServerHello。
ServerHello是服务器向客户端发送的响应ClientHello的消息,包含:
客户端使用siguature_algorithms
(仅客户端使用该扩展)来指示服务器应当使用什么算法来作为电子签名算法/摘要。因为并非所有的hash算法和加密算法都是兼容的,因此它们需要成对出现。
如果客户端使用这个扩展,则服务器必须从这个列表中选择对应的算法而不能忽略。如果客户端未使用这个扩展,则服务器应当具备下面的行为:
如果服务器和客户端协定好了使用某个密钥交换方法,如果该方法需要证书来校验,则该消息必须在ServerHello之后由服务器发送。该消息向客户端发送服务器的证书链。
所谓的证书链,是指从第一个证书开始,后面一个证书需要能够证明前面一个证书的有效性;最后一个证书是根证书。
server_name
和trusted_ca_keys
用于寻找证书signature_algorithms
扩展中的一个算法来加密注:TLSv1.2使用的证书结构是X.509v3
,参考:
该消息需要在Server Certificate消息发送之后发送。如果是匿名协商(anonymous),则需要在ServerHello之后发送。
注意,该消息仅当服务器发送的Server Certificate不足以和客户端进行密钥交换时才需要。意味着使用下面的密钥交换方法需要发送该消息:DHE_DSS,DHE_RSA,DH_anon.而下面的方法则无需也不能发送该消息:RSA, DH_DSS, DH_RSA.
该消息包括公钥,如Diffie-Hellman的公钥,或者其他算法的公钥。
不同的密钥交换算法需要不同的参数,参见本节附录 > 密钥交换参数
.
master_secret = PRF(pre_master_secret, "master secret",
ClientHello.random + ServerHello.random)[0..47];
最终结果是48字节。
其中,PRF
函数
pre_master_secret
取决于所使用的交换算法,下面分RSA和Diffie-Hellman进行介绍。
客户端生成一个48字节的pre_master_secret
,使用服务器提供的公钥加密后传回服务器,双方基于这个pre_master_secret
计算master_secret
.
使用参数中协定的Z
作为pre_master_secret
,使用之前,Z
所有开头的0应当被截断。
一个非匿名的服务器可以向客户端请求证书(也可以不请求,该请求时可选的)。如果需要请求,则要在Server Key Exchange或者Server Certificate之后请求。
当服务器响应完ClientHello之后,并且发送必要的信息之后,发送该消息然后等待客户端的响应。
该消息的结构体为空,通过Handshake type区分。
仅当服务器发送了Certificate Request之后,注意,该消息需要在收到ServerHelloDone之后再发送。
消息体同ServerCertificate
在收到ServerHelloDone之后发送。
如果使用RSA交换,则参数中包括服务器公钥加密后的pre_master_secret
;如果使用Diffie-Hellman交换,则参数中包含用于生成pre_master_secret
的Diffie-Hellman公钥
。
交换算法 | 结构 |
---|---|
rsa | 服务器公钥加密后的pre_master_secret ,结构: { client_version(2字节), random(46字节)} |
dhe_dss,dhe_rsa,dh_dss,dh_rsa,dh_anon | Diffie-Hellman算法的公钥,用于计算pre_master_secret |
通知另一方后续的数据将会使用已经协定好的加密方式和密钥进行加密,对方也需要使用同样的加密方式以及对应密钥解密数据。
该消息的结构体包含单字节常量1。
当一方发送Finished消息并且从对方收到Finished消息并验证之后,可以开始发送应用层数据。
该消息的结构包含一个12字节长的校验数据,校验数据生成:PRF(master_secret, label, Hash(handshake_messages))
如果是客户端发送的,则label为:“client finished”;如果是服务器发送的,则label为:“server finished”
handshake_messages
是除了所以已交换的TLS Record结构体除头部以外的Handshake结构体的拼接(不包括HelloRequest和Finished),双方分别计算以进行验证。
格式:PRF(master_secret, label, Hash(handshake_messages))
使用curl测试,https://www.baidu.com作为测试目标;
目前客户端的curl能够使用TLSv1.3发起ClientHello,但是baidu响应的协议是TLSv1.2;如下图所示:
本节内容包含如何从TLSv1.2(握手协议明文)的消息中提取出加密HTTP请求的算法、公钥以及相关参数。
计算签名的规则: ServerKeyExchange.signature = rsa(private_key, sha256( CientHello.random + ServerHello.random + ServerKeyExchange.params)
其中,params为去掉签名算法,签名长度和签名之外的数据。rsa为签名认证算法,sha256为hash算法,它们由ServerKeyExchange.signatureHashAndAlgorithm指定.
在Wireshark中,分别取出上面规则中涉及的数据,然后使用下面的命令生成签名,验证是否与消息体中的签名一致:
openssl dgst -sha256 -sign private.key data.bin -out signature
RSAPrivateKey ::= SEQUENCE {
version Version,
modulus INTEGER, -- n
publicExponent INTEGER, -- e
privateExponent INTEGER, -- d
prime1 INTEGER, -- p
prime2 INTEGER, -- q
exponent1 INTEGER, -- d mod (p-1)
exponent2 INTEGER, -- d mod (q-1)
coefficient INTEGER, -- (inverse of q) mod p
otherPrimeInfos OtherPrimeInfos OPTIONAL
}
从该密钥结构中能够同时提取公钥和私钥。因为其中包含了privateExponent。
注意,这里只是给出了私钥的结构,私钥不会被交换。
Certificate ::= SEQUENCE { tbsCertificate TBSCertificate, signatureAlgorithm AlgorithmIdentifier, signatureValue BIT STRING } TBSCertificate ::= SEQUENCE { version [0] EXPLICIT Version DEFAULT v1, serialNumber CertificateSerialNumber, signature AlgorithmIdentifier, issuer Name, validity Validity, subject Name, subjectPublicKeyInfo SubjectPublicKeyInfo, issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL, -- If present, version MUST be v2 or v3 extensions [3] EXPLICIT Extensions OPTIONAL -- If present, version MUST be v3 }
其中值得注意的是issuer(颁发者)和subject(主体名称)两个字段。
参考:https://stackoverflow.com/questions/39660181/understanding-ssl-tls-certificates-structure
.pem文件时PEM编码的,以-----BEGIN CERTIFICATE-----开头。因此pem文件的内容可能是key,证书,证书链;
.crt和.cert是证书文件
.csr是pem编码的,它是证书签名请求。用于向CA申请证书
所谓的PEM文件最初是用在EMAIL中的。PEM文件时BASE64编码的DER文件,并且以 —BEING XXX—和—END XXX—标记开始和结束,以及文件本身的类型。
算法 | 参数 |
---|---|
Diffie-Hellman 匿名 | p,g,Ys公钥 |
dhe_rsa, dhe_dss | p,g,Ys公钥, 摘要(client_random, server_random,p,g,Ys公钥) |
rsa,dh_dss,dh_rsa | - |
注:非匿名的交换需要摘要。
根证书具有两个特点,1.自身是证书链的起点,没有其他CA证书再能够证明它们的可信,它们是预设的受信任的证书;2.根证书能够用于颁发其他证书,从而其他证书也能够被信任。根证书都是自签名的证书。所谓的自签名,具体到证书细节上,就是证书的颁发者和证书的主体相同。
通常,用户可以创建一个自签名的证书,然后将其加入到系统的受信任的根证书中,然后使用此证书来颁发其他的证书。
根证书的有效期通常都比较长,比如十年。通常,根证书的私钥如果泄露是很严重的危机,因为这意味着攻击者可能使用这个根证书颁发任意证书,使得这些证书都受信任,从而导致中间人攻击。
一般情况下根证书很少联网,其取用需要经过严格的审核。所以,通常会把颁发证书的功能下放给一些二级机构,证书签名的机制实际上是一种信任委托关系。
根证书机构例子:GlobalSign Root CA.
证书的结构中包含三个部分: 证书内容,证书签名算法+参数以及签名。
证书签名算法,以SHA-1-With-RSA-Encryption为例,其id是:1.2.840.113549.1.1.11,
证书颁发的本质就是为请求者的证书添加一个只能由CA机构提供的公钥校验的签名。注意,由于每个证书都有一个serialNumber和一个绑定的公钥,而客户端理论上保存了CA机构的证书。因此,客户端在只信任已知的serialNumber的CA证书的情况下,能够检测到CA证书被篡改。
因此通过提供证书的签名,CA机构能够确保证书不被篡改,即CA机构确保了颁发出去的证书是受信任的,只要客户端能够校验这一点,客户端就能信任这个证书。
客户端不应当信任任何未知的serialNumber的证书;如果遇到需要新增的证书,客户端应当从官方机构获取。当然,或者证书的过程也能通过证书保证安全,因为获取证书也需要用到TLS协议,而且只能使用已有的证书去获取新的证书,所以客户端获取的证书在网络层面不会被篡改。至于在系统层面,比如被黑客入侵并篡改了证书,则客户端就不安全,但这不是证书要解决的问题。证书主要解决的是在不安全网络环境下进行安全的通信。
如果某个证书声称自己的根证书是某个CA,则客户端可以向该CA查询是否具有这个序列号的证书从而验证合法性。
简而言之,HMAC是带有key的hash函数。
参考文档
计算函数:
H = hashing function
B = 64, the block size for iterating hashing
L = hash output length, with 16 for MD5,20 for SHA1
ipad = 0x36 repeat B times
opad = 0x5C repeat B times
K = H(key) or key; 如果key长度大于B,需要先hash
ZK = K + zeros to B; 将K补齐到B位
output = H(K XOR opad, H(ZK XOR ipad, text))
为了安全,key至少要大于L
长度,并且应当随机选取。
示例:
openssl dgst -sha1 -hmac "key" file
RFC定义
PRF(secret, label, seed) = P_<hash>(secret, label + seed)
P_hash(secret, seed) = HMAC_hash(secret, A(1) + seed) +
HMAC_hash(secret, A(2) + seed) +
HMAC_hash(secret, A(3) + seed) + ...
A() is defined as:
A(0) = seed
A(i) = HMAC_hash(secret, A(i-1))
对于TLSv1.2,hash函数的选择是SHA-256.
迭代次数取决于所需的结果,SHA-256每次产生32个字节,如果需要80字节,就需要迭代3次,产生96字节,去掉16字节。
PRF计算的shell实现(SHA256):
# always use sha256 # usage: # prf secret_file label data_file out_size output # example: # prf secret_file 'master secret' client_server_random 48 master_secret set -e hash_size=32 secret_file=$1 label=$2 data_file=$3 let out_size=$4 output=$5 dir=$output.dir if [[ ! -e $secret_file ]];then echo "requires secret_file" >&2 exit 1 fi if [[ ! -e $data_file ]];then echo "requires data_file" >&2 exit 1 fi mkdir -p "$dir" let 'iterations=out_size/hash_size + (out_size%hash_size==0?0:1)' # function hmac DST_FILE SRC_FILE # HMAC output 32 bytes per file function hmac { # openssl dgst -sha256 -binary -mac hmac -macopt "hexkey:$hexkey" -out "$1" "$2" openssl dgst -sha256 -binary -hmac "$(cat "$secret_file")" -out "$1" "$2" } # seed echo -n "$label" > "$dir/seed" cat "$data_file" >> "$dir/seed" cat "$dir/seed" > "$dir/A0" i=1 while [[ $i -le $iterations ]];do # A(i) = hmac(A(i-1)) hmac "$dir/A$i" "$dir/A$((i-1))" # P(i) = hmac(A(i) + seed) cat "$dir/A$i" "$dir/seed" > "$dir/A${i}_seed" hmac "$dir/P$i" "$dir/A${i}_seed" # result = concat cat "$dir/P${i}" >> "$dir/key" let i++ done truncate -s "$out_size" "$dir/key" cp "$dir/key" "$output" hexdump -C "$output" # keep this for debug purepose rm -rf "$output.dir"
如果客户端协商 Extended Master Secret,则Master Secret由RFC文档定义https://tools.ietf.org/html/rfc7627
master_secret = PRF(pre_master_secret, “extended master secret”, session_hash) [0…47];
其中,session_hash替换原来的client_random+server_random作为seed.
session_hash在TLSv1.2使用的算法是SHA256, 内容包含Finished之前的所有握手信息
session_hash = Hash(handshake_messages)
handshake_messages包含所有收到和发送的握手消息(不包括TLS Record头部),从ClientHello到ClientKeyExchange(包括), 因为在ClientKeyExchange中会发送pre_master_secret。
cat client_hello_handshake.bin server_hello_handshake.bin server_certificate_handshake.bin server_hello_done_handshake.bin client_key_exchange_handshake.bin > session_handshake.bin
实践:
../prf.sh pre_master_secret.bin 'extended master secret' <(openssl dgst -sha256 -binary <(cat client_hello_handshake.bin server_hello_handshake.bin server_certificate_handshake.bin server_hello_done_handshake.bin client_key_exchange_handshake.bin)) 48 extended_master_secret
key_block = PRF(SecurityParameters.master_secret,
"key expansion",
SecurityParameters.server_random +
SecurityParameters.client_random);
until enough output has been generated. Then, the key_block is
partitioned as follows:
client_write_MAC_key[SecurityParameters.mac_key_length]
server_write_MAC_key[SecurityParameters.mac_key_length]
client_write_key[SecurityParameters.enc_key_length]
server_write_key[SecurityParameters.enc_key_length]
client_write_IV[SecurityParameters.fixed_iv_length]
server_write_IV[SecurityParameters.fixed_iv_length]
对于AES-128
而言, enc_key_length=16
,没有fixed_iv_length
实践:
../prf.sh pre_master_secret.bin 'key expansion' <(openssl dgst -sha256 -binary <(cat client_hello_handshake.bin server_hello_handshake.bin server_certificate_handshake.bin server_hello_done_handshake.bin client_key_exchange_handshake.bin)) 48 extended_master_secret
默认情况下,消息加密使用MAC-Then-Encrypt, 参考:
数据格式转变:
原始: encrypt( data || MAC || pad )
该扩展: encrypt( data || pad ) || MAC
注意,只有CBC加密模式有MAC,因此该扩展只应用于CBC加密算法;其他加密算算法应当忽略该扩展(Stream, AEAD)。
解密过程:首先验证MAC,如果MAC失效,则报错。然后再进行解密。
TLS Record的格式:
struct {
ContentType type;
ProtocolVersion version;
uint16 length;
GenericBlockCipher fragment;
opaque MAC;
} TLSCiphertext;
CBC结构
struct {
opaque IV[SecurityParameters.record_iv_length];
block-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
};
} GenericBlockCipher;
MAC的计算:
MAC(MAC_write_key, seq_num +
TLSCipherText.type +
TLSCipherText.version +
TLSCipherText.length +
IV +
ENC(content + padding + padding_length));
或者更简单的: MAC(MAC_write_key, seq_num + TLSCipherText excluding trailing MAC length bytes)
注意:seq_num为uint64,即8字节。
示例:
openssl dgst -sha1 -mac hmac -macopt "hexkey:$(hexdump -ve '1/1 "%02x"' key_block_client_write_MAC)" <(cat <(echo -ne '\x00\x00\x00\x00\x00\x00\x00\x00') <(echo -ne '\x16\x03\x03\x00\x30') <(head -c -20 client_finished_encrypt.bin))
签名数据=
PRF(master_secret, finished_label, Hash(handshake_messages))
[0…verify_data_length-1];
finished_label: 客户端"client finished", 服务器"server finished".
handshake_messages: 收到的所有消息(除去HelloRequest),不包含TLS Record头部。
Hash: 与PRF使用的hash相同,对于TLSv1.2, 就是sha256
实践:
../prf.sh extended_master_secret 'client finished' <(openssl dgst -sha256 -binary <(cat client_hello_handshake.bin server_hello_handshake.bin server_certificate_handshake.bin server_hello_done_handshake.bin client_key_exchange_handshake.bin)) 12 client_finished_verify_data
解密示例:注意, 必须加上-nopad
选项,否则可能导致bad decrypt
.
openssl enc -aes-128-cbc -d -in <(tail -c +17 client_application_data.bin|head -c -20) -iv "$(hexdump -v -e '1/1 "%02x"' <(head -c 16 client_application_data.bin))" -md sha1 -K "$(hexdump -v -e '1/1 "%02x"' key_block_client_write_key)" -nopad
$ curl https://www.baidu.com
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
*
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。