JAVA C QQ 的加密解密算法 高手看能不能转成DELPHI ( 积分: 100 )

  • 主题发起人 主题发起人 wqy888
  • 开始时间 开始时间
W

wqy888

Unregistered / Unconfirmed
GUEST, unregistred user!
* 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
* 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
* f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain ˆ preCrypt) ˆ
* prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt ˆ prePlain) ˆ
* preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
* 填充的字节数与原始明文长度有关,填充的方法是:
*
* <pre>
* <code>
*
* ------- 消息填充算法 -----------
* a = (明文长度 + 10) mod 8
* if(a 不等于 0) a = 8 - a;
* b = 随机数 &amp; 0xF8 | a; 这个的作用是把a的值保存了下来
* plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
* plain[1 至 a+2] = 随机数 &amp; 0xFF; 这里用随机数填充明文的第1到第a+2个字节
* plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
* plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
* ------- 消息填充算法 ------------
*
* </code>
* </pre>
*
*/
public class Crypter {
// 指向当前的明文块
private byte[] plain;
// 这指向前面一个明文块
private byte[] prePlain;
// 输出的密文或者明文
private byte[] out;
// 当前加密的密文位置和上一次加密的密文块位置,他们相差8
private int crypt, preCrypt;
// 当前处理的加密解密块的位置
private int pos;
// 填充数
private int padding;
// 密钥
private byte[] key;
// 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
// 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
private boolean header = true;
// 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
// 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
private int contextStart;
// 随机数对象
private static Random random = Util.random();

/**
* 解密
* @param in 密文
* @param offset 密文开始的位置
* @param len 密文长度
* @param k 密钥
* @return 明文
*/
public byte[] decrypt(byte[] in, int offset, int len, byte[] k) {
crypt = preCrypt = 0;
this.key = k;
int count;
byte[] m = new byte[offset + 8];

// 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
if((len % 8 != 0) || (len < 16)) return null;
// 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
prePlain = decipher(in, offset);
pos = prePlain[0] &amp; 0x7;
// 得到真正明文的长度
count = len - pos - 10;
// 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
if(count < 0) return null;

// 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
// 第一个8字节块也没有preCrypt,所有这里建一个全0的
for(int i = offset; i < m.length; i++)
m = 0;
// 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
out = new byte[count];
// 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
preCrypt = 0;
// 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
crypt = 8;
// 自然这个也是8
contextStart = 8;
// 加1,和加密算法是对应的
pos++;

// 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
// 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
// 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
// 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
padding = 1;
while(padding <= 2) {
if(pos < 8) {
pos++;
padding++;
}
if(pos == 8) {
m = in;
if(!decrypt8Bytes(in, offset, len)) return null;
}
}

// 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
// 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
int i = 0;
while(count != 0) {
if(pos < 8) {
out = (byte)(m[offset + preCrypt + pos] ^ prePlain[pos]);
i++;
count--;
pos++;
}
if(pos == 8) {
m = in;
preCrypt = crypt - 8;
if(!decrypt8Bytes(in, offset, len))
return null;
}
}

// 最后的解密部分,上面一个while已经把明文都解出来了,到了这里还剩下什么?对了,还剩下尾部的填充,应该全是0
// 所以这里有检查是否解密了之后是0,如果不是的话那肯定出错了,所以返回null
for(padding = 1; padding < 8; padding++) {
if(pos < 8) {
if((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0)
return null;
pos++;
}
if(pos == 8) {
m = in;
preCrypt = crypt;
if(!decrypt8Bytes(in, offset, len))
return null;
}
}
return out;
}

/**
* @param in
* 需要被解密的密文
* @param inLen
* 密文长度
* @param k
* 密钥
* @return Message 已解密的消息
*/
public byte[] decrypt(byte[] in, byte[] k) {
return decrypt(in, 0, in.length, k);
}

/**
* 加密
* @param in 明文字节数组
* @param offset 开始加密的偏移
* @param len 加密长度
* @param k 密钥
* @return 密文字节数组
*/
public byte[] encrypt(byte[] in, int offset, int len, byte[] k) {
plain = new byte[8];
prePlain = new byte[8];
pos = 1;
padding = 0;
crypt = preCrypt = 0;
this.key = k;
header = true;

// 计算头部填充字节数
pos = (len + 0x0A) % 8;
if(pos != 0)
pos = 8 - pos;
// 计算输出的密文长度
out = new byte[len + pos + 10];
// 这里的操作把pos存到了plain的第一个字节里面
// 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
plain[0] = (byte)((rand() &amp; 0xF8) | pos);

// 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
for(int i = 1; i <= pos; i++)
plain = (byte)(rand() &amp; 0xFF);
pos++;
// 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
for(int i = 0; i < 8; i++)
prePlain = 0x0;

// 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
padding = 1;
while(padding <= 2) {
if(pos < 8) {
plain[pos++] = (byte)(rand() &amp; 0xFF);
padding++;
}
if(pos == 8)
encrypt8Bytes();
}

// 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
int i = offset;
while(len > 0) {
if(pos < 8) {
plain[pos++] = in[i++];
len--;
}
if(pos == 8)
encrypt8Bytes();
}

// 最后填上0,以保证是8字节的倍数
padding = 1;
while(padding <= 7) {
if(pos < 8) {
plain[pos++] = 0x0;
padding++;
}
if(pos == 8)
encrypt8Bytes();
}

return out;
}

/**
* @param in
* 需要加密的明文
* @param inLen
* 明文长度
* @param k
* 密钥
* @return Message 密文
*/
public byte[] encrypt(byte[] in, byte[] k) {
return encrypt(in, 0, in.length, k);
}

/**
* 加密一个8字节块
*
* @param in
* 明文字节数组
* @return
* 密文字节数组
*/
private byte[] encipher(byte[] in) {
try {
// 迭代次数,16次
int loop = 0x10;
// 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
// 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
// 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
long y = Util.getUnsignedInt(in, 0, 4);
long z = Util.getUnsignedInt(in, 4, 4);
long a = Util.getUnsignedInt(key, 0, 4);
long b = Util.getUnsignedInt(key, 4, 4);
long c = Util.getUnsignedInt(key, 8, 4);
long d = Util.getUnsignedInt(key, 12, 4);
// 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
// 这个数是TEA算法的delta,实际是就是sqr(5)-1 * 2^31
long sum = 0;
long delta = 0x9E3779B9;
delta &amp;= 0xFFFFFFFFL;

// 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
while (loop-- > 0) {
sum += delta;
sum &amp;= 0xFFFFFFFFL;
y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
y &amp;= 0xFFFFFFFFL;
z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
z &amp;= 0xFFFFFFFFL;
}

// 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt((int)y);
dos.writeInt((int)z);
dos.close();
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}

/**
* 解密从offset开始的8字节密文
*
* @param in
* 密文字节数组
* @param offset
* 密文开始位置
* @return
* 明文
*/
private byte[] decipher(byte[] in, int offset) {
try {
// 迭代次数,16次
int loop = 0x10;
// 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
// 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
// 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
long y = Util.getUnsignedInt(in, offset, 4);
long z = Util.getUnsignedInt(in, offset + 4, 4);
long a = Util.getUnsignedInt(key, 0, 4);
long b = Util.getUnsignedInt(key, 4, 4);
long c = Util.getUnsignedInt(key, 8, 4);
long d = Util.getUnsignedInt(key, 12, 4);
// 算法的一些控制变量,为什么sum在这里也有数了呢,这个sum嘛就是和迭代次数有关系了
// 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
// 得到什么? Yeah,得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样
// 才能解密呀~~
long sum = 0xE3779B90;
sum &amp;= 0xFFFFFFFFL;
long delta = 0x9E3779B9;
delta &amp;= 0xFFFFFFFFL;

// 迭代开始了, #_#
while(loop-- > 0) {
z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
z &amp;= 0xFFFFFFFFL;
y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
y &amp;= 0xFFFFFFFFL;
sum -= delta;
sum &amp;= 0xFFFFFFFFL;
}

// 输出明文,注意要转成int
ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt((int)y);
dos.writeInt((int)z);
dos.close();
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}

/**
* 解密
*
* @param in
* 密文
* @return
* 明文
*/
private byte[] decipher(byte[] in) {
return decipher(in, 0);
}

/**
* 加密8字节
*/
private void encrypt8Bytes() {
// 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
for(pos = 0; pos < 8; pos++) {
if(header) plain[pos] ^= prePlain[pos];
else plain[pos] ^= out[preCrypt + pos];
}
// 这个完成到了我上面说的 f(plain ^ preCrypt)
byte[] crypted = encipher(plain);
// 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
System.arraycopy(crypted, 0, out, crypt, 8);

// 这个就是完成到了 f(plain ^ preCrypt) ^ prePlain,ok,完成了,下面拷贝一下就行了
for(pos = 0; pos < 8; pos++)
out[crypt + pos] ^= prePlain[pos];
System.arraycopy(plain, 0, prePlain, 0, 8);

// 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
preCrypt = crypt;
crypt += 8;
pos = 0;
header = false;
}

/**
* 解密8个字节
*
* @param in
* 密文字节数组
* @param offset
* 从何处开始解密
* @param len
* 密文的长度
* @return
* true表示解密成功
*/
private boolean decrypt8Bytes(byte[] in , int offset, int len) {
// 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
for(pos = 0; pos < 8; pos++) {
if(contextStart + pos >= len)
return true;
prePlain[pos] ^= in[offset + crypt + pos];
}

// 好,这里执行到了 d(crypt ^ prePlain)
prePlain = decipher(prePlain);
if(prePlain == null)
return false;

// 解密完成,wait,没完成哦,最后一步没做哦?
// 这里最后一步放到decrypt里面去做了,因为解密的步骤毕竟还是不太一样嘛
// 调整这些变量的值先
contextStart += 8;
crypt += 8;
pos = 0;
return true;
}

/**
* 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值
* 随机因子可以使相同的明文每次加密出来的密文都不一样
*
* @return
* 随机因子
*/
private int rand() {
return random.nextInt();
}
}


有兴趣的高手加我QQ 一起研究:
78926878
 
* 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
* 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
* f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain &amp;#710; preCrypt) &amp;#710;
* prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt &amp;#710; prePlain) &amp;#710;
* preCrypt 此外,算法有它的填充机制,其会在明文前和明文后分别填充一定的字节数,以保证明文长度是8字节的倍数
* 填充的字节数与原始明文长度有关,填充的方法是:
*
* <pre>
* <code>
*
* ------- 消息填充算法 -----------
* a = (明文长度 + 10) mod 8
* if(a 不等于 0) a = 8 - a;
* b = 随机数 &amp; 0xF8 | a; 这个的作用是把a的值保存了下来
* plain[0] = b; 然后把b做为明文的第0个字节,这样第0个字节就保存了a的信息,这个信息在解密时就要用来找到真正明文的起始位置
* plain[1 至 a+2] = 随机数 &amp; 0xFF; 这里用随机数填充明文的第1到第a+2个字节
* plain[a+3 至 a+3+明文长度-1] = 明文; 从a+3字节开始才是真正的明文
* plain[a+3+明文长度, 最后] = 0; 在最后,填充0,填充到总长度为8的整数为止。到此为止,结束了,这就是最后得到的要加密的明文内容
* ------- 消息填充算法 ------------
*
* </code>
* </pre>
*
*/
public class Crypter {
// 指向当前的明文块
private byte[] plain;
// 这指向前面一个明文块
private byte[] prePlain;
// 输出的密文或者明文
private byte[] out;
// 当前加密的密文位置和上一次加密的密文块位置,他们相差8
private int crypt, preCrypt;
// 当前处理的加密解密块的位置
private int pos;
// 填充数
private int padding;
// 密钥
private byte[] key;
// 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
// 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
private boolean header = true;
// 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
// 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
private int contextStart;
// 随机数对象
private static Random random = Util.random();

/**
* 解密
* @param in 密文
* @param offset 密文开始的位置
* @param len 密文长度
* @param k 密钥
* @return 明文
*/
public byte[] decrypt(byte[] in, int offset, int len, byte[] k) {
crypt = preCrypt = 0;
this.key = k;
int count;
byte[] m = new byte[offset + 8];

// 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
if((len % 8 != 0) || (len < 16)) return null;
// 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
prePlain = decipher(in, offset);
pos = prePlain[0] &amp; 0x7;
// 得到真正明文的长度
count = len - pos - 10;
// 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
if(count < 0) return null;

// 这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
// 第一个8字节块也没有preCrypt,所有这里建一个全0的
for(int i = offset; i < m.length; i++)
m = 0;
// 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
out = new byte[count];
// 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
preCrypt = 0;
// 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
crypt = 8;
// 自然这个也是8
contextStart = 8;
// 加1,和加密算法是对应的
pos++;

// 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
// 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
// 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
// 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
padding = 1;
while(padding <= 2) {
if(pos < 8) {
pos++;
padding++;
}
if(pos == 8) {
m = in;
if(!decrypt8Bytes(in, offset, len)) return null;
}
}

// 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
// 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
int i = 0;
while(count != 0) {
if(pos < 8) {
out = (byte)(m[offset + preCrypt + pos] ^ prePlain[pos]);
i++;
count--;
pos++;
}
if(pos == 8) {
m = in;
preCrypt = crypt - 8;
if(!decrypt8Bytes(in, offset, len))
return null;
}
}

// 最后的解密部分,上面一个while已经把明文都解出来了,到了这里还剩下什么?对了,还剩下尾部的填充,应该全是0
// 所以这里有检查是否解密了之后是0,如果不是的话那肯定出错了,所以返回null
for(padding = 1; padding < 8; padding++) {
if(pos < 8) {
if((m[offset + preCrypt + pos] ^ prePlain[pos]) != 0)
return null;
pos++;
}
if(pos == 8) {
m = in;
preCrypt = crypt;
if(!decrypt8Bytes(in, offset, len))
return null;
}
}
return out;
}

/**
* @param in
* 需要被解密的密文
* @param inLen
* 密文长度
* @param k
* 密钥
* @return Message 已解密的消息
*/
public byte[] decrypt(byte[] in, byte[] k) {
return decrypt(in, 0, in.length, k);
}

/**
* 加密
* @param in 明文字节数组
* @param offset 开始加密的偏移
* @param len 加密长度
* @param k 密钥
* @return 密文字节数组
*/
public byte[] encrypt(byte[] in, int offset, int len, byte[] k) {
plain = new byte[8];
prePlain = new byte[8];
pos = 1;
padding = 0;
crypt = preCrypt = 0;
this.key = k;
header = true;

// 计算头部填充字节数
pos = (len + 0x0A) % 8;
if(pos != 0)
pos = 8 - pos;
// 计算输出的密文长度
out = new byte[len + pos + 10];
// 这里的操作把pos存到了plain的第一个字节里面
// 0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
plain[0] = (byte)((rand() &amp; 0xF8) | pos);

// 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
for(int i = 1; i <= pos; i++)
plain = (byte)(rand() &amp; 0xFF);
pos++;
// 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
for(int i = 0; i < 8; i++)
prePlain = 0x0;

// 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
padding = 1;
while(padding <= 2) {
if(pos < 8) {
plain[pos++] = (byte)(rand() &amp; 0xFF);
padding++;
}
if(pos == 8)
encrypt8Bytes();
}

// 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
int i = offset;
while(len > 0) {
if(pos < 8) {
plain[pos++] = in[i++];
len--;
}
if(pos == 8)
encrypt8Bytes();
}

// 最后填上0,以保证是8字节的倍数
padding = 1;
while(padding <= 7) {
if(pos < 8) {
plain[pos++] = 0x0;
padding++;
}
if(pos == 8)
encrypt8Bytes();
}

return out;
}

/**
* @param in
* 需要加密的明文
* @param inLen
* 明文长度
* @param k
* 密钥
* @return Message 密文
*/
public byte[] encrypt(byte[] in, byte[] k) {
return encrypt(in, 0, in.length, k);
}

/**
* 加密一个8字节块
*
* @param in
* 明文字节数组
* @return
* 密文字节数组
*/
private byte[] encipher(byte[] in) {
try {
// 迭代次数,16次
int loop = 0x10;
// 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
// 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
// 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
long y = Util.getUnsignedInt(in, 0, 4);
long z = Util.getUnsignedInt(in, 4, 4);
long a = Util.getUnsignedInt(key, 0, 4);
long b = Util.getUnsignedInt(key, 4, 4);
long c = Util.getUnsignedInt(key, 8, 4);
long d = Util.getUnsignedInt(key, 12, 4);
// 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
// 这个数是TEA算法的delta,实际是就是sqr(5)-1 * 2^31
long sum = 0;
long delta = 0x9E3779B9;
delta &amp;= 0xFFFFFFFFL;

// 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
while (loop-- > 0) {
sum += delta;
sum &amp;= 0xFFFFFFFFL;
y += ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
y &amp;= 0xFFFFFFFFL;
z += ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
z &amp;= 0xFFFFFFFFL;
}

// 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt((int)y);
dos.writeInt((int)z);
dos.close();
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}

/**
* 解密从offset开始的8字节密文
*
* @param in
* 密文字节数组
* @param offset
* 密文开始位置
* @return
* 明文
*/
private byte[] decipher(byte[] in, int offset) {
try {
// 迭代次数,16次
int loop = 0x10;
// 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
// 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
// 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
long y = Util.getUnsignedInt(in, offset, 4);
long z = Util.getUnsignedInt(in, offset + 4, 4);
long a = Util.getUnsignedInt(key, 0, 4);
long b = Util.getUnsignedInt(key, 4, 4);
long c = Util.getUnsignedInt(key, 8, 4);
long d = Util.getUnsignedInt(key, 12, 4);
// 算法的一些控制变量,为什么sum在这里也有数了呢,这个sum嘛就是和迭代次数有关系了
// 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
// 得到什么? Yeah,得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样
// 才能解密呀~~
long sum = 0xE3779B90;
sum &amp;= 0xFFFFFFFFL;
long delta = 0x9E3779B9;
delta &amp;= 0xFFFFFFFFL;

// 迭代开始了, #_#
while(loop-- > 0) {
z -= ((y << 4) + c) ^ (y + sum) ^ ((y >>> 5) + d);
z &amp;= 0xFFFFFFFFL;
y -= ((z << 4) + a) ^ (z + sum) ^ ((z >>> 5) + b);
y &amp;= 0xFFFFFFFFL;
sum -= delta;
sum &amp;= 0xFFFFFFFFL;
}

// 输出明文,注意要转成int
ByteArrayOutputStream baos = new ByteArrayOutputStream(8);
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt((int)y);
dos.writeInt((int)z);
dos.close();
return baos.toByteArray();
} catch (IOException e) {
return null;
}
}

/**
* 解密
*
* @param in
* 密文
* @return
* 明文
*/
private byte[] decipher(byte[] in) {
return decipher(in, 0);
}

/**
* 加密8字节
*/
private void encrypt8Bytes() {
// 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
for(pos = 0; pos < 8; pos++) {
if(header) plain[pos] ^= prePlain[pos];
else plain[pos] ^= out[preCrypt + pos];
}
// 这个完成到了我上面说的 f(plain ^ preCrypt)
byte[] crypted = encipher(plain);
// 这个没什么,就是拷贝一下,java不像c,所以我只好这么干,c就不用这一步了
System.arraycopy(crypted, 0, out, crypt, 8);

// 这个就是完成到了 f(plain ^ preCrypt) ^ prePlain,ok,完成了,下面拷贝一下就行了
for(pos = 0; pos < 8; pos++)
out[crypt + pos] ^= prePlain[pos];
System.arraycopy(plain, 0, prePlain, 0, 8);

// 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
preCrypt = crypt;
crypt += 8;
pos = 0;
header = false;
}

/**
* 解密8个字节
*
* @param in
* 密文字节数组
* @param offset
* 从何处开始解密
* @param len
* 密文的长度
* @return
* true表示解密成功
*/
private boolean decrypt8Bytes(byte[] in , int offset, int len) {
// 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
for(pos = 0; pos < 8; pos++) {
if(contextStart + pos >= len)
return true;
prePlain[pos] ^= in[offset + crypt + pos];
}

// 好,这里执行到了 d(crypt ^ prePlain)
prePlain = decipher(prePlain);
if(prePlain == null)
return false;

// 解密完成,wait,没完成哦,最后一步没做哦?
// 这里最后一步放到decrypt里面去做了,因为解密的步骤毕竟还是不太一样嘛
// 调整这些变量的值先
contextStart += 8;
crypt += 8;
pos = 0;
return true;
}

/**
* 这是个随机因子产生器,用来填充头部的,如果为了调试,可以用一个固定值
* 随机因子可以使相同的明文每次加密出来的密文都不一样
*
* @return
* 随机因子
*/
private int rand() {
return random.nextInt();
}
}


有兴趣的高手加我QQ 一起研究:
78926878
 
熟悉C的看一下这个!!!!
// START OF FILE
/*****************************************************************************/
/*Notes: (OICQ uses 0x10 iterations, and modified something...)

IN : 64 bits of data in v[0] - v[1].
OUT: 64 bits of data in w[0] - w[1].
KEY: 128 bits of key in k[0] - k[3].

delta is chosen to be the real part of
the golden ratio: Sqrt(5/4) - 1/2 ~ 0.618034 multiplied by 2^32.

0x61C88647 is what we can track on the ASM codes.!!
*/

#ifndef _WIN32
#include <arpa/inet.h>
#else
#include &quot;win32dep.h&quot;
#endif

#include <string.h>

#include &quot;crypt.h&quot;

/*****************************************************************************/
void qq_encipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w)
{
register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0, delta = 0x9E3779B9; /* 0x9E3779B9 - 0x100000000 = -0x61C88647 */

while (n-- > 0) {
sum += delta;
y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
} // while

w[0] = htonl(y);
w[1] = htonl(z);
} // qq_enciper

/*****************************************************************************/
void qq_decipher(unsigned long *const v, const unsigned long *const k, unsigned long *const w)
{
register unsigned long y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0xE3779B90, // why this ? must be related with n value
delta = 0x9E3779B9;

/* sum = delta<<5, in general sum = delta * n */
while (n-- > 0) {
z -= ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
y -= ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
sum -= delta;
}

w[0] = htonl(y);
w[1] = htonl(z);
} // qq_decipher

/********************************************************************
* encrypt part
*******************************************************************/

void qq_encrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_prt)
{
unsigned char plain[8], // plain text buffer
plain_pre_8[8], // plain text buffer, previous 8 bytes
*crypted, // crypted text
*crypted_pre_8, // crypted test, previous 8 bytes
*inp; // current position in instr
int pos_in_byte = 1, // loop in the byte
is_header = 1, // header is one byte
count = 0, // number of bytes being crypted
padding = 0; // number of padding stuff

int rand(void) { // it can be the real random seed function
return 0xdead;
} // override with number, convenient for debug

/*** we encrypt every eight byte ***/
void encrypt_every_8_byte(void) {
for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
if (is_header) {
plain[pos_in_byte] ^= plain_pre_8[pos_in_byte];
} else {
plain[pos_in_byte] ^= crypted_pre_8[pos_in_byte];
}
} // prepare plain text
qq_encipher((unsigned long *) plain, (unsigned long *) key, (unsigned long *) crypted); // encrypt it

for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
crypted[pos_in_byte] ^= plain_pre_8[pos_in_byte];
}
memcpy(plain_pre_8, plain, 8); // prepare next

crypted_pre_8 = crypted; // store position of previous 8 byte
crypted += 8; // prepare next output
count += 8; // outstrlen increase by 8
pos_in_byte = 0; // back to start
is_header = 0; // and exit header
} // encrypt_every_8_byte

pos_in_byte = (instrlen + 0x0a) % 8; // header padding decided by instrlen
if (pos_in_byte) {
pos_in_byte = 8 - pos_in_byte;
}
plain[0] = (rand() &amp; 0xf8) | pos_in_byte;

memset(plain + 1, rand() &amp; 0xff, pos_in_byte++);
memset(plain_pre_8, 0x00, sizeof(plain_pre_8));

crypted = crypted_pre_8 = outstr;

padding = 1; // pad some stuff in header
while (padding <= 2) { // at most two byte
if (pos_in_byte < 8) {
plain[pos_in_byte++] = rand() &amp; 0xff;
padding++;
}
if (pos_in_byte == 8) {
encrypt_every_8_byte();
}
}

inp = instr;
while (instrlen > 0) {
if (pos_in_byte < 8) {
plain[pos_in_byte++] = *(inp++);
instrlen--;
}
if (pos_in_byte == 8) {
encrypt_every_8_byte();
}
}

padding = 1; // pad some stuff in tailer
while (padding <= 7) { // at most sever byte
if (pos_in_byte < 8) {
plain[pos_in_byte++] = 0x00;
padding++;
}
if (pos_in_byte == 8) {
encrypt_every_8_byte();
}
}

*outstrlen_prt = count;
} // qq_encrypt


/********************************************************************
* [decrypt part]
* return 0 if failed, otherwise return 1
********************************************************************/

int qq_decrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr)
{
unsigned char decrypted[8], m[8], *crypt_buff, *crypt_buff_pre_8, *outp;
int count, context_start, pos_in_byte, padding;

int decrypt_every_8_byte(void) {
for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
if (context_start + pos_in_byte >= instrlen)
return 1;
decrypted[pos_in_byte] ^= crypt_buff[pos_in_byte];
}
qq_decipher((unsigned long *) decrypted, (unsigned long *) key, (unsigned long *) decrypted);

context_start += 8;
crypt_buff += 8;
pos_in_byte = 0;
return 1;
} // decrypt_every_8_byte

// at least 16 bytes and %8 == 0
if ((instrlen % 8) || (instrlen < 16))
return 0;
// get information from header
qq_decipher((unsigned long *) instr, (unsigned long *) key, (unsigned long *) decrypted);
pos_in_byte = decrypted[0] &amp; 0x7;
count = instrlen - pos_in_byte - 10; // this is the plaintext length
// return if outstr buffer is not large enought or error plaintext length
if (*outstrlen_ptr < count || count < 0)
return 0;

memset(m, 0, 8);
crypt_buff_pre_8 = m;
*outstrlen_ptr = count; // everything is ok! set return string length

crypt_buff = instr + 8; // address of real data start
context_start = 8; // context is at the second 8 byte
pos_in_byte++; // start of paddng stuff

padding = 1; // at least one in header
while (padding <= 2) { // there are 2 byte padding stuff in header
if (pos_in_byte < 8) { // bypass the padding stuff, none sense data
pos_in_byte++;
padding++;
}
if (pos_in_byte == 8) {
crypt_buff_pre_8 = instr;
if (!decrypt_every_8_byte())
return 0;
}
} // while

outp = outstr;
while (count != 0) {
if (pos_in_byte < 8) {
*outp = crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte];
outp++;
count--;
pos_in_byte++;
}
if (pos_in_byte == 8) {
crypt_buff_pre_8 = crypt_buff - 8;
if (!decrypt_every_8_byte())
return 0;
}
} // while

for (padding = 1; padding < 8; padding++) {
if (pos_in_byte < 8) {
if (crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte])
return 0;
pos_in_byte++;
}
if (pos_in_byte == 8) {
crypt_buff_pre_8 = crypt_buff;
if (!decrypt_every_8_byte())
return 0;
}
} // for
return 1;
} // qq_decrypt

/*****************************************************************************/
/* This is the Public Function */
// return 1 is succeed, otherwise return 0
int qq_crypt(unsigned char flag,
unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr)
{
if (flag == DECRYPT)
return qq_decrypt(instr, instrlen, key, outstr, outstrlen_ptr);
else if (flag == ENCRYPT)
qq_encrypt(instr, instrlen, key, outstr, outstrlen_ptr);

return 1; // flag must be DECRYPT or ENCRYPT
} // qq_crypt

/*****************************************************************************/
// END OF FILE
 
后退
顶部