【IoT】加密与安全:CC254x 低功耗蓝牙 BLE 之 AES-128 加密算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/liwei16611/article/details/86312815

蓝牙数据是可以通过空中抓包而被抓取到的,因此需要将通信数据进行加密,即使别人截获了加密后的数据,也无法利用该数据。

AES 加密原理

CC254x 支持对称加密 AES:

加密过程:

需要加密的数据 A 与秘钥 KEY 进行一定的算法,获得加密过的数据 B。

解密过程:

加密过的数据 B 与秘钥 KEY 进行一定的逆运算算法,获得加密前的数据 A。

因此,在 BLE 连接交互数据时,可以对明文数据进行加密,确保数据的机密性,从而抵御攻击者。机密性是指第三方“攻击者”由于没有加密链路的共享密钥,因此无法拦截、破译或读取消息的原始内容。

1、BLE AES-128 加密算法简介

低功耗蓝牙 BLE 中的所有加密和认证都基于同一个加密引擎,称为高级加密系统(AES)。

BLE 使用 128(16 字节) 位的密钥和 128 位的数据块。也就是说,所有密钥的长度均为 128 位,每次加密生成的密文长度为 16 个字节。

AES 加密块非常简单,它包含两个输入和一个输出。

两个输入分别为 128 位的密钥值和 128 位的纯文本数据块,输出则为 128 位的加密数据块。

密钥和纯文本在使用上有一些不同:

纯文本可以直接为加密块使用,但密钥必须经过处理后才能使用。

在 BLE 中 AES 加密引擎被用于下列四个基本功能:

1)加密净荷数据

2)计算消息完整性校验值

3)数据签名

4)生成私有地址

数据签名在安全管理器中定义,生成私有地址在通用访问规范中定义。

2、BLE AES-128 加解密方法源码

2.1、BLE 设备端

1)测试环境

开发环境:

IAR8.20版本,Windows XP系统

测试设备:

CC2541/CC2540 开发板

测试例程:

1.4.0 协议栈中 simpleBLEPeripheral 例程

2)实现源码

/****************************************************************
* 名    称: Aes128EncryptAndDecrypTest()
*
* 功    能: 测试AES-128 加解密,注意我们加密时需要key,
*		      加密后的数据用于通信;同样解密的时候也
*		      需要用同一个key进行解密,这样,如果对方
*		      没有key 就无法解密出原始数据,进而保护了
*		      用户数据不被截取。
*
* 入口参数: 无    
* 出口参数: 无
****************************************************************/
static void Aes128EncryptAndDecrypTest(void)
{
	int i = 0;
	// 加密秘钥 16个字节也就是128 bit
	uint8 key[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
 
	// 需要加密的数据(保证16个字节,不够的自己填充)
	uint8 source_buf[16] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
 
	// 加密后数据存放区
	uint8 encrypted_buf[16];
 
	// 解密后数据存放区
	uint8 deccrypted_buf[16];
 
	// 开始加密,加密后的数据存放到 encrypted_buf
	LL_Encrypt( key, source_buf, encrypted_buf );
 
	// 开始解密,将解密后的数据存到deccrypted_buf
	LL_EXT_Decrypt( key, encrypted_buf, deccrypted_buf );
 
	//打印原始数据
	tx_printf("source:");
	for(i = 0;i < 16;i++)
	{
		txprintf("0x%02x ",source_buf[i]);
	}
	tx_printf("");
	//打印加密后的数据
	tx_printf("encrypte:");
	for(i = 0;i < 16;i++)
	{
		txprintf("0x%02x ",encrypted_buf[i]);
	}
	tx_printf("");
	//打印解密后的数据
	tx_printf("deccrypte:");
	for(i = 0;i < 16;i++)
	{
		txprintf("0x%02x ",deccrypted_buf[i]);
	}
	tx_printf("");
 
}

上述测试方法放到“simpleBLEPeripheral.c”文件中,在初始化函数“SimpleBLEPeripheral_Init”里面最后的地方调用上述测试方法“Aes128EncryptAndDecrypTest();”。

通过上述测试方法,我们看到设备端加密用的是“LL_Encrypt”方法,解密用的是“LL_EXT_Decrypt”方法,这两个方法的声明在“Ll.h”头文件中,这个头文件被“hci.h”头文件引用,所以如果提示找不到这两个方法的时候,我们引用“hci.h”头文件即可。

2.2、安卓手机端

1)测试环境:

开发环境:

Eclipse 开发工具,Windows XP 系统。

测试设备:

Eclipse 开发工具编译 java 工程可以在控制台输出结果。

2)测试源码:

package com.zzfenglin.aes;
 
import java.util.Formatter;
 
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
 
public class AesEntryDetry {
	// 加密秘钥 ,16个字节也就是128 bit
	private static final byte[] AES_KEY = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
			12, 13, 14, 15, 16 };
 
	// 需要加密的数据(保证16个字节,不够的自己填充)
	private static final byte[] SOURCE_BUF = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
			11, 12, 13, 14, 15, 16 };
 
	// Java测试工程入口方法,在这个方法中调用加解密方法并打印结果
	public static void main(String[] args) throws Exception {
		// 需要加密的原始数据转化成字符串并打印到控制台
		String strSource = BytetohexString(SOURCE_BUF);
		System.out.println("source:\n" + strSource);
 
		// 调用加密方法,对数据进行加密,加密后的数据存放到encryBuf字节数组中
		byte[] encryBuf = encrypt(AES_KEY, SOURCE_BUF);
		// 将加密后的字节数组数据转成字符串并打印到控制台
		String strEncry = BytetohexString(encryBuf).toLowerCase();
		System.out.println("encrypte:\n" + strEncry);
 
		// 调用解密方法,对数据进行解密,解密后的数据存放到decryBuf字节数组中
		byte[] decryBuf = decrypt(AES_KEY, encryBuf);
		// 将解密后的字节数组数据转成字符串并打印到控制台
		String strDecry = BytetohexString(decryBuf);
		System.out.println("decrypte:\n" + strDecry);
 
	}
 
	// 加密方法
	private static byte[] encrypt(byte[] key, byte[] clear) throws Exception {
		SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
		Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
		cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
		byte[] encrypted = cipher.doFinal(clear);
		return encrypted;
	}

	// 解密方法
	private static byte[] decrypt(byte[] key, byte[] encrypted)
			throws Exception {
		SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
		Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
		cipher.init(Cipher.DECRYPT_MODE, skeySpec);
		byte[] decrypted = cipher.doFinal(encrypted);
		return decrypted;
	}
 
	// 字节数组按照一定格式转换拼装成字符串用于打印显示
	private static String BytetohexString(byte[] b) {
		int len = b.length;
		StringBuilder sb = new StringBuilder(b.length * (2 + 1));
		Formatter formatter = new Formatter(sb);
 
		for (int i = 0; i < len; i++) {
			if (i < len - 1)
				formatter.format("0x%02X:", b[i]);
			else
				formatter.format("0x%02X", b[i]);
 
		}
		formatter.close();
 
		return sb.toString();
	}
}

3、如何使用 MAC 生成 AES 秘钥

例子:该方式可以保证每个设备的 AES 秘钥不同,但如果他人知道了秘钥的获取方式,则该加密方式将不再安全。

CC254x 将温度值放在广播数据中发送给手机 app:

1)CC254x 使用 mac 形成 AES 秘钥;

2)CC254x 将温度值利用 AES 秘钥进行加密;

3)CC254x 将加密后的温度值动态广播出来;

4)手机 app 使用 mac 形成 AES 秘钥。

5)手机app获取CC2541的广播数据,并解析出温度数据字段。

6)将加密后的温度值利用AES秘钥进行解密。


//******************************************************************************                                  
//name:             GUA_AES.c                     
//introduce:        AES驱动              
//author:           
//******************************************************************************       
#include <ioCC2540.h>
#include "LL.h"   
#include "GUA_AES.h"        
  
/*********************内部变量************************/
static GUA_U8 sbGUA_AES_Key[16] = {0};       //AES的秘钥
 
/*********************内部函数************************/
static void GUA_Get_LocalMac(GUA_U8 *pGUA_LocalMac);
static void GUA_AES_GetKey(GUA_U8 *pGUA_AES_LocalMac, GUA_U8 *pGUA_AES_Key);
 
//******************************************************************************                      
//name:             GUA_Get_LocalMac                     
//introduce:        获取本机mac                  
//parameter:        pGUA_LocalMac:mac需要保存到的位置,需要6个字节大小             
//return:           none                                     
//****************************************************************************** 
static void GUA_Get_LocalMac(GUA_U8 *pGUA_LocalMac)
{  
  pGUA_LocalMac[5] = *(GUA_U8 *)(0x780E);       //直接指向指针内容     
  pGUA_LocalMac[4] = *(GUA_U8 *)(0x780F);    
  pGUA_LocalMac[3] = *(GUA_U8 *)(0x7810);    
  pGUA_LocalMac[2] = XREG(0x7811);              //define函数直接读出数据     
  pGUA_LocalMac[1] = XREG(0x7812);    
  pGUA_LocalMac[0] = XREG(0x7813);   
}  
 
//******************************************************************************                      
//name:             GUA_AES_GetKey                     
//introduce:        通过mac形成自定义秘钥                  
//parameter:        pGUA_AES_LocalMac:mac地址 
//                  pGUA_AES_Key:秘钥存放位置,需要16字节大小  
//return:           none                  
//author:           甜甜的大香瓜                           
//email:            [email protected]               
//QQ group:         香瓜BLE之CC2541(127442605)                            
//changetime:       2017.03.29                        
//****************************************************************************** 
static void GUA_AES_GetKey(GUA_U8 *pGUA_AES_LocalMac, GUA_U8 *pGUA_AES_Key)
{    
  pGUA_AES_Key[0] = 'G';  
  pGUA_AES_Key[1] = 'U';
  pGUA_AES_Key[2] = 'A';
  pGUA_AES_Key[3] = '#';
  
  pGUA_AES_Key[4] = pGUA_AES_LocalMac[0];  
  pGUA_AES_Key[5] = pGUA_AES_LocalMac[1];
  pGUA_AES_Key[6] = pGUA_AES_LocalMac[2];
  pGUA_AES_Key[7] = pGUA_AES_LocalMac[3];
  pGUA_AES_Key[8] = pGUA_AES_LocalMac[4];
  pGUA_AES_Key[9] = pGUA_AES_LocalMac[5];  
  
  pGUA_AES_Key[10] = pGUA_AES_LocalMac[0] + pGUA_AES_LocalMac[1];  
  pGUA_AES_Key[11] = pGUA_AES_LocalMac[2] + pGUA_AES_LocalMac[3];
  pGUA_AES_Key[12] = pGUA_AES_LocalMac[4] + pGUA_AES_LocalMac[5];
  
  pGUA_AES_Key[13] = pGUA_AES_LocalMac[0] - pGUA_AES_LocalMac[1];
  pGUA_AES_Key[14] = pGUA_AES_LocalMac[2] - pGUA_AES_LocalMac[3];
  pGUA_AES_Key[15] = pGUA_AES_LocalMac[4] - pGUA_AES_LocalMac[5];  
} 
 
//******************************************************************************                      
//name:             GUA_AES_Encrypted                     
//introduce:        加密16字节的数据                  
//parameter:        pGUA_AES_Data:要加密的数据缓存区,必须16字节 
//                  pGUA_AES_EncryptedData:加密后的数据缓存区,必须16字节
//return:           none                                       
//****************************************************************************** 
void GUA_AES_Encrypted(GUA_U8 *pGUA_AES_Data, GUA_U8 *pGUA_AES_EncryptedData)
{    
  LL_Encrypt(sbGUA_AES_Key, pGUA_AES_Data, pGUA_AES_EncryptedData);
} 
 
//******************************************************************************                      
//name:             GUA_AES_Deccrypted                     
//introduce:        解密16字节以内的数据                  
//parameter:        pGUA_AES_EncryptedData:加密后的数据缓存区,必须16字节
//                  pGUA_AES_DeccryptedData:解密后的数据缓存区,必须16字节 
//return:           none                  
//author:           甜甜的大香瓜                           
//email:            [email protected]               
//QQ group:         香瓜BLE之CC2541(127442605)                            
//changetime:       2017.03.29                        
//****************************************************************************** 
void GUA_AES_Deccrypted(GUA_U8 *pGUA_AES_EncryptedData, GUA_U8 *pGUA_AES_DeccryptedData)
{    
  LL_EXT_Decrypt(sbGUA_AES_Key, pGUA_AES_EncryptedData, pGUA_AES_DeccryptedData); 
} 
 
//******************************************************************************                      
//name:             GUA_AES_Init                     
//introduce:        AES初始化                  
//parameter:        none             
//return:           none                                         
//******************************************************************************     
void GUA_AES_Init(void)        
{    
  GUA_U8 nbGUA_LocalMac[6] = {0};
  
  //获取mac
  GUA_Get_LocalMac(nbGUA_LocalMac);
 
  //通过mac获取秘钥
  GUA_AES_GetKey(nbGUA_LocalMac, sbGUA_AES_Key);
}

refer:

https://blog.csdn.net/zzfenglin/article/details/51729300

https://blog.csdn.net/feilusia/article/details/68070791

https://blog.csdn.net/feilusia/article/details/50085225

猜你喜欢

转载自blog.csdn.net/liwei16611/article/details/86312815
今日推荐