安全協議系列(三)----CCMP與WPA-PSK

本節討論 CCM 在 WiFi 中的實際應用 -- CCMP 協議安全

根據 RFC 3610,完成 CCMP 報文的加解密,須要提供:分組密鑰(K)、隨機數(Nonce)、附加認證數據(AAD),這三個參數從哪裏來?網絡

另外, 做爲處理對象的 CCMP 報文又來自哪裏? 正常是經過抓包獲取,但無線報文比普通的有線(以太)報文抓取相對麻煩點
幸運的是,萬能的 Internet 已經給咱們準備好了,在 Wireshark 網站 -- wiki.wireshark.org -- 中有個網頁連接
主流協議的報文都被世界各地的網友抓取並上傳到連接指向的頁面上
進入頁面,下載其中一個叫 wpa-Induction.cap 的抓包文件,該文件將做爲後續的解密報文
app

至於分組密鑰 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)
dom

就文件 wpa-Induction.cap 而言,它抓取的是 WPA-PSK 模式下 STA 與 AP 的交互報文,AP 的密碼(PSK)爲 Inductionide

本節討論 WPA-PSK 模式下:無線網絡密碼(PSK) --> PMK --> PTK --> K 的具體變化狀況oop

在無線路由器上配置過 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 是動態生成的,避免了上述擔憂
spa

腳本 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

C:\>perl PBKDF2.pl Induction Coherer
輸出以下:
Salt = SSID = Coherer
Password    = Induction
WPA-PSK:PMK = A288FCF0CAAACDA9A9F58633FF35E8992A01D9C10BA5E02EFDF8CB5D730CE7BC

前已述及,PMK 會協商出 PTK,這個協商過程就是著名的 EAPOL-Key 四次握手
具體而言,四次握手的前兩個報文(分別由 STA/AP 發出)各自包括了一串名爲 WPA Key Nonce 的隨機數(分別記爲 ANonce/SNonce)

在 wpa-Induction.cap 中,EAPOL-Key 四次握手報文分別爲 #87 #89 #92 #94,其中
#87 報文包含 WPA Key Nonce: 3e8e967dacd960324cac5b6aa721235bf57b949771c867989f49d04ed47c6933 -- ANonce
#89 報文包含 WPA Key Nonce: cdf405ceb9d889ef3dec42609828fae546b7add7baecbb1a394eac5214b1d386 -- SNonce

光有這兩個隨機數還不夠,IEEE 802.11i 中還規定,STA 和 AP 的無線 MAC 地址(分別記爲 AA/SPA)也要參與 PTK 的生成
最終生成 PTK 須要五個參數:PMK、AA、SPA、ANonce、SNonce

腳本 PMK2PTK.pl 對上述過程進行了實現,下面是其內容及執行結果

  1 use Digest::HMAC_SHA1 qw(hmac_sha1);
  2 
  3 if( $#ARGV != 4)
  4 {
  5   print "Usage: perl $0 PMK AA SPA ANonce SNonce\n";
  6   print "       PMK     -- Pairwise Master Key       32 bytes\n";
  7   print "       AA      -- Authenticator MAC address  6 bytes\n";
  8   print "       SPA     -- Supplicant MAC address     6 bytes\n";
  9   print "       ANonce  -- Authenticator Nonce       32 bytes\n";
 10   print "       SNonce  -- Supplicant Nonce          32 bytes\n";
 11   print "All input should be hexadecimal characters\n";
 12   exit 0;
 13 }
 14 
 15 if(length $ARGV[0] !=64)
 16 {
 17   print "PMK MUST be 32 bytes\n";
 18   exit 0;
 19 }
 20 if(length $ARGV[1] !=12)
 21 {
 22   print "AA MUST be 6 bytes\n";
 23   exit 0;
 24 }
 25 if(length $ARGV[2] !=12)
 26 {
 27   print "SPA MUST be 6 bytes\n";
 28   exit 0;
 29 }
 30 if(length $ARGV[3] !=64)
 31 {
 32   print "ANonce MUST be 32 bytes\n";
 33   exit 0;
 34 }
 35 if(length $ARGV[4] !=64)
 36 {
 37   print "SNonce MUST be 32 bytes\n";
 38   exit 0;
 39 }
 40 
 41 $ARGV[0]=uc $ARGV[0];
 42 $ARGV[1]=uc $ARGV[1];
 43 $ARGV[2]=uc $ARGV[2];
 44 $ARGV[3]=uc $ARGV[3];
 45 $ARGV[4]=uc $ARGV[4];
 46 
 47 $pmk    = pack 'H*', $ARGV[0];
 48 $aa     = pack 'H*', $ARGV[1];
 49 $spa    = pack 'H*', $ARGV[2];
 50 $anonce = pack 'H*', $ARGV[3];
 51 $snonce = pack 'H*', $ARGV[4];
 52 @z      = sort $ARGV[1],$ARGV[2];
 53 $bb     = $z[0].$z[1];
 54 @z      = sort $ARGV[3],$ARGV[4];
 55 $bb    .= $z[0].$z[1];
 56 $b      = pack 'H*', $bb;
 57 
 58 my $debug = 0;
 59 
 60 if ($debug)
 61 {
 62   print <<QQQ;
 63     如下摘自 IEEE 802.11i
 64     PTK ←  PRF-X(PMK, "Pairwise key expansion", Min(AA,SPA) || Max(AA,SPA) || Min(ANonce,SNonce) || Max(ANonce,SNonce))
 65           = PRF-X(K,    A,                       B)
 66     
 67     TKIP uses X = 512 and CCMP uses X = 384
 68     
 69     PRF-384(K, A, B) = PRF(K, A, B, 384)
 70     PRF-512(K, A, B) = PRF(K, A, B, 512)
 71     
 72     PRF(K, A, B, Len)
 73       for i  ← 0 to (Len+159)/160 do
 74         R ← R || H-SHA-1(K, A, B, i)
 75       return L(R, 0, Len)
 76     
 77     H-SHA-1(K, A, B, X) ← HMAC-SHA-1(K, A || Y || B || X)
 78     K is a key
 79     A is a unique label for each different purpose of the PRF
 80     B is a variable-length string
 81     Y is a single octet containing 0
 82     X is a single octet containing the loop parameter i
 83     || denotes concatenation
 84     
 85     總結
 86     B = min(AA,SPA) || Max(AA,SPA) || Min(ANonce,SNonce) || Max(ANonce,SNonce)
 87     計算   hmac_sha1("Pairwise key expansion"||0x00||B||0x00, PMK)
 88         || hmac_sha1("Pairwise key expansion"||0x00||B||0x01, PMK)
 89         || hmac_sha1("Pairwise key expansion"||0x00||B||0x02, PMK)
 90         || hmac_sha1("Pairwise key expansion"||0x00||B||0x03, PMK)
 91         || ...
 92     取上述結果前 384/512 Bit
 93 QQQ
 94 }
 95 
 96 undef $R;
 97 $Len = 512/8; # 384 -- TKIP, 512 -- CCMP
 98 $zero = pack 'H2',0;
 99 $a = "Pairwise key expansion";
100 for $i (0 ... ($Len+19)/20)
101 {
102   $idx = pack 'h2',$i;
103   $R .= hmac_sha1($a.$zero.$b.$idx, $pmk);
104 }
105 
106 print "\n";
107 print 'EAPOL-Key Confirm Key               ', unpack('H*',substr($R, 0, 16)),"\n";
108 print 'EAPOL-Key Encrypt Key               ', unpack('H*',substr($R, 16, 16)),"\n";
109 print '(TKIP|CCMP) Temporal Key            ', unpack('H*',substr($R, 32, 16)),"\n";
110 print 'TKIP MIC Key(for Authenticator Tx)  ', unpack('H*',substr($R, 48, 8)),"\n";
111 print 'TKIP MIC Key(for Supplicant Tx)     ', unpack('H*',substr($R, 56, 8)),"\n";
View Code

C:\>perl PMK2PTK.pl a288fcf0caaacda9a9f58633ff35e8992a01d9c10ba5e02efdf8cb5d730ce7bc 000c4182b255 000d9382363a
         3e8e967dacd960324cac5b6aa721235bf57b949771c867989f49d04ed47c6933
         cdf405ceb9d889ef3dec42609828fae546b7add7baecbb1a394eac5214b1d386
上面命令行實際在一行內輸入,只處爲折行顯示

輸出以下:
EAPOL-Key Confirm Key               b1cd792716762903f723424cd7d16511
EAPOL-Key Encrypt Key               82a644133bfa4e0b75d96d2308358433
(TKIP|CCMP) Temporal Key            15798d511beae0028313c8ab32f12c7e
TKIP MIC Key(for Authenticator Tx)  cb71c893482669da
TKIP MIC Key(for Supplicant Tx)     af0e9223fe1c0aed

從輸出結果中看到,PTK 並非一個單獨的密鑰,而是分紅若干部分
其中 EAPOL-Key Confirm Key 和 EAPOL-Key Encrypt Key 僅用於 EAPOL-Key 的四次握手(對握手報文起加密和認證做用)
第三行 (TKIP|CCMP) Temporal Key 參與報文的加解密,在咱們討論的上下文中,就是 CCM 中的分組密鑰 K
最後二行 TKIP MIC Key(...) 僅適用於 TKIP 協議,與 CCMP 協議無關

下面以報文 #99 爲例,剖析 CCMP 的具體操做,報文內容以下
  08 41 2c 00 00 0c 41 82 b2 55 00 0d 93 82 36 3a -- IEEE 802.11 Data 報文頭
  ff ff ff ff ff ff b0 01 01 00 00 20 00 00 00 00
  7e cc f6 0a c1 dd ff b0 47 96 c3 0b a1 9c 92 c6 -- 此行開始共 336 字節(共 21 行)爲密文部分
  6f 4d 1c e7 27 08 c2 95 cf 58 19 45 8c 18 d5 1f    提取爲文件 wpa-Induction-cipher.txt
  64 56 7a 7c c5 ff 85 e7 a6 8b 23 8a 33 5e 44 44
  f7 de 0c 5e ef 72 1d 9f db 0d 51 44 03 d1 c9 06
  46 15 23 3e fc e2 4b 41 6d 53 8c 88 84 5e 46 0d
  29 63 0e da 72 97 fd db b5 66 ac 0a 05 f9 21 1f
  bf 24 39 9a 15 a9 15 11 04 39 bd 0c 0c 51 0a 08
  4a 88 90 50 01 fc 64 cc 9a 4f ca d2 51 d6 e0 f1
  55 00 b7 13 fb 42 c2 44 60 58 2a 68 d0 a5 b9 9c
  80 8e 01 2c 20 0a c5 27 b0 eb 32 0f 75 7d 60 ea
  01 fa 79 f6 5c 2f c3 55 66 90 62 d9 25 e3 e4 4c
  02 91 c1 a7 36 d5 0f 0b 8c 6c 68 de 9e 53 6e d9
  7f eb 43 93 82 80 4b 73 92 3a 61 7f cc ef 37 60
  cf 65 98 f7 7e 39 b9 90 a6 d1 67 ab 5c a6 a9 57
  76 38 fe a8 34 2c 97 ab d5 54 f5 6f ea 48 eb 48
  be 52 df c8 27 66 7b 1c 09 08 78 58 b9 96 9a 74
  10 2d 53 e3 7d 35 2e e4 62 44 84 3d 02 f5 1b 04
  43 64 cb 26 33 fd 2e 8c 16 0a 21 31 24 56 e5 74
  74 89 33 e0 d8 49 5b e8 23 97 d8 9c b7 39 f7 ab
  a0 e8 44 c2 b8 dc 3a 3d 57 d1 a7 b0 7e a9 ff 97
  a3 d7 17 ff 02 83 0b 58 2c a8 94 27             -- 本行中前 8 字節爲認證字段部分,後 4 字節爲 FCS
                                                     CCMP 協議規定:認證字段長度 M = 8

如今已經知道分組密鑰 K = 15798d511beae0028313c8ab32f12c7e
另兩個參數 AAD/Nonce,它們在 IEEE 802.11i 有詳細定義,這裏直接給出結果

AAD = l(a) || a,構造比較複雜,咱們估且認爲它是下列字段的組合(實際上作了一些修正,感興趣可參考標準)
      l(a) || Frame Control || Address 1 || Address 2 || Address 3 || Sequence Control || Address 4 || Qos Control
長度    2           2             6            6            6               2                6             2
其中最後兩個字段 Address 四、Qos Control 爲可選字段, 因此 AAD 長度範圍是 24-32 字節
將 AAD 以 16 字節爲單位分組,獲得 B_一、B_2 兩個附加認證分組,其中 B_2 可能要用 0 填充

在本例中,實際的 AAD 內容爲(最後兩個字段 Address 四、Qos Control 不存在,末尾填充 0)
00 16 08 41 00 0c 41 82 b2 55 00 0d 93 82 36 3a ff ff ff ff ff ff 00 00 00 00 00 00 00 00 00 00
l(a)  Fc    Address 1         Address 2         Address 3         此處開始用 0 填充

生成 AAD 分組文件
C:\>perl -e "binmode STDOUT; print pack('H*','00160841000c4182b255000d9382363affffffffffff00000000000000000000')" > aad.txt

Nonce 相對簡單,其長度 = 15 - L = 15 - 2 = 13,格式爲
Nonce = Priority Octet || A2                || PN
      = 00             || 00 0d 93 82 36 3a || 00 00 00 00 00 01
      = 00 00 0d 93 82 36 3a 00 00 00 00 00 01

B_0 = Flags || Nonce                                  || l(m)
    = 59    || 00 00 0d 93 82 36 3a 00 00 00 00 00 01 || 01 50 -- 0x0150 = 336
    = 59 00 00 0d 93 82 36 3a 00 00 00 00 00 01 01 50
生成 B_0 分組文件
C:\>perl -e "binmode STDOUT; print pack('H*','5900000d9382363a0000000000010150')" > b_0.txt

計算 A_i ...
A_0 = 01 00 00 0d 93 82 36 3a 00 00 00 00 00 01 00 00
A_1 = 01 00 00 0d 93 82 36 3a 00 00 00 00 00 01 00 01
生成 A_1 || A_2 || ... 分組文件
C:\>perl -e "binmode STDOUT; for($i=1;$i<22;$i++){print pack('H28n','0100000d9382363a000000000001',$i)}" > a_i.txt
# 循環 21 次,由於 21 * 16 = 336,索引爲什麼從 1 開始,參見上節

計算密鑰流 E( K, A_1 || A_2 || ... )
C:\>openssl enc -aes-128-ecb -K 15798d511beae0028313c8ab32f12c7e -iv 0 -nopad -in a_i.txt > enc_key.txt

密文 與 加密密鑰 異或,獲得明文

 1 if ( $#ARGV <= 0 )
 2 {
 3   print <<QQQ;
 4     用法:perl $0 file1 file2 ...
 5     舉例:perl $0 a.txt b.txt c.txt
 6     說明:文件個數必須 >= 2
 7 QQQ
 8   exit 0;
 9 }
10 
11 my @file_content;
12 
13 binmode STDOUT;
14 
15 # 檢查其他入參是否爲十六進制和長度相等
16 foreach (@ARGV){
17   open INFILE, "<", $_ or die $!;
18   binmode INFILE;
19   local $/ = undef;
20   $buffer = <INFILE>; # 一次性讀入全部文件內容
21   close (INFILE);
22   push @file_content, $buffer;
23 }
24 
25 my $result = pack('H2', "00");
26 # 逐一提取每一個文件內容,進行 XOR
27 foreach (@file_content){
28   $result = $result ^ $_;
29 }
30 
31 #print uc unpack('H*', $result);
32 print $result;
View Code

C:\>perl xor_file.pl wpa-Induction-cipher.txt enc_key.txt > wpa-Induction-plain.txt

明文內容是什麼呢?咱們對下答案,看看 Wireshark 自身的 CCMP 解密結果,以下圖

原來是 DHCP Request 報文,這也說明 WiFi 安全屬於二層安全範疇
仔細對比明文 wpa-Induction-plain.txt 的內容,徹底符合

僅僅知足數據的機密性仍是不夠的,如何判斷報文是否被篡改過?
CCMP 是經過附在密文後的 8 字節認證字段(紅色顯示部分)來解決的,下面按照 RFC 3610 中的規定進行驗證

已知 A_0 = 01 00 00 0d 93 82 36 3a 00 00 00 00 00 01 00 00
生成 A_0 分組文件
C:\>perl -e "binmode STDOUT; print pack('H*','0100000d9382363a0000000000010000')" > a_0.txt

生成數據校驗密鑰 E(K, A_0)
C:\>openssl enc -aes-128-ecb -K 15798d511beae0028313c8ab32f12c7e -iv 0 -nopad -in a_0.txt > mac_key.txt
C:\>od -An -tx1 mac_key.txt
 37 ef 73 a5 87 be 43 61 92 b3 6e ed c2 01 a3 49 -- 取前 8 字節 37 ef 73 a5 87 be 43 61

數據校驗密鑰 與 認證字段 異或,獲得 X_n+1(即 CBC-MAC)的指望值
C:\>perl xor.pl 37ef73a587be4361 a3d717ff02830b58
結果爲 XOR = 9438645A853D4839

而真實的 CBC-MAC 由密鑰 K 對 -- B_0 || AAD分組 || WiFi明文分組(wpa-Induction-plain.txt) -- 進行 CBC 加密獲得

合併 B_0 || AAD分組 || WiFi明文分組
C:\>perl -pe "BEGIN{binmode STDOUT;}" b_0.txt aad.txt wpa-Induction-plain.txt > b.txt

AES 加密計算 CBC-MAC

 1 use Crypt::CBC;
 2 
 3 if( $#ARGV != 3)
 4 {
 5   print "Usage: perl $0 encrypt/decrypt Key(hex) IV(hex) infile\n";
 6   exit 0;
 7 }
 8 
 9 $cipher = Crypt::CBC->new(
10               -key          =>  pack('H*', $ARGV[1]),
11               -cipher       =>  'Rijndael',
12               -header       =>  'none',
13               -iv           =>  pack('H*', $ARGV[2]),
14               -padding      =>  'none',
15               -keysize      =>  16,
16               -blocksize    =>  16,
17               -literal_key  =>  1,
18 );
19 
20 open INFILE, "<", $ARGV[3] or die $!;
21 binmode INFILE;
22 binmode STDOUT;
23 if ($ARGV[0] =~ /^e/)
24 {
25   $cipher->start('encrypting');
26   local $/ = undef;
27   $buffer = <INFILE>; # 一次性讀入全部文件內容
28   print $cipher->encrypt($buffer);
29 }
30 elsif ($ARGV[0] =~ /^d/)
31 {
32   $cipher->start('decrypting');
33   local $/ = undef;
34   $buffer = <INFILE>; # 一次性讀入全部文件內容
35   print $cipher->decrypt($buffer);
36 }
37 else
38 {
39   print "not encrypt/decrypt";
40 }
View Code

C:\>perl AES-CBC.pl encrypt 15798d511beae0028313c8ab32f12c7e 00000000000000000000000000000000 b.txt > cbc-mac.txt
文件 cbc-mac.txt 的最後一個分組爲
 94 38 64 5A 85 3D 48 39 5B 35 A9 89 E6 47 A3 3B
其前 8 字節 9438645A853D4839指望值 相同,校驗成功,說明報文未被篡改

再看報文最後一個字段 FCS(藍色顯示部分),在 CCMP 中也是起校驗做用不過校驗範圍爲除 FCS 外的整個報文,即:報文頭 || 密文 || 認證字段,不像 WEP 報文中是針對明文校驗一樣也能夠利用 perl 自帶的 crc32.bat 腳本進行驗證

相關文章
相關標籤/搜索