本节讨论 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 }