本节讨论 CCM 在 WiFi 中的实际应用 -- CCMP 协议

根据 RFC 3610,完成 CCMP 报文的加解密,需要提供:分组密钥(K)、随机数(Nonce)、附加认证数据(AAD),这三个参数从哪里来?

另外, 作为处理对象的 CCMP 报文又来自哪里? 正常是通过抓包获取,但无线报文比普通的有线(以太)报文抓取相对麻烦点
幸运的是,万能的 Internet 已经给我们准备好了,在 Wireshark 网站 -- wiki.wireshark.org -- 中有个网页链接
主流协议的报文都被世界各地的网友抓取并上传到链接指向的页面上
进入页面,下载其中一个叫 wpa-Induction.cap 的抓包文件,该文件将作为后续的解密报文

至于分组密钥 K,它与 STA 如何接入 WiFi 网络有关,具体而言
如果 STA 是通过 WPA-PSK/WPA2-PSK 模式(这是家用无线路由器中使用最多的模式)接入,则 K 来源于配置此模式时输入的密码(后面记为 PSK)
如果 STA 是通过 WPA-Enterprise/WPA2-Enterprise 模式接入,则 K 来自 802.1X 认证过程中协商出来的密钥(后续会单独讨论 802.1X 认证)
这两种模式下,STA/AP 双方最终都会得到名为 PMK 的密钥
PMK 的作用类似一个密钥种子,它衍生出(更为准确说是协商出)密钥 PTK(其中就包括分组密钥 K)

就文件 wpa-Induction.cap 而言,它抓取的是 WPA-PSK 模式下 STA 与 AP 的交互报文,AP 的密码(PSK)为 Induction

本节讨论 WPA-PSK 模式下:无线网络密码(PSK) --> PMK --> PTK --> K 的具体变化情况

在无线路由器上配置过 WPA-PSK 模式的网友知道,配置时除了要输入 PSK,还要指定无线网络名(标准名为 SSID)
SSID 有两个作用:标识 AP 自己的名称(与其他 AP 区分出来),另一个就是参与 PMK 的构造
构造过程遵循 PKCS #5 v2.0 中的 PBKDF2 标准,简言之,我们可以认为 PMK = PBKDF2(PSK, SSID)

wpa-Induction.cap 中的 SSID 又是多少?这需要查看类型为 Beacon 的报文
其中有个字段解析为 SSID parameter set: Coherer,即 SSID 名称为 Coherer

这里我们看到,一旦输入的 PSK 和 SSID 固定,PMK 就不再变化,这带来了一定的安全性问题
因为知道 PSK 的 STA 可以通过抓取四次握手报文,嗅探别的 STA 与 AP 之间的流量(见后面详细说明)
在 WPA-Enterprise/WPA2-Enterprise 模式中,PMK 是动态生成的,避免了上述担心

脚本 PBKDF2.pl 是生成 PMK 过程的代码实现,下面是其内容及执行结果

 1 use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex);
 2 use Digest::SHA1  qw(sha1 sha1_hex sha1_base64);
 3 # PBKDF2(passphrase, ssid, 4096, 256) -- underlying function is HMAC-SHA1
 4 
 5 #(The first argument to the pseudorandom function PRF serves as HMAC's
 6 #   "key," and the second serves as HMAC's "text." 
 7 #   In the case of PBKDF2, the "key" is thus the password and the "text" is the salt.)  HMAC-
 8 #   SHA-1 has a variable key length and a 20-octet (160-bit) output
 9 #   value.
10 
11 if( $#ARGV != 1)
12 {
13   print "Usage: perl $0 passphrase ssid -- ASCII string form\n";
14   exit 0;
15 }
16 
17 $count = 4096;
18 $salt = $ARGV[1];
19 $password  = $ARGV[0];
20 print "Salt = SSID = $salt\nPassword    = $password\n";
21 
22 #PBKDF2 (P, S, c, dkLen)
23 #   Input:          P          password, an octet string
24 #                   S          salt, an octet string
25 #                   c          iteration count, a positive integer
26 #                   dkLen      intended length in octets of the derived
27 #                              key, a positive integer, at most
28 #                              (2^32 - 1) * hLen
29 #
30 #   Output:         DK         derived key, a dkLen-octet string
31 #
32 #      2. Let l be the number of hLen-octet blocks in the derived key,
33 #         rounding up, and let r be the number of octets in the last
34 #         block:
35 #
36 #                   l = CEIL (dkLen / hLen) ,
37 #                   r = dkLen - (l - 1) * hLen .
38 #
39 #         Here, CEIL (x) is the "ceiling" function, i.e. the smallest
40 #         integer greater than, or equal to, x.
41 #
42 $round = 2; # ceiling(256 bit/20)
43 #
44 #      3. For each block of the derived key apply the function F defined
45 #         below to the password P, the salt S, the iteration count c, and
46 #         the block index to compute the block:
47 #
48 #                   T_1 = F (P, S, c, 1) ,
49 #                   T_2 = F (P, S, c, 2) ,
50 #                   ...
51 #                   T_l = F (P, S, c, l) ,
52 #
53 for $i (1..$round)
54 {
55   $r .= &F($password, $salt, $count, $i);
56 }
57 $r = substr $r, 0, 32;
58 print "WPA-PSK:PMK = ", uc unpack('H*', $r);
59 sub F
60 {
61   ($password, $salt, $count, $ii) = @_;
62   $int_4 = pack 'N', $ii;
63   $yyy = $xxx = hmac_sha1(join ('',$salt,$int_4), $password);
64   for $i (2..$count)
65   {
66     $xxx = hmac_sha1($xxx, $password);
67     $yyy = $yyy ^ $xxx;
68   }
69   return $yyy;
70 }
View Code

相关文章: