TIMAC 学习笔记(三)
本文主要内容参考 《Security on TI IEEE 802.15.4 Compliant RF Devices》、《Design Note DN108》、《IEEE 802.15.4协议规范》来考察MAC层的安全机制。
1. MAC层安全机制
先简要介绍一下MAC层的帧格式,下图为通用MAC帧类型。
其中的,Frame Control域结构如下:
Frame Type表示帧类型,MAC层共有四种类型的帧,信标帧、数据帧、命令帧和确认帧。
Security Enable是MAC层的安全保护使能帧,当它设置为1时,Auxiliary Security Header就必须要存在。此域的包含一些同安全传输相关的设置,例如安全级别、密钥来源等,安全帧域长度随着不同的安全级别设置而取不同的数值。以下是安全域的组成。
当开启安全后,Security control位不能为0,用来表示安全级别,Security control字节中的[BIT1,BIT0]表示Security level,[BIT3,BIT2]表示key identifier mode.Key identifier仅仅的那个key identifier mode 大于0时才会被包含在传输帧中。详情如下图。
Frame counter用来确保每个帧的唯一性。
在IEEE 802.15.4-2006规范中,ENC-MIC用在MAC命令帧传输上,ENC用到MAC数据帧上,MIC用在信标帧上。
2. AES加密模式
AES是一种加密方法,结合不同的密钥,能够一次对指定长度的数据进行加密。当待加密的数据有很多时,就会有不同的策略来使用AES来加密。常用的有ECB、CTR、CBC-MAC、CCM几种模式。
2.1 ECB模式
ECB模式就是最基本的使用密钥,一次对16个字节的明文进行加密。这种加密模式的缺点就是,同样的明文输入会产生同样的密文输出,这使得它很容易在数据中识别出来。为了改进这种缺陷,人们提出了ECB-CBC模式。
2.2 ECB-CBC模式
这种模式,在ECB的基础上,后一个待加密明文在进入加密之前,与前一个加密密文输出进行异或,然后进行加密阶段。这样,每段密文输出独立于所有明文块,那么第一个明文块怎么保证它的独立性呢?人们引入了一个IV,在第一个明文块加密前,先与此IV进行异或处理,然后进行后续的链式加密处理。IV向量需要在收发双方之间保持同步。
2.3 CFB模式
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.误差传送:一个明文单元损坏影响多个单元;
3.唯一的IV;
2.3 OFB模式
优点:
1.隐藏了明文模式;
2.分组密码转化为流模式;
3.可以及时加密传送小于分组的数据;
缺点:
1.不利于并行计算;
2.对明文的主动攻击是可能的;
3.误差传送:一个明文单元损坏影响多个单元;
以上部分内容转载自:
happyhippy作者:Silent Void
出处:http://happyhippy.cnblogs.com/
转载须保留此声明,并注明在文章起始位置给出原文链接。
2.3 CTR模式
CTR模式只是仅仅用来加密的模式,为了确保唯一性,对于每个帧,需要有一个随机数nonce。这个随机数被收发双方用一个已知的Key来加密,用加密后的密文与带加密的明文异或。CTR模式每次只对16个字节块加密,如果最后一个字节块不足16个字节,则补0填充。
2.4 CBC-MAC模式
CBC-MAC仅仅用来做完整性验证。这种模式不包含加密帧,仅仅用于认证。被认证的数据划分为以16个字节为单位的块,不足的补0.这种操作模式产生的结果为16字节的数据,只取前M个字节(M=4,8,16),具体取决于软件设置多少位的认证字节数。
2.5 CCM模式
CCM模式是CTR模式和CBC-MAC的混合体,既有加密功能、又有验证功能。它会先在帧上添加认证帧,然后对进行CTR模式的加密。这里有IV向量,详细组成如下:
Sequence counter计算一个帧中16个字节块数。Flags标志域由如下组成:
Reserved保留位,设置为0.Adata用于指示传输帧中是否有认证数据.M是认证域的长度,具体编码为(认证字节长度-2)/2,有效值范围在[4-16]之间。
3. 抓包分析
基于TIMAC的安全数据发送分析
Level = 5 ,说明是加密加上MIC32,即4个字节的验证码,Key_id_mode等于1,Key_index等于3.
4. AES加密相关
CC2530片上含有硬件AES加密引擎,内部可以通过字节传输或者DMA传输,它能够直接支持ECB、CTR、CBC-MAC这几种模式,不能直接支持CCM模式,需要软件来配合使用CTR模式和CBC_MAC模式。
AES协处理器以16个字节为单位工作。一大块数据加载进AES处理器,加密自动执行,结果必须在下一大块数据载入之前,读取出来。在每块数据加载之前,一个开始加密的命令,必须发送给协处理器。
当使用CFB、OFB或者CTR模式时,16字节的加密块将分成4组,每组4个字节。每4个字节加载进入,然后读取4个字节的加密密文结果。
CBC-MAC是CBC模式的变体,当工作在CBC-MAC模式时,一次载入16个字节的数据,除了最后一块数据,在最后一块数据载入之前,需要将加密模式设置为CBC.当最后一块数据载入完成后,读取出来的就是消息的认证码。
CCM模式是CBC-MAC模式和CTR模式的结合。
一个完整的加密配置,包括双方的工作模式、填充模式、初始化向量。这三种因素一个都不能少,通讯双方要一致。
以下以相关的具体代码来分析AES硬件加密引擎的使用。
在实验这个AES加密引擎的时候,参考了《DN108 Using Encrytion in CC111xFx_CC243x and CC251xFx swra173.pdf》,但是,一步一步按照文档上的来,会卡死在最后一步。这个地方折腾了我好久好久,经过和datasheet详细对比后,终于解决了这个问题。
unsigned char iv_nonce[SIZE_OF_AES_BLOCK] = {0xf1, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff}; /****************************************************************************** * @fn SmartLock_Encrypt * * @brief Performs CCM encryption. * the Output buffer must large than input buffer. and the length of * the Output buffer should be the 16 multiple. * input parameters * * @param Input - Pointer to pre-encrypt string * @param Len - Length of Input[] in octets * @param AesKey - Pointer to AES Key * * output parameters * * @param Output - Pointer to after-encrypt string * * @return None * */ extern void SmartLock_Encrypt(uint8 *Input,uint8 *Output,uint8 Len,uint8 *AesKey); void SmartLock_Encrypt(uint8 *Input,uint8 *Output,uint8 Len,uint8 *AesKey) { uint8 i, j; //HalAesInit(); #if 1 uint8 blocks,remainder; uint8 *bptr; //Calcute the AES block length blocks = Len >> 4; remainder = Len & 0x0f; if(remainder)blocks++; bptr = (uint8 *)osal_mem_alloc(blocks * SIZE_OF_AES_BLOCK); if(!bptr)return; osal_memcpy(bptr,Input,Len); if(Len != SIZE_OF_AES_BLOCK){ osal_memcpy(bptr+Len,0,SIZE_OF_AES_BLOCK - remainder); } #endif #if 0 AES_SET_MODE(AES_MODE_CTR); //CTR mode.16 bytes divide into 4 group. #else AES_SET_MODE(AES_MODE_CBC); //AES_SET_MODE(AES_MODE_CFB); //AES_SET_MODE(AES_MODE_OFB); //AES_SET_MODE(AES_MODE_ECB); #endif //1. Download Key (Set ENCCS.CMD = 10b), // and start corresponding AES process (ENCCS.ST = 1) AES_SET_OPERATION(AES_LOAD_KEY); AES_START(); for (i = 0; i < SIZE_OF_AES_BLOCK; i++) { ENCDI = AesKey[i]; } // Monitor AES (ENCCS.RDY) to wait until key downloaded while(!(ENCCS & 0x08)); //2. Dwonload IV/NONCE (Set ENCCS.CMD = 11b) // and start corresponding AES process (ENCCS.ST = 1) AES_SET_OPERATION(AES_LOAD_IV); AES_START(); for (i = 0; i < SIZE_OF_AES_BLOCK; i++) { ENCDI = iv_nonce[i]; } // Monitor AES (ENCCS.RDY) to wait until IV/NONCE downloaded while(!(ENCCS & 0x08)); ////////////////////////////////////////////////////////////////////////////// AES_SET_OPERATION(AES_ENCRYPT); // 3. Perform AES encryption/decryption on allocated AES buffer ("aes_buffer_1"): #if 0 // use for CFB OFB CTR mode for (j = 0; j < blocks; j++) { AES_START(); // In CTR mode need to write data in block of 32 bits unsigned short jj, k; for(jj = 0; jj < 4 ; jj++) { // Writing the input data with zero-padding for(k = 0; k < 4; k++) { ENCDI = bptr[(j*SIZE_OF_AES_BLOCK) + 4*jj + k]; //ENCDI = Input[(j*SIZE_OF_AES_BLOCK) + 4*jj + k]; } while(!(ENCCS & 0x08)) { wait_count++; if(wait_count == 0x00FF) { wait_count = 0; break; } } // Read out data for every 4th byte for(k = 0; k < 4; k++) { Output[(j*SIZE_OF_AES_BLOCK) + 4*jj + k] = ENCDO; } } } #else //use for ECB CBC mode for(j = 0; j< blocks ; j++) { AES_START(); unsigned short jj, k; // Writing the input data with zero-padding for(k = 0; k < 16; k++) { ENCDI = bptr[(j*SIZE_OF_AES_BLOCK) + k]; //ENCDI = Input[(j*SIZE_OF_AES_BLOCK) + 4*jj + k]; } while(!(ENCCS & 0x08)) { wait_count++; if(wait_count == 0x00FF) { wait_count = 0; break; } } // Read out data for every 4th byte for(k = 0; k < 16; k++) { Output[(j*SIZE_OF_AES_BLOCK) + k] = ENCDO; } } #endif #if 1 osal_mem_free(bptr); #endif } /****************************************************************************** * @fn SmartLock_Decrypt * * @brief Performs CCM decryption. * the Output Buffer must great than the Input Buffer, and the Output * Buffer length must be the 16 multiple. * input parameters * * @param Input - Pointer to pre-decrypt string * @param Len - Length of Input[] in octets * @param AesKey - Pointer to AES Key or Pointer to Key Expansion buffer. * * output parameters * * @param Output - Pointer to after-decrypt string * * @return None * */ extern void SmartLock_Decrypt(uint8 *Input,uint8 *Output,uint8 Len,uint8 *AesKey); void SmartLock_Decrypt(uint8 *Input,uint8 *Output,uint8 Len,uint8 *AesKey) { unsigned short i, j; //HalAesInit(); #if 1 uint8 blocks,remainder; uint8 *bptr; //Calcute the AES block length blocks = Len >> 4; remainder = Len & 0x0f; if(remainder)blocks++; bptr = (uint8 *)osal_mem_alloc(blocks * SIZE_OF_AES_BLOCK); if(!bptr)return; osal_memset( bptr, 0, blocks * SIZE_OF_AES_BLOCK ); #endif #if 0 AES_SET_MODE(AES_MODE_CTR); #else AES_SET_MODE(AES_MODE_CBC); //AES_SET_MODE(AES_MODE_CFB); //AES_SET_MODE(AES_MODE_OFB); //AES_SET_MODE(AES_MODE_ECB); #endif //1. Download Key (Set ENCCS.CMD = 10b), // and start corresponding AES process (ENCCS.ST = 1) AES_SET_OPERATION(AES_LOAD_KEY); AES_START(); for (i = 0; i < SIZE_OF_AES_BLOCK; i++) { ENCDI = AesKey[i]; } // Monitor AES (ENCCS.RDY) to wait until key downloaded while(!(ENCCS & 0x08)); //2. Dwonload IV/NONCE (Set ENCCS.CMD = 11b) // and start corresponding AES process (ENCCS.ST = 1) AES_SET_OPERATION(AES_LOAD_IV); AES_START(); for (i = 0; i < SIZE_OF_AES_BLOCK; i++) { ENCDI = iv_nonce[i]; } // Monitor AES (ENCCS.RDY) to wait until IV/NONCE downloaded while(!(ENCCS & 0x08)); ////////////////////////////////////////////////////////////////////////////// AES_SET_OPERATION(AES_DECRYPT); // 3. Perform AES encryption/decryption on allocated AES buffer ("aes_buffer_1"): #if 0 //test for CFB OFB CTR mode for (j = 0; j < blocks; j++) { AES_START(); // In CTR mode need to write data in block of 32 bits unsigned short jj, k; for(jj = 0; jj < 4 ; jj++) { // Writing the input data with zero-padding for(k = 0; k < 4; k++) { ENCDI = Input[(j*SIZE_OF_AES_BLOCK) + 4*jj + k]; } while(!(ENCCS & 0x08)) { wait_count++; if(wait_count == 0xFF) { wait_count = 0; break; } } // Read out data for every 4th byte for(k = 0; k < 4; k++) { bptr[(j*SIZE_OF_AES_BLOCK) + 4*jj + k] = ENCDO; //Output[(j*SIZE_OF_AES_BLOCK) + 4*jj + k] = ENCDO; } } } #else //test ECB CBC mode for (j = 0; j < blocks; j++) { AES_START(); // In CBC mode need to write data in block of 16 bytes unsigned short jj, k; // Writing the input data with zero-padding for(k = 0; k < 16; k++) { ENCDI = Input[(j*SIZE_OF_AES_BLOCK) + k]; } while(!(ENCCS & 0x08)) { wait_count++; if(wait_count == 0xFF) { wait_count = 0; break; } } // Read out data for every 4th byte for(k = 0; k < 16; k++) { bptr[(j*SIZE_OF_AES_BLOCK) + k] = ENCDO; //Output[(j*SIZE_OF_AES_BLOCK) + 4*jj + k] = ENCDO; } } #endif #if 1 osal_memcpy(Output,bptr,Len); osal_mem_free(bptr); #endif }
这里面要特别注意下面这句话。
while(!(ENCCS & 0x08)) { wait_count++; if(wait_count == 0xFF) { wait_count = 0; break; } } 在等待硬件加密完成时,不能一直用while循环死等,要有超时推出处理,否则,就会卡死在这里,这是血泪的教训。
AES加密要求16字节块对齐,因此,如果加密字节不是16字节的倍数,输出缓存需要足够的空间(16字节的倍数)来容纳加密密文。
上述两个函数实现了ECB、CBC、CFB、OFB、CTR模式的加密。最后在考虑各种加密模式的优劣性,选用CBC模式加密。CCM模式太复杂,就没研究了。
测试结果:
ECB模式加密:
Key 0x2b7e151628aed2a6abf7158809cf4f3c
Before encrypt 0x6bc1bee22e409f96e93d7e117393172a
After encrypt 0x3ad77bb40d7a3660a89ecaf32466ef97
备注:用于加密的数组输入,采用的是大端模式输入,即Key的输入数组 Arrar[0] = 0x2B ,而不是0x3C,密文和IV也是同样的理解。
CBC模式加密:
Key 0x2b7e151628aed2a6abf7158809cf4f3c
IV 0x000102030405060708090a0b0c0d0e0f
Before encrypt 0x6bc1bee22e409f96e93d7e117393172a
After encrypt 0x7649abac8119b246cee98e9b12e9197d
参考资料:Android AES加密