⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 crypter.java

📁 lumaQQ的源文件
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*                    notXX
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.qq;

import java.io.ByteArrayOutputStream;
import java.util.Random;


/**
 * 加密解密QQ消息的工具类. QQ消息的加密算法是一个16次的迭代过程,并且是反馈的,每一个加密单元是8字节,输出也是8字节,密钥是16字节
 * 我们以prePlain表示前一个明文块,plain表示当前明文块,crypt表示当前明文块加密得到的密文块,preCrypt表示前一个密文块
 * f表示加密算法,d表示解密算法 那么从plain得到crypt的过程是: crypt = f(plain &circ; preCrypt) &circ;
 * prePlain 所以,从crypt得到plain的过程自然是 plain = d(crypt &circ; prePlain) &circ;
 * 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>
 * 
 * @author luma
 * @author notXX
 */
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();
    // 字节输出流
	private ByteArrayOutputStream baos;
	
	/**
	 * 构造函数
	 */
	public Crypter() {
		baos = new ByteArrayOutputStream(8);
	}
    
    /**
     * 解密
     * @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] & 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[i] = 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[i] = (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() & 0xF8) | pos);
        
        // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
        for(int i = 1; i <= pos; i++)
            plain[i] = (byte)(rand() & 0xFF);
        pos++;
        // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
        for(int i = 0; i < 8; i++)
            prePlain[i] = 0x0;
        
        // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
        padding = 1;
        while(padding <= 2) {
            if(pos < 8) {
                plain[pos++] = (byte)(rand() & 0xFF);
                padding++;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -