关于TEA加密算法在Java中遇到的问题

问题是在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;
}

输出:
cpp输出

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工具(测试代码与此工具类在同一目录下)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值