问题是在Java中int的最高位是正负标志位,涉及到最高位的位运算便有可能出错。
两段测试代码,先看Java的:
public static void testTEA(){
int[] datas = {0x01234567, 0x89abcdef};
int[] key = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
int y = datas[0];
int z = datas[1];
int DELTA = 0x9e3779b9;
int sum = 0;
for (int i = 0; i < 1; i++) {
sum += DELTA;
y += ((z << 4) + key[0]) ^ (z + sum) ^ ((z >> 5) + key[1]);
System.out.println(Integer.toHexString((z << 4) + key[0]));
System.out.println(Integer.toHexString(z + sum));
System.out.println(Integer.toHexString((z>>5)));
z += ((y << 4) + key[2]) ^ (y + sum) ^ ((y >> 5) + key[3]);
}
}
再看C++的:
void test_TEA() {
uint32_t y = 0x01234567;
uint32_t z = 0x89abcdef;
uint32_t key[] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
uint32_t delta = 0x9e3779b9;
int i;
uint32_t sum = 0;
for (i = 0; i < 1; i++) {
sum += delta;
y += ((z << 4) + key[0]) ^ (z + sum) ^ ((z >> 5) + key[1]);
cout << hex << (z << 4) + key[0] << endl;
cout << hex << z + sum << endl;
cout << hex << (z >> 5) << endl;
z += ((y << 4) + key[2]) ^ (y + sum) ^ ((y >> 5) + key[3]);
}
}
两段代码的逻辑与参数都是相同的,但是因为Java语言本身是没有无符号整数,导致了Java中无法算出正确结果。
Java输出:
abcdf001
27e347a8
fc4d5e6f
C++输出:
abcdf001
27e347a8
44d5e6f
可以看到,问题出在"z>>5"这里,算出的值是“错误的”。在Java中,0x89abcdef已经是一个“负数”了。
根本原因在于int的位数已经不够,这里需要32位,而java只有31位,所以办法就是扩充其位数。
最简单的办法就是用long代替int。
public static void testTEA(){
long[] datas = {0x01234567L, 0x89abcdefL};
long[] key = { 0x11111111L, 0x22222222L, 0x33333333L, 0x44444444L };
long y = datas[0];
long z = datas[1];
int DELTA = 0x9e3779b9;
int sum = 0;
for (int i = 0; i < 1; i++) {
sum += DELTA;
y += ((z << 4) + key[0]) ^ (z + sum) ^ ((z >> 5) + key[1]);
System.out.println(Long.toHexString((z << 4) + key[0]));
System.out.println(Long.toHexString(z + sum));
System.out.println(Long.toHexString((z>>5)));
z += ((y << 4) + key[2]) ^ (y + sum) ^ ((y >> 5) + key[3]);
}
}
一定要注意在数值后加上"L",否则高位会被填充。
如果不加“L”,默认的数值其实是“ffffffff89abcdef”,相信这绝对不是我们想要的数值。
这样一来,输出也变成这样:
8abcdf001
27e347a8
44d5e6f
那么问题来了,右移是没问题了,左移却又出问题了,"8abcdf001"明显多出一位来。
并且不只这一个问题,每次进行位运算以及运算结果,都要保证位数不超过32位,也就是8位16进制数的形式。否则,加密次数多了之后,一定会出错。
所以,我需要一个可以去掉高位的方法:
private static long long2longInt(long num) {
return Long.parseLong(Integer.toHexString((int) num), 16);
}
然后,对每一步可能产生影响的数据进行惨无人道的包裹,就像这样:
/**
* 加密,每次支持64bit,即两个无符号整数,改用long代替int
*
* @param content 原文
* @param key 密钥
* @param times 加密轮数
* @return 加密后
*/
private static byte[] encrypt(byte[] content, long[] key, int times) {
long[] pendingData = byte2longArray(content);
long y = pendingData[0], z = pendingData[1];
long sum = 0;
long key1 = key[0], key2 = key[1], key3 = key[2], key4 = key[3];
for (int i = 0; i < times; i++) {
sum += DELTA;
sum = long2longInt(sum);
y += long2longInt((z << 4) + key1) ^ long2longInt(z + sum) ^ long2longInt((z >> 5) + key2);
y = long2longInt(y);
z += long2longInt((y << 4) + key3) ^ long2longInt(y + sum) ^ long2longInt((y >> 5) + key4);
z = long2longInt(z);
}
pendingData[0] = y;
pendingData[1] = z;
return longArray2Byte(pendingData);
}
需要注意的,因为涉及到的运算结果总是交叉进行的,前一步的结果在当前循环体内都要被后一步用于计算,所以“去除高位”应当马上执行,而不是等待此次循环结束。
有了这一步之后,其他算法就和C++中的保持一致了,最后再来测试一下,加密轮数为32下的情况。
C++测试代码如下:
#include<iostream>
using namespace std;
typedef unsigned int uint32_t;
void test_TEA() {
uint32_t y = 0x01234567;
uint32_t z = 0x89abcdef;
uint32_t key[] = { 0x11111111, 0x22222222, 0x33333333, 0x44444444 };
uint32_t delta = 0x9e3779b9;
int i;
uint32_t sum = 0;
cout << "start" << endl;
for (i = 0; i < 32; i++) {
sum += delta;
y += ((z << 4) + key[0]) ^ (z + sum) ^ ((z >> 5) + key[1]);
z += ((y << 4) + key[2]) ^ (y + sum) ^ ((y >> 5) + key[3]);
}
cout<< hex << y <<endl;
cout<< hex << z <<endl;
}
int main(){
test_TEA();
return 0;
}
输出:
Java测试代码(加密逻辑封装为工具类TEA了):
public static void main(String[] args) {
long[] key = {0x11111111L, 0x22222222L, 0x33333333L, 0x44444444L};
byte[] bytes = toHexBytes("0123456789abcdef");
System.out.println(bytesToHexString(TEA.encryptByTea(bytes,key,32)));
}
输出:
cf6286d6e432db64
OK的。
解密时初始和要随着常数变动,为加密轮数的倍数,之前工具中忘记变动了,导致解密不对称。
/**
* 解密,每次支持64bit,即两个无符号整数,改用long代替int
*
* @param encryptContent 加密内容
* @param key 密钥
* @param times 加密轮数
* @return 加密前
*/
private static byte[] decrypt(byte[] encryptContent, long[] key, int times) {
long[] pendingData = byte2longArray(encryptContent);
long y = pendingData[0], z = pendingData[1];
long sum = DELTA * times;
long key1 = key[0], key2 = key[1], key3 = key[2], key4 = key[3];
for (int i = 0; i < times; i++) {
z -= long2longInt((y << 4) + key3) ^ long2longInt(y + sum) ^ long2longInt((y >> 5) + key4);
z = long2longInt(z);
y -= long2longInt((z << 4) + key1) ^ long2longInt(z + sum) ^ long2longInt((z >> 5) + key2);
y = long2longInt(y);
sum -= DELTA;
sum = long2longInt(sum);
}
pendingData[0] = y;
pendingData[1] = z;
return longArray2Byte(pendingData);
}
Java封装的工具github链接:TEA工具(测试代码与此工具类在同一目录下)