TLSv1.2抓包解密分析过程之RSA_WITH_AES_128_CBC_SHA256


RSA_WITH_AES_128_CBC_SHA256最tls 1.2中最简单的加密协议. 大公司都不再使用了. 但是这个协议非常好分析, 非常适合用于学习tls 1.2的加密.
RSA_WITH_AES_128_CBC_SHA256解释:

  1. 使用RSA非对称加密来传输AES密钥
  2. Hash算法使用SHA256
  3. 应用数据使用AES128 CBC模式加密. (CBC模式需要iv)

这种模式下, 一旦私钥泄露, 抓包的数据就能解密, 所以不安全.

RFC: https://tools.ietf.org/html/rfc5246

准备工作

数据采集过程

  1. 生成自签名证书 https://blog.csdn.net/wzj_whut/article/details/85715347

  2. 导出私钥和公钥的RSA参数 https://blog.csdn.net/wzj_whut/article/details/86477568

  3. 使用自签名的证书, 搭建一个http服务器,

  4. 使用最新版的wireshark抓包.

以下是我生成的证书和抓包数据
https://github.com/wzjwhut/tlsv12-demo/tree/master/src/main/resources
以下是RSA的私钥和公钥相关的参数(n, d, e)
https://github.com/wzjwhut/tlsv12-demo/blob/master/src/main/java/com/wzjwhut/example/MyRSAInfo.java

参考代码
https://github.com/wzjwhut/tlsv12-demo/blob/master/src/main/java/com/wzjwhut/example/Analyse_RSA_WITH_AES_128_CBC_SHA256.java

基本交互流程

tls基本交互流程

客户端 服务器 (1) Client Hello( Random) 客户端将自己支持的cipher suit和一个随机值random传给服务器. 这个值将用于生成密钥 (2) Server Hello( Random ) 服务器将选定的cipher suit和一个随机值random传给客户端, 这个值将用于生成密钥 (3) Certificate证书 把证书传给客户端, 证书中包括RSA私钥和签名 (4) Hello Done Hello过程结束 (5) Client Key Exchange 客户端生成一个pre-master, 然后使用RSA加密, 再传给服务器, 这个值将用于密钥计算, (6) Change Cipher Spec 这个消息表明 之后客户端消 息都是加密的 (7) Encrypted Handshake Message 加密过的Fin ished消息 (8) Change Cipher Spec 这个消息表示之 后的服务器消息 都是经过加密处 理的 (9) Encrypted Handshake Message 加密过的Fin ished消息 客户端 服务器 tls最简单的交互流程(完全展开)

解密分析

以下示例的数据参数
RSA参数
https://github.com/wzjwhut/tlsv12-demo/blob/master/src/main/java/com/wzjwhut/example/MyRSAInfo.java
wireshark抓包数据
https://github.com/wzjwhut/tlsv12-demo/blob/master/src/main/resources/TLS_RSA_WITH_AES_128_CBC_SHA256.pcapng

取出hello中的random

注意random是32个字节, 包含4个字节的时间戳,

 public static final byte[] clientRandom = HexUtils.fromHexString(
         "5c 48 6a 33 93 14 eb 56 d2 7d 86 07 56 83 80 95\n" +
         "ff 1e 3c 32 b4 21 d0 70 ab e2 fb a0 6a b2 6a 3f\n");

 public static final byte[] serverRandom = HexUtils.fromHexString(
         "5c 48 6a 36 a6 57 51 c6 13 a2 88 df 39 a0 42 02\n" +
         "9c 7b 35 cb 4f 55 5d 91 3c 7f 89 e2 c6 21 e2 eb\n");

取出Client Key Exchange中的premaster并解密

public static final byte[] clientEncryptedPreMaster = HexUtils.fromHexString(
      "69 8d 0e 95 67 40 6b ae 0f cb 7e c1 59 34 c4 93\n" +
      "ba a1 ae bb 64 e7 84 e3 54 bc 1e ed 9c 8a 96 7a\n" +
      "f1 51 58 56 28 9b 1a a8 fa a2 e5 0b 2c 0c 20 36\n" +
      "37 55 9c 9d 5f 1a 76 19 18 6e 65 f0 e5 88 19 99\n" +
      "04 b4 49 31 af ce 30 08 22 f5 89 ed cf 32 5b 01\n" +
      "32 b8 76 7e 0d 27 98 f2 df 7e 6c 88 5f 96 14 fd\n" +
      "4d ff e5 b6 0d 50 da e6 72 f3 c9 18 52 10 e8 d9\n" +
      "c8 c6 78 03 b6 bf 07 11 60 95 14 77 34 6d c0 c9\n" +
      "4c 5d 4c 25 25 78 68 70 62 3f a9 21 35 f5 f0 46\n" +
      "34 21 3a e0 63 56 a0 84 64 e5 9e d4 b9 fe 1a c4\n" +
      "d4 6d 31 70 4f da ae c5 ee 70 62 db 16 00 b7 f4\n" +
      "c0 3b ce ad 44 fd a6 1a eb 7f 8b 1c 03 91 47 b9\n" +
      "15 69 5e 70 e8 6b d1 82 ae d8 b6 76 c3 c5 ba 56\n" +
      "e2 87 ee bc 73 c4 3c 7a 67 8a 2f 14 e4 ea d4 67\n" +
      "73 8b ea 9a 7c fb ac e4 a3 d9 cf 4e 81 ba b4 0c\n" +
      "2d 87 02 fa 82 f6 f3 53 b6 f6 45 26 26 1a 1d 02");

	/** 使用RSA私钥解密. 解密后的数据, 最后48字节为pre-master. 其它数据丢弃
		pre-master前两个字节为0x03 0x03. 如果不是, 那说明有问题.
	 */
    public static byte[] decryptPreMaster(){
        BigInteger premaster = new BigInteger(clientEncryptedPreMaster);
        byte[] out = premaster.modPow(MyRSAInfo.d, MyRSAInfo.n).toByteArray();
        return Arrays.copyOfRange(out, out.length-48, out.length);
    }

解密后的pre-master为

//前两个字节是版本号, 对于tls 1.2, 为固定值 03 03
03 03 5f 60 30 50 2c e2 75 df 0a 4c 13 65 62 e5
5d db 0f 08 36 5e a9 e7 c2 b4 25 50 28 4a 4b d9
66 4b 4c c8 93 fe bc 48 44 cf d6 e2 31 de 53 7b

计算Master Secret

RFC中的定义的计算规则为
master_secret = PRF(pre_master_secret, "master secret",ClientHello.random + ServerHello.random)
PRF的定义为

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(0) = seed
A(i) = HMAC_hash(secret, A(i-1))

需要特别注意的是代码中HMAC_hash的参数顺序. secret是作为key的
RSA_WITH_AES_128_CBC_SHA256使用SHA256作为哈希算法, 所以
HMAC_hash为HMAC_hash256
代码实现

/** PRF实现 */
public static byte[] PRF(byte[] secret, byte[] label, byte[] seed, int outBytes) throws Exception{
   //SHA-256每次产生32字节数字
   seed = HexUtils.join(label, seed);
   int c = (outBytes+32-1)/32;
   byte[][] buf = new byte[c][32];
   byte[] preA = seed;
   for(int i=0; i<c; i++){
       byte[] Ai = DigestUtil.hmacsha256(preA, secret); //注意先后顺序.本例中是key在后.
       buf[i] = DigestUtil.hmacsha256(HexUtils.join(Ai, seed), secret);
       preA = Ai;
   }
   return Arrays.copyOf(HexUtils.join(buf), outBytes);
}

/** 生成master secret, 总共48字节 */
 public static byte[] computeMasterSecret(byte[] premaster) throws Exception{
     return PRF(premaster,
             "master secret".getBytes(),
             HexUtils.join(clientRandom , serverRandom), 48);
 }

结果为

e1 e6 95 01 1e 6e 42 9b 4d 20 a1 a1 fa c7 cc 67
df cc 53 55 1d 26 37 53 17 7f 09 66 4a 18 a8 b3
6e bd 5f 51 94 dc fc 1e 2f 1b 15 36 98 7a 6a d7

生成密钥

这些密钥包括
两端的HMAC-SHA256的key
client_write_MAC_key[32]
server_write_MAC_key[32]
两端的AES加密密钥
client_write_key[16]
server_write_key[16]
注意. 本例是以AES128作为例子的, 使用其它的算法, 参数也不相同.
示例代码

//PRF已经在上面定义过了
byte[] keyBlock = PRF(masterSecret, "key expansion".getBytes(),
                HexUtils.join(serverRandom, clientRandom), 96);
        

keyBlock为

30 5e 8f 6c bd 27 12 67 b1 eb 37 28 af 4d 91 d2
92 cb a2 55 7f 99 96 49 db df 7f 78 f7 a6 7d a6
1d 76 90 54 64 c9 84 3c 16 58 6a 07 af e1 3f 8c
92 c3 22 41 2f 47 04 75 37 b3 ba 09 2f 27 16 25
bd c9 9e 7e eb 6a 3c c5 48 2e 6d 4c 42 92 d4 9a
08 50 41 8f e7 a3 d8 ff 56 c4 7f c4 75 57 24 eb

将key block拆分为各自的密钥

/** 以下就是生成的各种密钥 */
byte[] clientMacKey = Arrays.copyOfRange(keyBlock, 0, 32);
byte[] serverMacKey = Arrays.copyOfRange(keyBlock, 32, 64);
byte[] clientAESKey = Arrays.copyOfRange(keyBlock, 64, 64+16);
byte[] serverAESKey = Arrays.copyOfRange(keyBlock, 64+16, 64+16+16);        

解密Encrypted Handshake Message

这个消息比较难理解, 它是把一个handshake消息加密之后, 再封装到handshake消息体中.

 struct {
     opaque IV[SecurityParameters.record_iv_length];
     //以下数据加密, 生成消息体
     block-ciphered struct {
         opaque content[];
         opaque MAC[32]; //本例使用HMAC-SHA256, 输出32字节
         uint8 padding[GenericBlockCipher.padding_length]; //用于对齐16字节. 填充的内容为padding_length
         uint8 padding_length;  //对齐字节的长度
        //最终整个个结构体必须是16的倍数.
     };
 } GenericBlockCipher;

只拿client的消息作为说明. 本例中的client encrypted handshake message消息体为

/** 前16个字节为AES的IV. 剩下的就是加密数据 */
public static final byte[] clientEncryptedHandShakeMessage = HexUtils.fromHexString(
       "22 96 1f c2 56 c4 12 48 b0 91 b6 e5 7c b8 1f 0a\n" +
       "40 e9 b6 ee b6 25 76 71 58 37 d4 37 f9 65 e7 a5\n" +
       "80 94 bb b3 d8 78 c5 a9 c4 b8 29 94 27 da 82 41\n" +
       "e8 22 d6 38 55 48 51 17 6c 8e e8 44 9e 6a 9c aa\n" +
       "68 14 66 eb 95 7d 73 9d cc 47 ac ae 69 c9 b6 73\n");
       
byte[] iv = Arrays.copyOf(clientEncryptedHandShakeMessage, 16);
byte[] content = Arrays.copyOfRange(clientEncryptedHandShakeMessage, 16, 
                clientEncryptedHandShakeMessage.length);
byte[] decrypted = CipherUtil.cbcDecrypt(clientAESKey, iv, content);

解密后的数据为

14 00 00 0c cc 4d 39 01 b6 55 af cd 8d b7 e5 c3
b2 34 3e d5 ed b8 d0 4a ed 27 2e bd 70 fa ca a4
1b 3f 30 ff b7 e3 f3 5a 4f 3d 37 b5 b1 4e 22 cc
0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f

先使用GenericBlockCipher结构体来解析, 结果为

//消息体.
14 00 00 0c cc 4d 39 01 b6 55 af cd 8d b7 e5 c3

//32字节的mac.
b2 34 3e d5 ed b8 d0 4a ed 27 2e bd 70 fa ca a4
1b 3f 30 ff b7 e3 f3 5a 4f 3d 37 b5 b1 4e 22 cc

0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f 0f //padding length为15, 也就是它之前的15个字节是padding.
                                               // 即本行为填充字节.

Handshake的数据格式为

struct {
        HandshakeType msg_type;    /* handshake type */
        uint24 length;             /* bytes in message */
        body;
} Handshake;

使用这个格式来套上面的数据得到

14 //类型为20, 表示finished消息
00 00 0c //长度为12
cc 4d 39 01 b6 55 af cd 8d b7 e5 c3  //即Finished的verify data

Finished的verify data的生成规则为

  1. 收集从Client Hello到本消息之前的所有Handshake类型消息(不包含本次的消息),
    注意点:
    1). Change Cipher Spec不是Handshake类型的消息.
    2). 要去掉record layer的消息头

  2. 执行以下运算

byte[] computedClientEncryptedHandshake = PRF(masterSecret, "client finished".getBytes(),
                    DigestUtil.sha256(clientAllHandshakeMessage), 12);
结果应当为
cc 4d 39 01 b6 55 af cd 8d b7 e5 c3

MAC的计算方式为

官方定义
 MAC(MAC_write_key, seq_num +
                       TLSCompressed.type +
                       TLSCompressed.version +
                       TLSCompressed.length +
                       TLSCompressed.fragment);
 本例没有使用TLSCompressed, TLSCompressed等同于TLSPlaintext
seq_num 从0开始     

本例中
seq = 0 0 0 0 0 0 0 0, 8个字节
type = Handshake=22,
version = 03 03
length = 0 16, 16个字节, 即Finished消息体的长度.
fragment = 0x14 00 00 0c cc 4d 39 01 b6 55 af cd 8d b7 e5 c3
示例代码

  byte[] mac = DigestUtil.hmacsha256(HexUtils.join(
          new byte[]{0,0,0,0,0,0,0,0, 22, 3, 3, 0, 16},
          HexUtils.fromHexString("14 00 00 0c cc 4d 39 01 b6 55 af cd 8d b7 e5 c3")
          ),
          clientMacKey);
  logger.info("clientEncryptedHandshake mac: \r\n{}", HexUtils.dumpString(mac, 16));
结果为
b2 34 3e d5 ed b8 d0 4a ed 27 2e bd 70 fa ca a4
1b 3f 30 ff b7 e3 f3 5a 4f 3d 37 b5 b1 4e 22 cc

解密Application Data

和解密Encrypted Handshake Message一样

  byte[] iv = Arrays.copyOf(clientEncryptedAppData, 16);
  byte[] content = Arrays.copyOfRange(clientEncryptedAppData, 16, clientEncryptedAppData.length);
  byte[] decrypted = CipherUtil.cbcDecrypt(clientAESKey, iv, content);
  logger.info("decrypted application msg: \r\n{}", HexUtils.dumpString(decrypted, 16));
  logger.info("decrypted: \r\n{}", new String(decrypted));

结果为

GET / HTTP/1.1
Host: 115.28.94.100
Accept: text/html
Accept-Encoding: gzip, deflate
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Content-Length: 0

//后面是padding数据, 没去管它.
tk^6��,��S0����^��z���QUb�

猜你喜欢

转载自blog.csdn.net/wzj_whut/article/details/86626529