SSL秘钥协商过程

写在SSL之前

加密必须要有明文,密文,加密算法。

常见加密

  • 对称加密-秘钥长度越长,加密效率越低,但是安全性高

    • DES:已经被破解
      • 要求秘钥长度八字节
      • 在对数据进行加密之前先对数据按每组八个字节进行分组,然后分段加密,最后在将每一段加密数据进行组合
    • 3DES:效率低
      • 秘钥疮毒24字节,内部会将秘钥分为三分
      • 需要加密的数据线按每组8个字节进行分组,然后分别使用3份秘钥进行加密->解密->加密;
      • 若三份秘钥都相同,加密成功。
    • TAES
      • 使用最广泛的对称加密算法
      • 16、24、32字节
    • TDEA
    • ……
  • 非对称加密-非对称加密对数据加密的效率低,但是加密的强度高

    • RSA(数字签名和密钥交换)
    • ECC(椭圆曲线加密算法)
  • Hash算法
    不可逆,不用于加密,只用于计算,将任意长度的数据,生成一个固定长度的字符串

  • MD4/MD5
    • 散列值长度16、32字节
  • SHA-1/2
  • …..

加密过程(摘自TSL协议)

Client                            Server

ClientHello           -------->
                                  ServerHello
                                  Certificate*
                                  ServerKeyExchange*
                                  CertificateRequest*
                      <--------   ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished              -------->
                                  [ChangeCipherSpec]
                      <--------   Finished
Application Data      <------->   Application Data

简单的说便是:
SSL客户端在TCP链接建立之后,发起一个握手请求ClientHello,这个消息里面包含了自己可实现的加密算法列表和自己的相关信息;
SSL的服务器端会回应一个ServerHello,这里面确定了这次通信所需要的算法,然后发过去自己的证书(里面包含了身份和自己的公钥)。
Client在收到这个消息后会生成一个秘密消息,用SSL服务器的公钥加密后传过去,
SSL服务器端用自己的私钥解密后,会话密钥协商成功,
双方可以用同一份会话密钥来通信了。

如果上面的说明不够清晰,这里我们用个形象的比喻,我们假设A与B通信,A是SSL客户端,B是SSL服务器端,加密后的消息放在方括号[]里,以突出明文消息的区别。双方的处理动作的说明用圆括号()括起。

A:我想和你安全的通话,我这里的对称加密算法有DES,RC5,密钥交换算法有RSA和DH,摘要算法有MD5和SHA。

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

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

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

A: [我的秘密是…]

B: [其它人不会听到的…]

OpenSSL

OpenSSL是一个安全套接字层密码库,C语言实现。
主要提供于Internet上提供加密传输。

openssl提供了两种运行模式:交互模式/批处理模式

  • 直接输入openssl回车进入交互模式,输入带命令选项的openssl进入批处理模式。

OpenSSL整个软件包大概可以分成三个主要的功能部分:密码算法库、SSL协议库以及应用程序。
OpenSSL的目录结构自然也是围绕这三个功能部分进行规划的。

消息摘要算法应用

Example:消息摘要算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 用SHA1算法计算文件file.txt的哈西值,输出到stdout
openssl dgst -sha1 file.txt

# 用SHA1算法计算文件file.txt的哈西值,输出到文件digest.txt:
openssl sha1 -out digest.txt file.txt

# 用DSS1(SHA1)算法为文件file.txt签名,输出到文件dsasign.bin。签名的private key必须为DSA算法产生的,保存在文件dsakey.pem中。
openssl dgst -dss1 -sign dsakey.pem -out dsasign.bin file.txt

# 用dss1算法验证file.txt的数字签名dsasign.bin,验证的private key为DSA算法产生的文件dsakey.pem。
openssl dgst -dss1 -prverify dsakey.pem -signature dsasign.bin file.txt

# 用sha1算法为文件file.txt签名,输出到文件rsasign.bin,签名的private key为RSA算法产生的文件rsaprivate.pem。
openssl sha1 -sign rsaprivate.pem -out rsasign.bin file.txt

# 用sha1算法验证file.txt的数字签名rsasign.bin,验证的public key为RSA算法生成的rsapublic.pem。
openssl sha1 -verify rsapublic.pem -signature rsasign.bin file.txt

对称加密算法应用

Example:对称加密算法

1
2
3
4
5
6
7
8
9
10
11
#对称加密应用例子,用DES3算法的CBC模式加密文件plaintext.doc,加密结果输出到文件ciphertext.bin。
openssl enc -des3 -salt -in plaintext.doc -out ciphertext.bin

# 用DES3算法的OFB模式解密文件ciphertext.bin,提供的口令为trousers,输出到文件plaintext.doc。注意:因为模式不同,该命令不能对以上的文件进行解密。
openssl enc -des-ede3-ofb -d -in ciphertext.bin -out plaintext.doc -pass pass:trousers

# 用Blowfish的CFB模式加密plaintext.doc,口令从环境变量PASSWORD中取,输出到文件ciphertext.bin。
openssl bf-cfb -salt -in plaintext.doc -out ciphertext.bin -pass env:PASSWORD

#给文件ciphertext.bin用base64编码,输出到文件base64.txt。
openssl base64 -in ciphertext.bin -out base64.txt

DSA

Example:DSA

1
2
3
4
5
6
7
8
9
10
11
# 生成1024位DSA参数集,并输出到文件dsaparam.pem。
openssl dsaparam -out dsaparam.pem 1024

# 使用参数文件dsaparam.pem生成DSA私钥匙,采用3DES加密后输出到文件dsaprivatekey.pem
openssl gendsa -out dsaprivatekey.pem -des3 dsaparam.pem

# 使用私钥匙dsaprivatekey.pem生成公钥匙,输出到dsapublickey.pem
openssl dsa -in dsaprivatekey.pem -pubout -out dsapublickey.pem

# 从dsaprivatekey.pem中读取私钥匙,解密并输入新口令进行加密,然后写回文件dsaprivatekey.pem
openssl dsa -in dsaprivatekey.pem -out dsaprivatekey.pem -des3 -passin

RSA

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
# 产生1024位RSA私匙,用3DES加密它,口令为trousers,输出到文件rsaprivatekey.pem
openssl genrsa -out rsaprivatekey.pem -passout pass:trousers -des3 1024

# 从文件rsaprivatekey.pem读取私匙,用口令trousers解密,生成的公钥匙输出到文件rsapublickey.pem
openssl rsa -in rsaprivatekey.pem -passin pass:trousers -pubout -out rsapubckey.pem

# 用公钥匙rsapublickey.pem加密文件plain.txt,输出到文件cipher.txt
openssl rsautl -encrypt -pubin -inkey rsapublickey.pem -in plain.txt -out cipher.txt

# 使用私钥匙rsaprivatekey.pem解密密文cipher.txt,输出到文件plain.txt
openssl rsautl -decrypt -inkey rsaprivatekey.pem -in cipher.txt -out plain.txt

# 用私钥匙rsaprivatekey.pem给文件plain.txt签名,输出到文件signature.bin
openssl rsautl -sign -inkey rsaprivatekey.pem -in plain.txt -out signature.bin

# 用公钥匙rsapublickey.pem验证签名signature.bin,输出到文件plain.txt
openssl rsautl -verify -pubin -inkey rsapublickey.pem -in signature.bin -out plain

# 从X.509证书文件cert.pem中获取公钥匙,用3DES加密mail.txt,输出到文件mail.enc
openssl smime -encrypt -in mail.txt -des3 -out mail.enc cert.pem

# 从X.509证书文件cert.pem中获取接收人的公钥匙,用私钥匙key.pem解密S/MIME消息mail.enc,结果输出到文件mail.txt
openssl smime -decrypt -in mail.enc -recip cert.pem -inkey key.pem -out mail.txt

# cert.pem为X.509证书文件,用私匙key,pem为mail.txt签名,证书被包含在S/MIME消息中,输出到文件mail.sgn
openssl smime -sign -in mail.txt -signer cert.pem -inkey key.pem -out mail.sgn

# 验证S/MIME消息mail.sgn,输出到文件mail.txt,签名者的证书应该作为S/MIME消息的一部分包含在mail.sgn中
openssl smime -verify -in mail.sgn -out mail.txt

程序示例

MD5

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
#include <openssl/md5.h> // md5 头文件
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

void getMD5(const char *str, char *result) {
MD5_CTX ctx;
// 初始化
MD5_Init(&ctx);
// 添加数据
MD5_Update(&ctx, str, strlen(str)); // 计算结果
unsigned char md[16] = {0};
MD5_Final(md, &ctx);
for (int i = 0; i < 16; ++i) {
sprintf(&result[i * 2], "%02x", md[i]);
}
}

int main(int argc, char *argv[]) {
char result[33] = {0};
getMD5("hello, md5", result);
printf("md5 value: %s\n", result);

return 0;
}

HMAC

此处两个示例,第一个不能在win下使用。

Example1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <string.h>
#include <openssl/hmac.h>

int main() {
const char key[] = "012345678";

char data[] = "hello world";

unsigned char *result;
unsigned int len = 20;

result = (unsigned char *) malloc(sizeof(char) * len);

HMAC_CTX ctx;
HMAC_CTX_init(&ctx);

HMAC_Init_ex(&ctx, key, (int) strlen(key), EVP_sha1(), NULL);
HMAC_Update(&ctx, (unsigned char *) &data, strlen(data));
HMAC_Final(&ctx, result, &len);
HMAC_CTX_cleanup(&ctx);

printf("HMAC digest: ");

for (int i = 0; i != len; i++)
printf("%02x", (unsigned int) result[i]);

printf("\n");

free(result);

return 0;
}

Example2:

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/hmac.h>

int main(int argc, char *argv[]) {

const char key[] = "1999";
const char data[] = "Hello";
unsigned char *md = malloc(EVP_MAX_MD_SIZE);
memset(md, 0x00, EVP_MAX_MD_SIZE);
unsigned int len_md = sizeof(md);

HMAC(EVP_sha1(), key, (int) strlen(key), (const unsigned char *) data, strlen(data), md, &len_md);
printf("data = %s,len = %d\n", md, len_md);

char mdString[40] = {'\0'};
for (int i = 0; i < 20; i++)
sprintf(&mdString[i * 2], "%02x", (unsigned int) md[i]);

printf("HMAC digest: %s\n", mdString);

free(md);

return 0;
}