最近在使用Java实现QQ的基本通讯协议,QQ的通讯协议中使用MD5算法两次加密密码,并将通讯中的所有数据包使用TEA算法进行加密。MD5加密可以通过Java API提供的MessageDigest直接进行,而TEA加密则需自己编写。
TEA加密每次使用128bit密钥加密64bit数据,生成加密数据亦为64bit,为典型的对称加密算法。该算法使用C语言实现非常简单,但使用Java进行实现时,受制于Java的特性,在实现上稍有复杂。
代码如下:
public class TEA {
/
value is 0xffffffff <br> filter long to unsigned int
*/
public static Long UIFILTER = Long.decode("0xffffffff");
/
value is 0xff <br> filter short to unsigned byte
/
public static Short UBFILTER = Short.decode("0xff");
/** Encipher method <br>
<p>64bit plaintext, 128bit key @param v Plaintext
@param k Key @return
/
public static Short[] encipher(Short [] v, Short [] k)
{
Long[] vL = shortToLong(v);
Long[] kL = shortToLong(k);
Long[] wL = new Long[vL.length];
Short[] w = null;
long y = vL[0];
long z = vL[1];
long a = kL[0];
long b = kL[1];
long c = kL[2];
long d = kL[3];
long n = 0x10; / do encrypt 16 (0x10) times /
long sum = 0;
long delta = Long.decode("0x9E3779B9"); / 0x9E3779B9 - 0x100000000 = -0x61C88647 /
while (n– > 0) {
sum += delta;
sum &= UIFILTER;
y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
y &= UIFILTER;
z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
z &= UIFILTER;
}
wL[0] = y;
wL[1] = z;
w = longToShort(wL);
return w;
}
/** Decipher method <br>
<p>64bit ciphertext, 128bit key @param v Ciphertext
@param k Key @return
/
public static Short[] decipher(Short [] v, Short [] k)
{
Long[] vL = shortToLong(v);
Long[] kL = shortToLong(k);
Long[] wL = new Long[vL.length];
Short[] w = null;
long y = vL[0];
long z = vL[1];
long a = kL[0];
long b = kL[1];
long c = kL[2];
long d = kL[3];
long n = 0x10;
/ sum = delta << 4, in general sum = delta n /
long sum = Long.decode("0xE3779B90");
long delta = Long.decode("0x9E3779B9");
// use filter to get unsigned int
// use ~x +1 instead -x
while (n– > 0) {
z += (~(((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d)) + 1);
z &= UIFILTER;
y += (~(((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b)) + 1);
y &= UIFILTER;
sum += ((~delta) + 1);
sum &= UIFILTER;
}
wL[0] = y;
wL[1] = z;
w = longToShort(wL);
return w;
}
/
convert bytes to longs, put every 4 bytes to 1 long <br> <p>use long instead unsigned int<br>
use short instead unsigned byte @param source - the Short [] that save unsigned bytes to convert
@return the Long [] save unsigned int /
public static Long [] shortToLong(Short [] source){
int sourlen = source.length;
int turn = sourlen / 4; // how many turns
int remainder = sourlen % 4; // how many bytes in first turn
int tarlen = turn + (remainder == 0 ? 0 : 1);
Long [] target = new Long[tarlen];
for(int i = 0; i < target.length; i++){
target[i] = Long.parseLong("0");
}
int iter = 0;
int turnIter = 0;
for(turnIter = 0; turnIter < tarlen; turnIter++){
for(iter = 0; iter < 4; iter++){
target[turnIter] <<= 8;
if((turnIter != turn - 1) || ((turnIter == turn - 1) && (iter < remainder || remainder == 0)))
target[turnIter] += source[turnIter * 4 + iter];
}
}
return target;
}
/
convert longs to bytes <br> <p>cut 1 long(32bit valid to instead unsigned int) to 4 short(8bit valid to instead unsigned byte) <br>
use long instead unsigned int <br> use short instead unsigned byte <br>
@param source - the Long [] that save unsigned int to convert @return the Short [] that save unsigned bytes
/
public static Short [] longToShort(Long [] source){
int sourlen = source.length;
Short [] target = new Short[sourlen 4];
int turn = target.length % 4;
int iter = 0;
int move = 0;
for(iter = 0; iter < target.length; iter++){
move = 8 * (3 - (iter % 4));
target[iter] = Short.parseShort(Long.toString((source[iter / 4] & (UBFILTER << move)) >> move));
}
return target;
}
}
代码中需要注意的几点如下:
1. 由于Java种没有无符号数的概念,故如int, long, byte等基础类型其最高位均为符号位,故不能使用该种类型进行无符号运算。本代码中使用long代替unsigned int,使用short代替unsigned byte。
2. 相
对C语言代码,在解密时不使用减法,而是用取反加一的方式将减法转换为加法。这是由于Java中Long类型是有符号的,其进行的计算亦为有符号计算。若
被减数小于减数时,减法结果将为负数,这不符合无符号减法的规则。转化为加法运算后,将其高位溢出,则可得无符号减法的结果。
3. 由于Java中不允许高位溢出,会抛出异常,故高位溢出需要手工完成,即将运算结果与0xffffffff或0xff进行与运算(视具体类型而定)。
测试类如下:
public class TestPoint {
public static void main(String [] args){
Long[] keyLong = new Long[4];
//加密解密所用的KEY
keyLong[0] = Long.decode("0x789f5645");
keyLong[1] = Long.decode("0xf68bd5a4");
keyLong[2] = Long.decode("0x81963ffa");
keyLong[3] = Long.decode("0x458fac58");
Short[] key = TEA.longToShort(keyLong);
Short[] info = new Short[]{
1, 2, 3, 4, 5, 6, 7, 8
};
System.out.print("原数据:");
for(Short i : info)
System.out.print(i + " ");
System.out.println();
Short[] secretInfo = TEA.encipher(info, key);
System.out.print("加密后的数据:");
for(Short i : secretInfo)
System.out.print(i + " ");
System.out.println();
Short[] decryptInfo = TEA.decipher(secretInfo, key);
System.out.print("解密后的数据:");
for(Short i : decryptInfo)
System.out.print(i + " ");
}
}
TEA加密每次使用128bit密钥加密64bit数据,生成加密数据亦为64bit,为典型的对称加密算法。该算法使用C语言实现非常简单,但使用Java进行实现时,受制于Java的特性,在实现上稍有复杂。
代码如下:
public class TEA {
/
value is 0xffffffff <br> filter long to unsigned int
*/
public static Long UIFILTER = Long.decode("0xffffffff");
/
value is 0xff <br> filter short to unsigned byte
/
public static Short UBFILTER = Short.decode("0xff");
/** Encipher method <br>
<p>64bit plaintext, 128bit key @param v Plaintext
@param k Key @return
/
public static Short[] encipher(Short [] v, Short [] k)
{
Long[] vL = shortToLong(v);
Long[] kL = shortToLong(k);
Long[] wL = new Long[vL.length];
Short[] w = null;
long y = vL[0];
long z = vL[1];
long a = kL[0];
long b = kL[1];
long c = kL[2];
long d = kL[3];
long n = 0x10; / do encrypt 16 (0x10) times /
long sum = 0;
long delta = Long.decode("0x9E3779B9"); / 0x9E3779B9 - 0x100000000 = -0x61C88647 /
while (n– > 0) {
sum += delta;
sum &= UIFILTER;
y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
y &= UIFILTER;
z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
z &= UIFILTER;
}
wL[0] = y;
wL[1] = z;
w = longToShort(wL);
return w;
}
/** Decipher method <br>
<p>64bit ciphertext, 128bit key @param v Ciphertext
@param k Key @return
/
public static Short[] decipher(Short [] v, Short [] k)
{
Long[] vL = shortToLong(v);
Long[] kL = shortToLong(k);
Long[] wL = new Long[vL.length];
Short[] w = null;
long y = vL[0];
long z = vL[1];
long a = kL[0];
long b = kL[1];
long c = kL[2];
long d = kL[3];
long n = 0x10;
/ sum = delta << 4, in general sum = delta n /
long sum = Long.decode("0xE3779B90");
long delta = Long.decode("0x9E3779B9");
// use filter to get unsigned int
// use ~x +1 instead -x
while (n– > 0) {
z += (~(((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d)) + 1);
z &= UIFILTER;
y += (~(((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b)) + 1);
y &= UIFILTER;
sum += ((~delta) + 1);
sum &= UIFILTER;
}
wL[0] = y;
wL[1] = z;
w = longToShort(wL);
return w;
}
/
convert bytes to longs, put every 4 bytes to 1 long <br> <p>use long instead unsigned int<br>
use short instead unsigned byte @param source - the Short [] that save unsigned bytes to convert
@return the Long [] save unsigned int /
public static Long [] shortToLong(Short [] source){
int sourlen = source.length;
int turn = sourlen / 4; // how many turns
int remainder = sourlen % 4; // how many bytes in first turn
int tarlen = turn + (remainder == 0 ? 0 : 1);
Long [] target = new Long[tarlen];
for(int i = 0; i < target.length; i++){
target[i] = Long.parseLong("0");
}
int iter = 0;
int turnIter = 0;
for(turnIter = 0; turnIter < tarlen; turnIter++){
for(iter = 0; iter < 4; iter++){
target[turnIter] <<= 8;
if((turnIter != turn - 1) || ((turnIter == turn - 1) && (iter < remainder || remainder == 0)))
target[turnIter] += source[turnIter * 4 + iter];
}
}
return target;
}
/
convert longs to bytes <br> <p>cut 1 long(32bit valid to instead unsigned int) to 4 short(8bit valid to instead unsigned byte) <br>
use long instead unsigned int <br> use short instead unsigned byte <br>
@param source - the Long [] that save unsigned int to convert @return the Short [] that save unsigned bytes
/
public static Short [] longToShort(Long [] source){
int sourlen = source.length;
Short [] target = new Short[sourlen 4];
int turn = target.length % 4;
int iter = 0;
int move = 0;
for(iter = 0; iter < target.length; iter++){
move = 8 * (3 - (iter % 4));
target[iter] = Short.parseShort(Long.toString((source[iter / 4] & (UBFILTER << move)) >> move));
}
return target;
}
}
代码中需要注意的几点如下:
1. 由于Java种没有无符号数的概念,故如int, long, byte等基础类型其最高位均为符号位,故不能使用该种类型进行无符号运算。本代码中使用long代替unsigned int,使用short代替unsigned byte。
2. 相
对C语言代码,在解密时不使用减法,而是用取反加一的方式将减法转换为加法。这是由于Java中Long类型是有符号的,其进行的计算亦为有符号计算。若
被减数小于减数时,减法结果将为负数,这不符合无符号减法的规则。转化为加法运算后,将其高位溢出,则可得无符号减法的结果。
3. 由于Java中不允许高位溢出,会抛出异常,故高位溢出需要手工完成,即将运算结果与0xffffffff或0xff进行与运算(视具体类型而定)。
测试类如下:
public class TestPoint {
public static void main(String [] args){
Long[] keyLong = new Long[4];
//加密解密所用的KEY
keyLong[0] = Long.decode("0x789f5645");
keyLong[1] = Long.decode("0xf68bd5a4");
keyLong[2] = Long.decode("0x81963ffa");
keyLong[3] = Long.decode("0x458fac58");
Short[] key = TEA.longToShort(keyLong);
Short[] info = new Short[]{
1, 2, 3, 4, 5, 6, 7, 8
};
System.out.print("原数据:");
for(Short i : info)
System.out.print(i + " ");
System.out.println();
Short[] secretInfo = TEA.encipher(info, key);
System.out.print("加密后的数据:");
for(Short i : secretInfo)
System.out.print(i + " ");
System.out.println();
Short[] decryptInfo = TEA.decipher(secretInfo, key);
System.out.print("解密后的数据:");
for(Short i : decryptInfo)
System.out.print(i + " ");
}
}