版权声明:
博主:枫之星雨
声明:本文为博主原创文章,转载请注明原文出处。
博文地址:http://blog.csdn.net/zzfenglin
邮箱:[email protected]
QQ号:454086991(申请加好友时请备注”技术交流“)
加密
在连接时,可以对净荷中的数据进行加密,确保数据的机密性,从而抵御攻击者。机密性是指第三方“攻击者”由于没有加密链路的共享密钥,因此无法拦截、破译或读取消息的原始内容。
加密数据包含一个消息完整性校验值,表明该数据包已经过认证。为了验明发送方的有效身份,认证使用共享密钥为已加密的数据计算签名,可防止第三方篡改数据包中的任何内容。通过认证,消息的接收方能够确信收到的数据包来自一个可信设备。
加密数据包还包含一个数据包计数器,用来防止重放攻击。重放攻击是这样一种攻击方式:攻击者截取一个既有的消息,随后再次发送该消息以期望收到响应。试想,假如没有重放攻击保护,攻击者有可能扫描到某个设备的大量数据包,然后再次发送这些数据包,其结果可能不堪设想。显然,防止重放攻击是一件非常重要的事情。
AES-128加密算法简介
低功耗蓝牙中的所有加密和认证都基于同一个加密引擎,称为高级加密系统(AES)。AES最初源自美国的一项政府计划,试图寻找未来可用的加密引擎。一直以来,AES被用于许多有线和无线标准,迄今为止安全研究人员还没有找到其算法的弱点。
AES可以有多种形式,取决于在给定的时间内能够处理的数据块以及密钥的大小。低功耗蓝牙使用128位的密钥和128位的数据块。也就是说,所有密钥的长度均为128位,每次加密生成的密文长度为16个字节。
AES加密块非常简单,它包含两个输入和一个输出。两个输入分别为128位的密钥值和128位的纯文本数据块,输出则为128位的加密数据块。密钥和纯文本在使用上有一些不同;纯文本可以直接为加密块使用,但密钥必须经过处理后才能使用。可见,更有效的方法是只设立一个密钥,用于不同的纯文本块以进行快速加密,而非使用不同的密钥为每个块加密。
在低功耗蓝牙里,AES加密引擎被用于下列四个基本功能:
1.加密净荷数据
2.计算消息完整性校验值
3.数据签名
4.生成私有地址
数据签名在安全管理器中定义,生成私有地址在通用访问规范中定义。
注意,本文主要是为了讲解AES-128算法在BLE设备和安卓手机之间通信时使用的过程,所以并没有去深入研究讲解AES算法。如果有对该算法感兴趣的,可以去网上搜索相关资料深入了解一下。
AES-128加解密方法源码
我们的实验案例是BLE设备端(从机)发送加密的数据给安卓手机(主机)并解密出原始有效数据,以及安卓手机(主机)发送加密的数据给BLE设备端(从机)并解密出原始有效数据。通信过程不便于演示,所以我们换种方式来呈现我们的实现源码:同样的原始有效数据,同样的加解密Key,分别在设备端和Java测试环境中对原始有效数据进行加密然后解密,通过看设备端和Java测试环境中加密之后的数据是否一致以及解密之后的数据是否一致,来判断我们的数据加密传输能否成功,如果加密之后数据一致并且解密之后的数据也一致,那加密传输就可以成功实现。
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”头文件即可。因为上面的注释比较详细,所以我们就不再赘述了。
(3)测试结果:
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(); } }
Java测试实例工程的CSDN下载链接如下:
http://download.csdn.net/detail/zzfenglin/9555823
(3)测试结果:
3.通过BLE设备端测试程序和安卓手机端测试程序结果的比较,我们发现原始有效数据和加密的Key都一样的情况下,二者加密的数据和解密后的数据也都一致,这就保证了设备端把加密数据发给安卓手机端之后,安卓手机端可以成功解密,并且安卓手机端发送加密数据给设备端之后,设备端也可以成功解密。上述的安卓手机端测试代码是在Java工程中,后期要在安卓工程中使用的话,只需要将相关的方法拷贝出去,或者将上述测试的Java文件改写成安卓工程中的一个工具类即可。
Java端参考资料
AES参考代码如下:
算法/模式/填充 16字节加密后数据长度 不满16字节加密后长度 AES/CBC/NoPadding 16 不支持 AES/CBC/PKCS5Padding 32 16 AES/CBC/ISO10126Padding 32 16 AES/CFB/NoPadding 16 原始数据长度 AES/CFB/PKCS5Padding 32 16 AES/CFB/ISO10126Padding 32 16 AES/ECB/NoPadding 16 不支持 AES/ECB/PKCS5Padding 32 16 AES/ECB/ISO10126Padding 32 16 AES/OFB/NoPadding 16 原始数据长度 AES/OFB/PKCS5Padding 32 16 AES/OFB/ISO10126Padding 32 16 AES/PCBC/NoPadding 16 不支持 AES/PCBC/PKCS5Padding 32 16 AES/PCBC/ISO10126Padding 32 16
可以看到,在原始数据长度为16的整数倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],除了NoPadding填充之外的任何方式,加密数据长度都等于16*(n+1);NoPadding填充情况下,CBC、ECB和PCBC三种模式是不支持的,CFB、OFB两种模式下则加密数据长度等于原始数据长度。