A5/1流密码算法的实现与分析

1. 背景简介

关注GSM手机网络安全的人都知道,21年来,全球35亿人一直在使用的传统64位GSM手机网络加密技术最近已经被公开破解,窃听手机现在已经不是国安 局的专利了,任何人都可以利用破解出来的密码本破译截获的手机信息。安全专家们此前曾展示过一套不法分子可用于窃听并破解64位GSM信号的设备,这套设 备的成本低于1000美元(大部分所使用的破解软件都是免费开源软件)。这种情况不禁令普通百姓,甚至持有商业秘密的大公司心惊肉跳。更令人感到害怕的是手机运营商的态度,当28岁的德国电脑工程师Karsten Nohl宣布破解了64位GSM加密系统之后,他们只是承认了这套系统在安全方面存在的问题,却迟迟不肯出台新的加密算法。

现在,连更高级别的手机通讯加密机制--KASUMI系统--这个采用128位A5/3加密算法的系统也已经被人们成功破解,这套系统正被目前新兴的3G网络所使用。相比去年破解64位的A5/1加密系统时采用Nvidia GPU集群花费了数月时间破解出2TB密码本的工作,这次破解行动则使用了更为复杂的算法(related-key sandwich attack)来破解128位加密系统,而且破解的过程只花了2个小时。

这次破解是由以色列Weizmann科学学院( Weizmann Institute of Science )数学系以及计算机科学系的教职员工完成的。参与这次破解的有 Orr Dunkelman, Nathan Keller和Adi Shamir,其中Adi Shamir便是大名鼎鼎的RSA公钥加密算法的发明人之一。他们所使用的破解算法采用了一种不断更换密钥的方法,“我们只需要使用4个相关联的密钥。226个数据,230比特内存,经过232次计算便可以完成破解。计算的复杂性很小,我们使用单台PC机只花了

不到两个小时便模拟了整个破解过程。

不过据Karsten Nohl教授介绍,这种破解方式从有效性上看不如之前的A5/1破解方式,由于这种破解方法必须事先编好数百万条内容已知的明文,然后再把这些明文一一放到运营商的无线网络中进行传输,之后截获这些信息并对运营商的加密方式进行破解,因此虽然破解计算本身速度很快,但传送明文并搜集截获的信息所花费的时间却需要很长。

目前运营商所使用的KASUMI(A5/3)加密算法是在MISTY算法的基础上改进而来。MISTY算法是由三菱的工程师开发出来的,原始的MISTY算法保密性更强,但所需完成的计算任务则比KASUMI算法更为繁重。

一句话,大量的预计算似乎破解A5/3并不容易。(参见reflextor.com/trac/a51)

而本文专注的是A5/1算法的实现,及相应方程系统的描述。

2. A5/1算法结构

直接图示了……

A5/1流密码算法的实现与分析_开源软件

图1 A5/1算法

A5/1流密码算法的实现与分析_密码_02

图2 A5/1算法结构

A5/1流密码算法的实现与分析_手机_03

图3 A5/1算法描述

A5/1流密码算法的实现与分析_开源软件_04

图4 A5/1算法

3. 代码实现

 

  1. #include <stdio.h>  
  2.  
  3. /*LSB---低字节位*/ 
  4.  
  5. /*MSB---高字节位*/ 
  6.  
  7. /* Masks for the three shift registers */ 
  8.  
  9. #define R1MASK 0x07FFFF /* 19 bits, numbered 0..18 */  
  10.  
  11. #define R2MASK 0x3FFFFF /* 22 bits, numbered 0..21 */  
  12.  
  13. #define R3MASK 0x7FFFFF /* 23 bits, numbered 0..22 */  
  14.  
  15. /* Middle bit of each of the three shift registers, for clock control */ 
  16.  
  17. #define R1MID 0x000100 /* bit 8 */  
  18.  
  19. #define R2MID 0x000400 /* bit 10 */  
  20.  
  21. #define R3MID 0x000400 /* bit 10 */  
  22.  
  23. /* Feedback taps, for clocking the shift registers.  
  24.  
  25. * These correspond to the primitive polynomials  
  26.  
  27. * x^19 + x^5 + x^2 + x + 1,   
  28.  
  29. * x^22 + x + 1,  
  30.  
  31. * x^23 + x^15 + x^2 + x + 1.   
  32.  
  33. */ 
  34.  
  35. #define R1TAPS 0x072000 /* bits 18,17,16,13 */  
  36.  
  37. #define R2TAPS 0x300000 /* bits 21,20 */  
  38.  
  39. #define R3TAPS 0x700080 /* bits 22,21,20,7 */  
  40.  
  41. /* Output taps, for output generation */ 
  42.  
  43. /* 高位是高字节 */ 
  44.  
  45. #define R1OUT 0x040000 /* bit 18 (the high bit) */  
  46.  
  47. #define R2OUT 0x200000 /* bit 21 (the high bit) */  
  48.  
  49. #define R3OUT 0x400000 /* bit 22 (the high bit) */  
  50.  
  51. typedef unsigned char byte;  
  52.  
  53. typedef unsigned long word;  
  54.  
  55. typedef word bit;  
  56.  
  57. /* Calculate the parity of a 32-bit word, i.e. the sum of its bits modulo 2 */ 
  58.  
  59. bit parity(word x) {  
  60.  
  61. x ^= x&gt;&gt;16;  
  62.  
  63. x ^= x&gt;&gt;8;  
  64.  
  65. x ^= x&gt;&gt;4;  
  66.  
  67. x ^= x&gt;&gt;2;  
  68.  
  69. x ^= x&gt;&gt;1;  
  70.  
  71. return x&1;  
  72.  
  73. }  
  74.  
  75. /*Above is cool,but why?  
  76.  
  77. int parity(unsigned long ino)  
  78.  
  79. {  
  80.  
  81. int noofones = 0;  
  82.  
  83. unsigned long mask = 0x00000001ul; /* start at first bit */ 
  84.  
  85. while(mask != 0) /* until all bits tested */ 
  86.  
  87. {  
  88.  
  89. if(mask & ino) /* if bit is 1, increment noofones */ 
  90.  
  91. {  
  92.  
  93. noofones++;  
  94.  
  95. }  
  96.  
  97. mask = mask <&lt; 1; /* go to next bit */ 
  98.  
  99. }  
  100.  
  101. /* if noofones is odd, least significant bit will be 1 */ 
  102.  
  103. return (noofones & 1);   
  104.  
  105. }  
  106.  
  107. */  
  108.  
  109. /* Clock one shift register */ 
  110.  
  111. word clockone(word reg, word mask, word taps) {  
  112.  
  113. word t = reg & taps;//仅取抽头位,做反馈用  
  114.  
  115. reg = (reg &lt;&lt; 1) & mask;//左移一位,&掩码,则表示只取掩码位  
  116.  
  117. reg |= parity(t);//反馈位  
  118.  
  119. return reg;  
  120.  
  121. }  
  122.  
  123. /* The three shift registers. They're in global variables to make the code  
  124.  
  125. * easier to understand.  
  126.  
  127. * A better implementation would not use global variables. */ 
  128.  
  129. word R1, R2, R3;  
  130.  
  131. /* Look at the middle bits of R1,R2,R3, take a vote, and  
  132.  
  133. * return the majority value of those 3 bits. */ 
  134.  
  135. bit majority() {  
  136.  
  137. int sum;  
  138.  
  139. sum = parity(R1&R1MID) + parity(R2&R2MID) + parity(R3&R3MID);  
  140.  
  141. if (sum >= 2)  
  142.  
  143. return 1;  
  144.  
  145. else 
  146.  
  147. return 0;  
  148.  
  149. }  
  150.  
  151. /* Clock two or three of R1,R2,R3, with clock control  
  152.  
  153. * according to their middle bits.  
  154.  
  155. * Specifically, we clock Ri whenever Ri's middle bit  
  156.  
  157. * agrees with the majority value of the three middle bits.*/ 
  158.  
  159. void clock() {  
  160.  
  161. bit maj = majority();  
  162.  
  163. if (((R1&R1MID)!=0) == maj)//(R1&R1MID)!=0)的返回值是0或1  
  164.  
  165. R1 = clockone(R1, R1MASK, R1TAPS);  
  166.  
  167. if (((R2&R2MID)!=0) == maj)  
  168.  
  169. R2 = clockone(R2, R2MASK, R2TAPS);  
  170.  
  171. if (((R3&R3MID)!=0) == maj)  
  172.  
  173. R3 = clockone(R3, R3MASK, R3TAPS);  
  174.  
  175. }  
  176.  
  177. /* Clock all three of R1,R2,R3, ignoring their middle bits.  
  178.  
  179. * This is only used for key setup. */ 
  180.  
  181. void clockallthree() {  
  182.  
  183. R1 = clockone(R1, R1MASK, R1TAPS);  
  184.  
  185. R2 = clockone(R2, R2MASK, R2TAPS);  
  186.  
  187. R3 = clockone(R3, R3MASK, R3TAPS);  
  188.  
  189. }  
  190.  
  191. /* Generate an output bit from the current state.  
  192.  
  193. * You grab a bit from each register via the output generation taps;  
  194.  
  195. * then you XOR the resulting three bits. */ 
  196.  
  197. bit getbit() {  
  198.  
  199. return parity(R1&R1OUT)^parity(R2&R2OUT)^parity(R3&R3OUT);  
  200.  
  201. }  
  202.  
  203. /* Do the A5/1 key setup. This routine accepts a 64-bit key and  
  204.  
  205. * a 22-bit frame number. */ 
  206.  
  207. void keysetup(byte key[8], word frame) {  
  208.  
  209. int i;  
  210.  
  211. bit keybit, framebit;  
  212.  
  213. /* Zero out the shift registers. */ 
  214.  
  215. R1 = R2 = R3 = 0;  
  216.  
  217. /* Load the key into the shift registers,  
  218.  
  219. * LSB of first byte of key array first,  
  220.  
  221. * clocking each register once for every  
  222.  
  223. * key bit loaded. (The usual clock  
  224.  
  225. * control rule is temporarily disabled.) */ 
  226.  
  227. for (i=0; i<64; i++)   
  228.  
  229. {  
  230.  
  231. clockallthree(); /* always clock */ 
  232.  
  233. keybit = (key[i/8] >&gt; (i&7)) & 1; /* The i-th bit of the key */ 
  234.  
  235. R1 ^= keybit; R2 ^= keybit; R3 ^= keybit;  
  236.  
  237. }  
  238.  
  239. /* Load the frame number into the shift  
  240.  
  241. * registers, LSB first,  
  242.  
  243. * clocking each register once for every  
  244.  
  245. * key bit loaded. (The usual clock  
  246.  
  247. * control rule is still disabled.) */ 
  248.  
  249. for (i=0; i<22; i++)  
  250.  
  251. {  
  252.  
  253. clockallthree(); /* always clock */ 
  254.  
  255. framebit = (frame >&gt; i) & 1; /* The i-th bit of the frame # */ 
  256.  
  257. R1 ^= framebit; R2 ^= framebit; R3 ^= framebit;  
  258.  
  259. }  
  260.  
  261. /* Run the shift registers for 100 clocks  
  262.  
  263. * to mix the keying material and frame number  
  264.  
  265. * together with output generation disabled,  
  266.  
  267. * so that there is sufficient avalanche.  
  268.  
  269. * We re-enable the majority-based clock control  
  270.  
  271. * rule from now on. */ 
  272.  
  273. for (i=0; i<100; i++) {  
  274.  
  275. clock();  
  276.  
  277. }  
  278.  
  279. /* Now the key is properly set up. */ 
  280.  
  281. }  
  282.  
  283. /* Generate output. We generate 228 bits of  
  284.  
  285. * keystream output. The first 114 bits is for  
  286.  
  287. * the A->B frame; the next 114 bits is for the  
  288.  
  289. * B-&gt;A frame. You allocate a 15-byte buffer  
  290.  
  291. * for each direction, and this function fills  
  292.  
  293. * it in. */ 
  294.  
  295. void run(byte AtoBkeystream[], byte BtoAkeystream[])   
  296.  
  297. {  
  298.  
  299. int i;  
  300.  
  301. /* Zero out the output buffers. */ 
  302.  
  303. for (i=0; i<=113/8; i++)  
  304.  
  305. AtoBkeystream[i] = BtoAkeystream[i] = 0;  
  306.  
  307. /* Generate 114 bits of keystream for the  
  308.  
  309. * A->B direction. Store it, MSB first. */ 
  310.  
  311. /*每个字节的高位先存*/ 
  312.  
  313. for (i=0; i<114; i++) {  
  314.  
  315. clock();  
  316.  
  317. AtoBkeystream[i/8] |= getbit() &lt;&lt; (7-(i&7));  
  318.  
  319. }  
  320.  
  321. /* Generate 114 bits of keystream for the  
  322.  
  323. * B->A direction. Store it, MSB first. */ 
  324.  
  325. for (i=0; i<114; i++) {  
  326.  
  327. clock();  
  328.  
  329. BtoAkeystream[i/8] |= getbit() &lt;&lt; (7-(i&7));  
  330.  
  331. }  
  332.  
  333. }  
  334.  
  335. /* Test the code by comparing it against  
  336.  
  337. * a known-good test vector. */ 
  338.  
  339. void test() {  
  340.  
  341. byte key[8] = {0x12, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};  
  342.  
  343. word frame = 0x134;  
  344.  
  345. byte goodAtoB[15] = { 0x53, 0x4E, 0xAA, 0x58, 0x2F, 0xE8, 0x15,  
  346.  
  347. 0x1A, 0xB6, 0xE1, 0x85, 0x5A, 0x72, 0x8C, 0x00 };  
  348.  
  349. byte goodBtoA[15] = { 0x24, 0xFD, 0x35, 0xA3, 0x5D, 0x5F, 0xB6,  
  350.  
  351. 0x52, 0x6D, 0x32, 0xF9, 0x06, 0xDF, 0x1A, 0xC0 };  
  352.  
  353. byte AtoB[15], BtoA[15];  
  354.  
  355. int i, failed=0;  
  356.  
  357. keysetup(key, frame);  
  358.  
  359. run(AtoB, BtoA);  
  360.  
  361. /* Compare against the test vector. */ 
  362.  
  363. for (i=0; i&lt;15; i++)  
  364.  
  365. if (AtoB[i] != goodAtoB[i])  
  366.  
  367. failed = 1;  
  368.  
  369. for (i=0; i&lt;15; i++)  
  370.  
  371. if (BtoA[i] != goodBtoA[i])  
  372.  
  373. failed = 1;  
  374.  
  375. /* 先打印已知的密钥和帧数. */ 
  376.  
  377. printf("key: 0x");  
  378.  
  379. for (i=0; i&lt;8; i++)  
  380.  
  381. printf("%02X", key[i]);  
  382.  
  383. printf("\n");  
  384.  
  385. printf("frame number: 0x%06X\n", (unsigned int)frame);  
  386.  
  387. /* 打印已知的228位密钥流. */ 
  388.  
  389. printf("known good output:\n");  
  390.  
  391. printf(" A->B: 0x");  
  392.  
  393. for (i=0; i<15; i++)  
  394.  
  395. printf("%02X", goodAtoB[i]);  
  396.  
  397. printf(" B->A: 0x");  
  398.  
  399. for (i=0; i<15; i++)  
  400.  
  401. printf("%02X", goodBtoA[i]);  
  402.  
  403. printf("\n");  
  404.  
  405. /* 打印计算出的228位密钥流. */ 
  406.  
  407. printf("observed output:\n");  
  408.  
  409. printf(" A->B: 0x");  
  410.  
  411. for (i=0; i<15; i++)  
  412.  
  413. printf("%02X", AtoB[i]);  
  414.  
  415. printf(" B->A: 0x");  
  416.  
  417. for (i=0; i&lt;15; i++)  
  418.  
  419. printf("%02X", BtoA[i]);  
  420.  
  421. printf("\n");  
  422.  
  423. if (!failed) {  
  424.  
  425. printf("Self-check succeeded: everything looks ok.\n");  
  426.  
  427. return;  
  428.  
  429. else {  
  430.  
  431. /* Problems! The test vectors didn't compare*/ 
  432.  
  433. printf("\nI don't know why this broke; contact the authors.\n");  
  434.  
  435. exit(1);  
  436.  
  437. }  
  438.  
  439. }  
  440.  
  441. int main(void) {  
  442.  
  443. test();  
  444.  
  445. return 0;  
  446.  
  447. }  
  448.  

4. 试验结果

已知的生成的228位最初密钥,和新生成的228位最初密钥的对比。

5. A5/1算法的方程描述

假设我们获得了A5/1的一系列密钥流,可以把线性移位寄存器中的状态及初始密钥当做变元,随着clock产生密钥流的方程。

先看两个小例子:

A5/1流密码算法的实现与分析_手机_05

A5/1流密码算法的实现与分析_密码_06

A5/1流密码算法的实现与分析_国安_07

这里的线性移位寄存器的反馈函数可表示为矩阵的形式。而输出变元经过组合函数f便生出了密钥流位。这里的方程应该先反馈,再输出。其是先输出后反馈?对上例补一个说明……

A5/1流密码算法的实现与分析_密码_08

再一个例子:

A5/1流密码算法的实现与分析_手机_09

A5/1流密码算法的实现与分析_国安_10

A5/1流密码算法的实现与分析_密码_11

注:生出方程的次数的高低仅与非线性组合函数或过滤函数有关。

下面开始针对A5/1分析:

A5/1流密码算法的实现与分析_手机_12

这个择多函数的描述很巧妙,实际A5/1流密码算法的实现与分析_手机_13,整个方程的非线性性也就由它来确定。

另对每个LSFR来说,其状态转移方程不像上面小例子乘个矩阵那么简单。因为每clock一下都要考虑到择多函数的干扰。

A5/1流密码算法的实现与分析_手机_14

这里对每个V,根据vi的值去决定移动,一致则移,否则不移。在生出方程的时候,初始用64个变元表示所有3个寄存器的状态。

先看个小例子:

A5/1流密码算法的实现与分析_密码_15

A5/1流密码算法的实现与分析_密码_16

其所产生的方程全都是受状态转移方程的控制,可看到其相对复杂,其大小以指数形式增长,故光存这些多项式就很占内存。