TEA 加密解法,統一了C語言、Java與PHP的運算結果


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

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM