适用单片机的btea加密代码剖析
以前做单片机bootloader时候,尝试过搞些简单的加密来保护app的bin文件,后面在网上抄了份tea代码
1 #define MX ((z>>5)^(y<<2))+(((y>>3)^(z<<4))^(sum^y))+(k[p&(3^e)]^z) 2 #define DELTA 0x9e3779b9 3 #define S_LOOPTIME 1 4 5 /* 6 *key must be 128bit = 16 Bytes. 7 */ 8 void btea_encrypt(uint8_t* buf,uint8_t* key,uint32_t block_size) 9 { 10 uint8_t* v = (uint8_t*)buf; 11 uint8_t* k = (uint8_t*)key; 12 uint8_t z = v[block_size - 1],y = v[0],sum = 0,e; 13 uint32_t p,q ; 14 // Coding Part 15 16 q = S_LOOPTIME + 52 / block_size ; 17 while ( q-- > 0 ) 18 { 19 sum += DELTA ; 20 e = sum >> 2 & 3 ; 21 for (p = 0 ; p < block_size - 1 ; p++) 22 { 23 y = v[p + 1], 24 v[p] += MX; 25 z = v[p]; 26 } 27 28 y = v[0] ; 29 z = v[block_size - 1] += MX; 30 } 31 } 32 33 /* 34 *key must be 128bit = 16 Bytes. 35 *inbuf == outbuf == buf 36 */ 37 void btea_decrpyt(uint8_t* buf,uint8_t* key,uint32_t block_size) 38 { 39 uint8_t* v=(uint8_t*)buf; 40 uint8_t* k=(uint8_t*)key; 41 uint8_t z = v[block_size - 1],y = v[0],sum = 0,e ; 42 uint32_t p,q ; 43 44 //Decoding Part... 45 q = S_LOOPTIME + 52 / block_size ; 46 sum = q * DELTA ; 47 while (sum != 0) 48 { 49 e = sum >> 2 & 3 ; 50 for (p = block_size - 1 ; p > 0 ; p--) 51 { 52 z = v[p - 1], 53 v[p] -= MX; 54 y = v[p]; 55 } 56 57 z = v[block_size - 1] ; 58 y = v[0] -= MX; 59 sum -= DELTA ; 60 } 61 }
既然有人送鱼了,能吃饱,就直接用了,看着如此复杂的计算,就没有关心过其实现原理。
最近有其他需求用到加密,趁手的只有btea了,就从冷库里把这条鱼拿了出来。好家伙,so smelly!!!直接发现4个问题
1.密钥第5-16字节怎么改,加密解密结果都一样
2.传入的大小block_size = 1的情况下,无法解密出结果
3.S_LOOPTIME >= 256,无法解密出结果
4.S_LOOPTIME < 256的情况下,变更DELTA后,概率性无法解密出结果
网上查,都是uint32_t形式的,对byte处理的看着就是八仙过海,可惜没有能讲清的。
躲不掉了,还是得理解才行,一个个问题查吧。
1.密钥5-16位无效问题
跟密钥有关的就 e = sum >> 2 & 3 ; 和 k[p&(3^e)] 两句,一眼看到&和3,就有点像学生看到30°、45°和60°那种感觉了。查下运算符优先级(这种不加括号的写法真的恶心),右移>>运算高于按位与&运算,所以不需要在意sum,e最后的范围就是[0,3],进而(3 ^ e)取值也是[0,3],再&p后,p & (3 ^ e)的结果也在[0,3],这意味着,密钥下标就会用前4个字节。R U kidding???深度思考下,为啥能出这种幺蛾子,肯定就是上面提到的,原本uint32_t改成uint8_t没改到精髓,以前22个uint32_t,mask自然就是22 - 1,即为3,现在同样用128bit的密钥,那可是24个uint8_t,那mask不得改成么24 - 1 = 15才行吗。
2.单字节数据无法解密问题
这就得先大体上了解下tea的玩法了,加密过程就是(多次)遍历数组,每个元素加上MX这样一个表达式,姑且理解为一个变化的val吧,解密就是(多次)遍历数组减回对应的val。那下一步就是要搞清楚,MX依赖啥东西了,z、y、sum、k、p、e,这种没有技术文档的代码还毫无可读性,白送的鱼,没办法,哎。看眼缘一个个分析吧
1)k,这玩意就是密钥,加解密都已知
2)sum,sum在加密过程就是DELTA在轮数上的累加,那对应的解密过程就是DELTA和总轮数确认结果然后递减,而总轮数就是代码里的q,由配置S_LOOPTIME和block_size决定,二者均已知,最终sum在加解密过程中均为已知项
3)e直接就是sum的固定方式位运算,sum确定e确定,已知;
4)p,遍历数组时的索引,表示当前在处理的元素,加密时递增,解密时递减,已知
5)z,把buf看成环形,z代表前一个元素,详见下表,表格内数字x代表z = buf[x]
6)y,把buf看成环形,y代表后一个元素,详见下表,表格内数字x代表y = buf[x]
| p | 0 | 1 | 2 | ... | block_size - 3 | block_size - 2 | block_size - 1 |
| z | block_size - 1 | 0 | 1 | ... | block_size - 4 | block_size - 3 | block_size - 2 |
| y | 1 | 2 | 3 | ... | block_size - 2 | block_size - 1 | 0 |
经过上述分析,就剩z,y需要确定了,在加密过程,处理p数据时依赖于其前后的数据;解密时,处理p数据时依赖与加密时用到的前后数据。根据block_size,需要分4类讨论:
a)block_size >= 3,加密最后的一个数据所依赖的y、z均不会再变化,解密直接用,make sense
b)2 == block_size,加密最后的一个数据所依赖的y、z均为另一个元素,不变化,也可直接用于解密
c)1 == block_size,加密最后的一个数据所依赖的y、z均为自身,加密计算后变化,无法用于解密
d)0 == block_size,嘥鸠气
综上,因为依赖的关系,这个代码只能用于2bytes以上的文本加密
3.循环轮数超过0xFF后失效
这个问题下意识就是轮数数据类型出问题了,一查 ,uint32_t的q,居然不是。再查下q和什么鬼挂钩了,sum!!!加密轮数就很干净地按q算,解密却是按sum是否为0作为结束的指示,而这个sum还是个uint8_t,那问题显而易见了,当总轮数超过255后,计为round,在解密过程中round执行了(round & 0xFF)次后,一字节的sum就变0了,毕竟((round & 0xFFFFFF00) * DELTA)对低8位毫无贡献,最后解密执行次数不足,能解出来原文就怪了。
4.循环轮数 < 256的情况下,变更DELTA概率失效
这个跟3原理通的,解密过程如果DELTA * 次数正好整除256,那就会提前结束。
综合下来,这条鱼更多像是别人拿着32位的ltea鱼解剖出来的8位btea鱼。上面排问题过程可以理解为见鱼思渔了。下面,上修复了4个问题的渔具
1 #define TEA_CFG_MX ((prev >> 5) ^ (next << 2)) + (((next >> 3) ^ (prev << 4)) ^ (sum ^ next)) + (key[idx & (TEA_KEY_LEN_MASK ^ keyMask)] ^ prev) 2 #define TEA_CFG_DELTA 0x9e3779b9 3 #define TEA_CFG_LOOP_TIMES 6 4 #define TEA_CFG_KEY_LEN_POWER 4 5 6 #define TEA_MX TEA_CFG_MX 7 #define TEA_DELTA TEA_CFG_DELTA 8 #define TEA_LOOP_TIMES TEA_CFG_LOOP_TIMES 9 #define TEA_KEY_LEN_POWER TEA_CFG_KEY_LEN_POWER 10 #define TEA_KEY_LEN (1U << TEA_KEY_LEN_POWER) 11 #define TEA_KEY_LEN_MASK (TEA_KEY_LEN - 1) 12 13 void btea_encrypt(uint8_t* buf,uint8_t* key,uint32_t block_size) 14 { 15 uint8_t prev; 16 uint8_t next; 17 uint8_t keyMask; 18 uint32_t idx; 19 uint32_t restRound; 20 uint32_t sum; 21 22 if((0 == block_size) || (NULL == buf) || (NULL == key)) 23 { 24 return; 25 } 26 27 if(1 == block_size) 28 { 29 for(uint32_t i = 0;i < TEA_KEY_LEN;i++) 30 { 31 buf[0] ^= key[i]; 32 } 33 return; 34 } 35 36 sum = 0; 37 prev = buf[block_size - 1]; 38 next = buf[0]; 39 restRound = TEA_LOOP_TIMES + 52 / block_size ; 40 41 while (restRound--) 42 { 43 sum += TEA_DELTA ; 44 keyMask = (sum >> 2) & TEA_KEY_LEN_MASK ; 45 for (idx = 0;idx < block_size - 1;idx++) 46 { 47 next = buf[idx + 1]; 48 buf[idx] += TEA_MX; 49 prev = buf[idx]; 50 } 51 52 next = buf[0]; 53 buf[block_size - 1] += TEA_MX; 54 prev = buf[block_size - 1]; 55 } 56 } 57 58 void btea_decrypt(uint8_t* buf,uint8_t* key,uint32_t block_size) 59 { 60 uint8_t prev; 61 uint8_t next; 62 uint8_t keyMask; 63 uint32_t idx; 64 uint32_t restRound; 65 uint32_t sum; 66 67 if((0 == block_size) || (NULL == buf) || (NULL == key)) 68 { 69 return; 70 } 71 72 if(1 == block_size) 73 { 74 for(uint32_t i = 0;i < TEA_KEY_LEN;i++) 75 { 76 buf[0] ^= key[i]; 77 } 78 return; 79 } 80 81 sum = 0; 82 prev = buf[block_size - 1]; 83 next = buf[0]; 84 restRound = TEA_LOOP_TIMES + 52 / block_size ; 85 86 87 sum = restRound * TEA_DELTA ; 88 while (restRound--) 89 { 90 keyMask = (sum >> 2) & TEA_KEY_LEN_MASK ; 91 for (idx = block_size - 1;idx > 0;idx--) 92 { 93 prev = buf[idx - 1]; 94 buf[idx] -= TEA_MX; 95 next = buf[idx]; 96 } 97 98 prev = buf[block_size - 1]; 99 buf[0] -= TEA_MX; 100 next = buf[0]; 101 sum -= TEA_DELTA ; 102 } 103 }
浙公网安备 33010602011771号