去年PHP与Andriod终端通讯,想使用TEA加密,却发现Java实现的TEA只能由Java解密、PHP实现的TEA只能由PHP解密。这不是我们想要的。
昨天中午有空,想起加密这回事,仔细研究了TEA算法,本人笨,经过十五个小时的摸索,终于实现了C语言、Java与32bit的PHP加密解密一致性。
首先,来一段网上流行的C语言描述的TEA算法:
#include <stdint.h>
void encrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
这段代码出现在Wiki百科、百度百科,描述的就是TEA加密解密算法的核心。可惜,如果不加思考与分析,就使用这段代码,就完蛋了。因为这是一段错误的代码。
TEA加密,就是使用一个128bit的密钥,对64bit的明文,经过16轮、32轮或64轮的运算,生成64bit的密文。下面是我修改过的代码:

#include <stdio.h>
#include <stdint.h>
void encrypt (uint32_t v[], uint32_t k[]) {
uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
} /* end cycle */
v[0]=v0; v[1]=v1;
}
void decrypt (uint32_t v[], uint32_t k[]) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */
uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
int main() {
uint32_t v[2] = {0x20, 0x10}, k[4] = {0x04, 0x03, 0x02, 0x01};
printf("Encrypt And Decrypt:\n");
printf("%08X%08X\n", v[0], v[1]);
encrypt(v, k);
printf("%08X%08X\n", v[0], v[1]);
decrypt(v, k);
printf("%08X%08X\n", v[0], v[1]);
printf("Key:\n");
printf("%08X%08X%08X%08X\n", k[0], k[1], k[2], k[3]);
return 0;
}

相对于C语言,Java显示非常臃肿,可能是本人Java水平太次了,只能写成这样子了。下面是Java的实现:

/** * @author: heiing 2013-01-20 01:20 */ public class TEA {
public static byte[] encrypt(byte[] data, byte[] key) {
int data_len = data.length; // 数据的长度
if (data_len == 0) {
return new byte[] {};
}
TEA t = new TEA();
if (!t.setKey(key)) {
return new byte[] {};
}
int group_len = 8;
int residues = data_len % group_len; // 余数
int dlen = data_len - residues;
// 用于储存加密的密文,第一字节为余数的大小
int result_len = data_len + 1;
if (residues > 0) {
result_len += group_len - residues;
}
byte[] result = new byte[result_len];
result[0] = (byte)residues;
byte[] plain = new byte[group_len];
byte[] enc = new byte[group_len];
for (int i = 0; i < dlen; i += group_len) {
for (int j = 0; j < group_len; j++) {
plain[j] = data[i + j];
}
enc = t.encrypt_group(plain);
for (int k = 0; k < group_len; k++) {
result[i + k + 1] = enc[k];
}
}
if (residues > 0) {
for (int j = 0; j < residues; j++) {
plain[j] = data[dlen + j];
}
int padding = group_len - residues;
for (int j = 0; j < padding; j++) {
plain[residues + j] = (byte)0x00;
}
enc = t.encrypt_group(plain);
for (int k = 0; k < group_len; k++) {
result[dlen + k + 1] = enc[k];
}
}
return result;
}
public static byte[] decrypt(byte[] data, byte[] key) {
int group_len = 8;
if (data.length % group_len != 1) {
return new byte[] {};
}
TEA t = new TEA();
if (!t.setKey(key)) {
return new byte[] {};
}
int data_len = data.length - 1, dlen; // 数据的长度
int residues = (int)(data[0]); // 余数
if (residues > 0) {
dlen = data_len - group_len;
} else {
dlen = data_len;
}
byte[] result = new byte[dlen + residues];
byte[] dec = new byte[group_len];
byte[] enc = new byte[group_len];
for (int i = 0; i < dlen; i += group_len) {
for (int j = 0; j < group_len; j++) {
enc[j] = data[i + j + 1];
}
dec = t.decrypt_group(enc);
for (int k = 0; k < group_len; k++) {
result[i + k] = dec[k];
}
}
if (residues > 0) {
for (int j = 0; j < group_len; j++) {
enc[j] = data[dlen + j + 1];
}
dec = t.decrypt_group(enc);
for (int k = 0; k < residues; k++) {
result[dlen + k] = dec[k];
}
}
return result;
}
/**
* 设置密钥
* @param k 密钥
* @return 密钥长度为16个byte时, 设置密钥并返回true,否则返回false
*/
public boolean setKey(byte[] k) {
if (k.length != 16) {
return false;
}
k0 = bytes_to_uint32(new byte[] {k[0], k[1], k[2], k[3]});
k1 = bytes_to_uint32(new byte[] {k[4], k[5], k[6], k[7]});
k2 = bytes_to_uint32(new byte[] {k[8], k[9], k[10], k[11]});
k3 = bytes_to_uint32(new byte[] {k[12], k[13], k[14], k[15]});
return true;
}
/**
* 设置加密的轮数,默认为32轮
* @param loops 加密轮数
* @return 轮数为16、32、64时,返回true,否则返回false
*/
public boolean setLoops(int loops) {
switch (loops) {
case 16:
case 32:
case 64:
this.loops = loops;
return true;
}
return false;
}
private static long UINT32_MAX = 0xFFFFFFFFL;
private static long BYTE_1 = 0xFFL;
private static long BYTE_2 = 0xFF00L;
private static long BYTE_3 = 0xFF0000L;
private static long BYTE_4 = 0xFF000000L;
private static long delta = 0x9E3779B9L;
private long k0, k1, k2, k3;
private int loops = 32;
/**
* 加密一组明文
* @param v 需要加密的明文
* @return 返回密文
*/
private byte[] encrypt_group(byte[] v) {
long v0 = bytes_to_uint32(new byte[] {v[0], v[1], v[2], v[3]});
long v1 = bytes_to_uint32(new byte[] {v[4], v[5], v[6], v[7]});
long sum = 0L;
long v0_xor_1 = 0L, v0_xor_2 = 0L, v0_xor_3 = 0L;
long v1_xor_1 = 0L, v1_xor_2 = 0L, v1_xor_3 = 0L;
for (int i = 0; i < loops; i++) {
sum = toUInt32(sum + delta);
v0_xor_1 = toUInt32(toUInt32(v1 << 4) + k0);
v0_xor_2 = toUInt32(v1 + sum);
v0_xor_3 = toUInt32((v1 >> 5) + k1);
v0 = toUInt32( v0 + toUInt32(v0_xor_1 ^ v0_xor_2 ^ v0_xor_3) );
v1_xor_1 = toUInt32(toUInt32(v0 << 4) + k2);
v1_xor_2 = toUInt32(v0 + sum);
v1_xor_3 = toUInt32((v0 >> 5) + k3);
System.out.printf("%08X\t%08X\t%08X\t%08X\n", i, v0, v0 >> 5, k3);
v1 = toUInt32( v1 + toUInt32(v1_xor_1 ^ v1_xor_2 ^ v1_xor_3) );
}
byte[] b0 = long_to_bytes(v0, 4);
byte[] b1 = long_to_bytes(v1, 4);
return new byte[] {b0[0], b0[1], b0[2], b0[3], b1[0], b1[1], b1[2], b1[3]};
}
/**
* 解密一组密文
* @param v 要解密的密文
* @return 返回明文
*/
private byte[] decrypt_group(byte[] v) {
long v0 = bytes_to_uint32(new byte[] {v[0], v[1], v[2], v[3]});
long v1 = bytes_to_uint32(new byte[] {v[4], v[5], v[6], v[7]});
long sum = 0xC6EF3720L, tmp = 0L;
for (int i = 0; i < loops; i++) {
tmp = toUInt32(toUInt32(v0 << 4) + k2);
v1 = toUInt32( v1 - toUInt32(tmp ^ toUInt32(v0 + sum) ^ toUInt32((v0 >> 5) + k3)) );
tmp = toUInt32(toUInt32(v1 << 4) + k0);
v0 = toUInt32( v0 - toUInt32(tmp ^ toUInt32(v1 + sum) ^ toUInt32((v1 >> 5) + k1)) );
sum = toUInt32(sum - delta);
}
byte[] b0 = long_to_bytes(v0, 4);
byte[] b1 = long_to_bytes(v1, 4);
return new byte[] {b0[0], b0[1], b0[2], b0[3], b1[0], b1[1], b1[2], b1[3]};
}
/**
* 将 long 类型的 n 转为 byte 数组,如果 len 为 4,则只返回低32位的4个byte
* @param n 需要转换的long
* @param len 若为4,则只返回低32位的4个byte,否则返回8个byte
* @return 转换后byte数组
*/
private static byte[] long_to_bytes(long n, int len) {
byte a = (byte)((n & BYTE_4) >> 24);
byte b = (byte)((n & BYTE_3) >> 16);
byte c = (byte)((n & BYTE_2) >> 8);
byte d = (byte)(n & BYTE_1);
if (len == 4) {
return new byte[] {a, b, c, d};
}
byte ha = (byte)(n >> 56);
byte hb = (byte)((n >> 48) & BYTE_1);
byte hc = (byte)((n >> 40) & BYTE_1);
byte hd = (byte)((n >> 32) & BYTE_1);
return new byte[] {ha, hb, hc, hd, a, b, c, d};
}
/**
* 将4个byte转为 Unsigned Integer 32,以 long 形式返回
* @param bs 需要转换的字节
* @return 返回 long,高32位为0,低32位视为Unsigned Integer
*/
private static long bytes_to_uint32(byte[] bs) {
return ((bs[0]<<24) & BYTE_4) +
((bs[1]<<16) & BYTE_3) +
((bs[2]<<8) & BYTE_2) +
(bs[3] & BYTE_1);
}
/**
* 将long的高32位清除,只保留低32位,低32位视为Unsigned Integer
* @param n 需要清除的long
* @return 返回高32位全为0的long
*/
private static long toUInt32(long n) {
return n & UINT32_MAX;
}
// -------------------------------------------------------
// 以下 是用于Debug的函数
// -------------------------------------------------------
private static void println_array(byte[] b) {
for (byte x : b) {
System.out.printf("%02X ", x);
}
System.out.println();
}
/*private static void println_array(long[] b) {
for (long x : b) {
System.out.printf("%016X ", x);
}
System.out.println();
}*/
private static void test() {
}
public static void main(String[] args) {
// byte[] bs = new byte[] {(byte)0xFF, (byte)0xEE, (byte)0xDD, (byte)0xCC};
// System.out.printf("%016X\n", bytes_to_uint32(bs));
// System.out.println(bytes_to_uint32(bs));
//
//
TEA t = new TEA();
byte[] pnt = new byte[] {
0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x10
};
byte[] k = new byte[] {
0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x01
};
t.setKey(k);
// byte[] enc = t.encrypt(v, k);
// byte[] dec = t.decrypt(enc, k);
byte[] enc = t.encrypt_group(pnt);
//byte[] enc = new byte[] {(byte) 0xC1, (byte) 0xC6, 0x48, 0x7A, (byte) 0x9E, 0x6F, (byte) 0xF2, 0x56};
byte[] dec = t.decrypt_group(enc);
//println_array(v_from_byte_to_long(new byte[]{ 0x7F, 0x1E, 0x55, 0x56, 0x32, 0x35, 0x65, 0x78 }));
//println_array(k_from_byte_to_long(new byte[]{ 0x7F, 0x1E, 0x55, 0x56, 0x32, 0x35, 0x65, 0x78, 0x6F, 0x1E, 0x55, 0x56, 0x32, 0x35, 0x65, 0x78 }));
//println_array(long_to_bytes((long)0x7E987654, 8));
//byte b = (byte)0xEF;
//println_array(new long[] { (b << 24) & 0xFF000000L } );
//println_array(new long[] {(byte)0xEF});
// String[] plain = new String[32];
// for (i = 0; i < 32; i++) {
// plain[i] = String.
// }
// byte[] pnt = "123".getBytes();
// byte[] enc = encrypt(pnt, k);
// byte[] dec = decrypt(enc, k);
System.out.println("Key:");
println_array(k);
System.out.println("Encrypt And Decrypt:");
println_array(pnt);
println_array(enc);
println_array(dec);
}
}

如果安装的32bit的PHP的话,那么PHP的整数应该是比较蛋疼的,下面是PHP的实现:

/** * @author: heiing 2013-01-20 03:55 */ class TEA {
public static function encrypt($data, $key) {
$data_len = strlen($data);
if (0 == $data_len) {
return '';
}
$t = new TEA();
if (!$t->setKey($key)) {
return '';
}
$group_len = 8;
$residues = $data_len % $group_len;
if ($residues > 0) {
$pad_len = $group_len - $residues;
$data .= str_repeat("\0", $pad_len);
$data_len += $pad_len;
}
$result = array(chr($residues));
for ($i = 0; $i < $data_len; $i += $group_len) {
$result[] = $t->encrypt_group(substr($data, $i, $group_len));
}
return implode('', $result);
}
public static function decrypt($data, $key) {
$group_len = 8;
$data_len = strlen($data);
if ($data_len % $group_len != 1) {
return '';
}
$t = new TEA();
if (!$t->setKey($key)) {
return '';
}
$residues = ord($data{0});
$result = array();
for ($i = 1; $i < $data_len; $i += $group_len) {
$result[] = $t->decrypt_group(substr($data, $i, $group_len));
}
if ($residues > 0) {
$lastpos = count($result) - 1;
$result[$lastpos] = substr($result[$lastpos], 0, $residues);
}
return implode('', $result);
}
/**
* 设置密钥
* @param string $key 密钥
* @return boolean 密钥长度为16个byte时, 设置密钥并返回true,否则返回false
*/
public function setKey($key) {
if (strlen($key) != 16) {
return false;
}
$this->k0 = self::bytes_to_uint32(substr($key, 0, 4));
$this->k1 = self::bytes_to_uint32(substr($key, 4, 4));
$this->k2 = self::bytes_to_uint32(substr($key, 8, 4));
$this->k3 = self::bytes_to_uint32(substr($key, 12, 4));
return true;
}
/**
* 设置加密的轮数,默认为32轮
* @param int $loops 加密轮数
* @return boolean 轮数为16、32、64时,返回true,否则返回false
*/
public function setLoops($loops) {
switch ($loops) {
case 16:
case 32:
case 64:
$this->loops = $loops;
return true;
}
return false;
}
const UINT32_MAX = 0xFFFFFFFF;
const BYTE_1 = 0xFF;
const BYTE_2 = 0xFF00;
const BYTE_3 = 0xFF0000;
const BYTE_4 = 0xFF000000;
const RSHIFT_5 = 0x07FFFFFF;
const delta = 0x9E3779B9;
private $k0 = 0, $k1 = 0, $k2 = 0, $k3 = 0;
private $loops = 32;
/**
* 加密一组明文
* @param string $v 需要加密的明文
* @return string 返回密文
*/
private function encrypt_group($v) {
$v0 = self::bytes_to_uint32(substr($v, 0, 4));
$v1 = self::bytes_to_uint32(substr($v, 4));
$sum = 0;
for ($i = 0; $i < $this->loops; ++$i) {
$sum = self::toUInt32($sum + self::delta);
$v0_xor_1 = self::toUInt32(self::toUInt32($v1 << 4) + $this->k0);
$v0_xor_2 = self::toUInt32($v1 + $sum);
$v0_xor_3 = self::toUInt32(($v1 >> 5 & self::RSHIFT_5) + $this->k1);
$v0 = self::toUInt32( $v0 + self::toUInt32($v0_xor_1 ^ $v0_xor_2 ^ $v0_xor_3) );
$v1_xor_1 = self::toUInt32(self::toUInt32($v0 << 4) + $this->k2);
$v1_xor_2 = self::toUInt32($v0 + $sum);
$v1_xor_3 = self::toUInt32(($v0 >> 5 & self::RSHIFT_5) + $this->k3);
$v1 = self::toUInt32( $v1 + self::toUInt32($v1_xor_1 ^ $v1_xor_2 ^ $v1_xor_3) );
}
return self::long_to_bytes($v0, 4) . self::long_to_bytes($v1, 4);
}
/**
* 解密一组密文
* @param string $v 要解密的密文
* @return string 返回明文
*/
private function decrypt_group($v) {
$v0 = self::bytes_to_uint32(substr($v, 0, 4));
$v1 = self::bytes_to_uint32(substr($v, 4));
$sum = 0xC6EF3720;
for ($i = 0; $i < $this->loops; ++$i) {
$v1_xor_1 = self::toUInt32(self::toUInt32($v0 << 4) + $this->k2);
$v1_xor_2 = self::toUInt32($v0 + $sum);
$v1_xor_3 = self::toUInt32(($v0 >> 5 & self::RSHIFT_5) + $this->k3);
$v1 = self::toUInt32( $v1 - self::toUInt32($v1_xor_1 ^ $v1_xor_2 ^ $v1_xor_3) );
$v0_xor_1 = self::toUInt32(self::toUInt32($v1 << 4) + $this->k0);
$v0_xor_2 = self::toUInt32($v1 + $sum);
$v0_xor_3 = self::toUInt32(($v1 >> 5 & self::RSHIFT_5) + $this->k1);
$v0 = self::toUInt32( $v0 - self::toUInt32($v0_xor_1 ^ $v0_xor_2 ^ $v0_xor_3) );
$sum = self::toUInt32($sum - self::delta);
}
return self::long_to_bytes($v0, 4) . self::long_to_bytes($v1, 4);
}
/**
* 将 long 类型的 $n 转为 byte 数组,如果 len 为 4,则只返回低32位的4个byte
* @param int $n 需要转换的long
* @param int $len 若为4,则只返回低32位的4个byte,否则返回8个byte
* @return string 转换后byte数组
*/
private static function long_to_bytes($n, $len) {
$a = (self::BYTE_4 & $n) >> 24;
$b = (self::BYTE_3 & $n) >> 16;
$c = (self::BYTE_2 & $n) >> 8;
$d = self::BYTE_1 & $n;
$p4 = pack('CCCC', $a, $b, $c, $d);
if (4 == $len) {
return $p4;
}
return self::long_to_bytes($n >> 32, 4) . $p4;
}
/**
* 将4个byte转为 Unsigned Integer 32,以 long 形式返回
* @param string $bs 需要转换的字节
* @return int 返回 long
*/
private static function bytes_to_uint32($bs) {
$a = (0xFFFFFFFF & ord($bs{0})) << 24;
$b = (0xFFFFFFFF & ord($bs{1})) << 16;
$c = (0xFFFFFFFF & ord($bs{2})) << 8;
$d = ord($bs{3});
return $a + $b + $c + $d;
}
/**
* 将long的高32位清除,只保留低32位,低32位视为Unsigned Integer
* @param int $n 需要清除的long
* @return int 返回高32位全为0的long
*/
private static function toUInt32($n) {
return $n & self::UINT32_MAX;
}
// -------------------------------------------------------
// 以下 是用于Debug的函数
// -------------------------------------------------------
public static function printx($s) {
$l = strlen($s);
for($i = 0; $i < $l; ++$i) {
printf('%1$02X ', ord($s{$i}));
}
echo "\n";
}
public static function main() {
echo "\n---------start---------\n";
$zero = str_repeat(chr(0), 3);
$key = $zero . chr(4) . $zero . chr(3) . $zero . chr(2) . $zero . chr(1);
$pnt = $zero . chr(0x20) . $zero . chr(0x10);
$t = new TEA();
$t->setKey($key);
$enc = $t->encrypt_group($pnt);
$dec = $t->decrypt_group($enc);
// $pnt = '123';
// $enc = TEA::encrypt($pnt, $key);
// $dec = TEA::decrypt($enc, $key);
printf("Key:\n");
self::printx($key);
printf("Encrypt And Decrypt:\n");
self::printx($pnt);
self::printx($enc);
self::printx($dec);
/*//printf('%1$0X ', 0x9E3779B9);
printf('%1$0X ', self::toUInt32( self::bytes_to_uint32(chr(0xFF) . chr(0xEE) . chr(0xDD) . chr(0xCC)) * 2));
echo "\n";
self::printx(self::long_to_bytes(0x12345678, 4));
self::printx(self::long_to_bytes(0x98, 4));*/
}
}

经过这一次实践,造成不同语言的加密结果不一致的原因是整数类型的不一致。TEA要求使用32位无符号整数进行运算,而Java与PHP的没有无符号的整数,Java可以使用Long来兼容,而32位的PHP就稍为麻烦一些,在位运算时需要特别留意。Java要注意的是需要使用L后缀来表示一个Long型常量,比如 delta,一不小心就成了负的整数。32位的PHP在右移时,很容易把符号位称过来,又成了负数,因此右移5位时,需要0x07FFFFFF来修正。
|