去年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来修正。