本人写了个TEA算法,有没有哪位朋友有研究过的,看看我写得如何?是否有不好的地方。大家探讨探讨。源码如下
unit CTeaUnit;interface
uses
  Windows, Messages, SysUtils, Classes,dialogs;type
  TByteArray = array of byte;
  CTea = class
  private
    function encipher(inB: TByteArray): TByteArray;
    function decipher(inB: TByteArray; offset: Integer): TByteArray; overload;
    function decipher(inB: TByteArray): TByteArray; overload;
    procedure encrypt8Bytes();
    function decrypt8Bytes(inB: TByteArray; offset, len: integer): boolean;
  protected
  public
    constructor Create;
    destructor Destroy; override;
    function decrypt(inB: TByteArray; offset: Integer; len: Integer; k: TByteArray): TByteArray; overload;
    function decrypt(inB: TByteArray; k: TByteArray): TByteArray; overload;
    function encrypt(inB: TByteArray; offset: Integer; len: Integer; k: TByteArray): TByteArray; overload;
    function encrypt(inB: TByteArray; k: TByteArray): TByteArray; overload;  published
  end;implementation
var
    // 指向当前的明文块
  plain: TByteArray;
    // 这指向前面一个明文块
  prePlain: TByteArray;
    // 输出的密文或者明文
  outB: TByteArray;
    // 当前加密的密文位置和上一次加密的密文块位置,他们相差8
  crypt, preCrypt: Integer;
    // 当前处理的加密解密块的位置
  pos: Integer;
    // 填充数
  padding: Integer;
    // 密钥
  key: TByteArray;
    // 用于加密时,表示当前是否是第一个8字节块,因为加密算法是反馈的
    // 但是最开始的8个字节没有反馈可用,所有需要标明这种情况
  header: boolean;
    // 这个表示当前解密开始的位置,之所以要这么一个变量是为了避免当解密到最后时
    // 后面已经没有数据,这时候就会出错,这个变量就是用来判断这种情况免得出错
  contextStart: Integer;constructor CTea.Create;
begin
  header := true;
  Randomize;
end;{ destructor - just as a placeholder if cleanup will be necessary }destructor CTea.Destroy;
begin
  inherited Destroy;
end;

解决方案 »

  1.   

    function CTea.decrypt(inB: TByteArray; offset: Integer; len: Integer; k: TByteArray): TByteArray;
    {/**
     * 解密
     * @param in 密文
     * @param offset 密文开始的位置
     * @param len 密文长度
     * @param k 密钥
     * @return 明文
     */}
    var
      count, i: integer;
      m: TByteArray;
    begin
      crypt := 0;
      preCrypt := 0;
      key := k;
      SetLength(m, offset + 8);
      // 因为QQ消息加密之后至少是16字节,并且肯定是8的倍数,这里检查这种情况
      if ((len mod 8 <> 0) or (len < 16)) then begin
        Result := nil;
        Exit;
      end;
      // 得到消息的头部,关键是得到真正明文开始的位置,这个信息存在第一个字节里面,所以其用解密得到的第一个字节与7做与
      prePlain := decipher(inB, offset);
      pos := prePlain[0] and $7;
      // 得到真正明文的长度
      count := len - pos - 10;
      // 如果明文长度小于0,那肯定是出错了,比如传输错误之类的,返回
      if (count < 0) then begin
        Result := nil;
        Exit;
      end;
      //这个是临时的preCrypt,和加密时第一个8字节块没有prePlain一样,解密时
      //第一个8字节块也没有preCrypt,所有这里建一个全0的
      for i := offset to Length(m) - 1 do
        m[i] := 0;
      // 通过了上面的代码,密文应该是没有问题了,我们分配输出缓冲区
      SetLength(outB, count);
      // 设置preCrypt的位置等于0,注意目前的preCrypt位置是指向m的,因为java没有指针,所以我们在后面要控制当前密文buf的引用
      preCrypt := 0;
      // 当前的密文位置,为什么是8不是0呢?注意前面我们已经解密了头部信息了,现在当然该8了
      crypt := 8;
      // 自然这个也是8
      contextStart := 8;
      // 加1,和加密算法是对应的
      Inc(pos);
      // 开始跳过头部,如果在这个过程中满了8字节,则解密下一块
      // 因为是解密下一块,所以我们有一个语句 m = in,下一块当然有preCrypt了,我们不再用m了
      // 但是如果不满8,这说明了什么?说明了头8个字节的密文是包含了明文信息的,当然还是要用m把明文弄出来
      // 所以,很显然,满了8的话,说明了头8个字节的密文除了一个长度信息有用之外,其他都是无用的填充
      padding := 1;
      while padding <= 2 do
      begin
        if (pos < 8) then begin
          Inc(pos);
          Inc(padding);
        end;
        if (pos = 8) then begin
          m := inB;
          if (not decrypt8Bytes(inB, offset, len)) then begin
            Result := nil;
            Exit;
          end;
        end;
      end;
      // 这里是解密的重要阶段,这个时候头部的填充都已经跳过了,开始解密
      // 注意如果上面一个while没有满8,这里第一个if里面用的就是原始的m,否则这个m就是in了
      i := 0;
      while (count <> 0) do begin
        if (pos < 8) then begin
          outB[i] := byte(m[offset + preCrypt + pos] xor prePlain[pos]);
          Inc(i);
          Dec(count);
          Inc(pos);
        end;
        if (pos = 8) then begin
          m := inB;
          preCrypt := crypt - 8;
          if (not decrypt8Bytes(inB, offset, len)) then begin
            Result := nil;
            Exit;
          end;
        end;
      end;
      // 最后的解密部分,上面一个while已经把明文都解出来了,到了这里还剩下什么?对了,还剩下尾部的填充,应该全是0
      // 所以这里有检查是否解密了之后是0,如果不是的话那肯定出错了,所以返回null
      for padding := 1 to 7 do begin
        if (pos < 8) then begin
          if ((m[offset + preCrypt + pos] xor prePlain[pos]) <> 0) then begin
            Result := nil;
            Exit;
          end;
          inc(pos);
        end;
        if (pos = 8) then begin
          m := inB;
          preCrypt := crypt;
          if (not decrypt8Bytes(inB, offset, len)) then begin
            Result := nil;
            Exit;
          end;
        end;
      end;  //Result := outB;
      SetLength(Result, Length(outB));
      CopyMemory(@Result[0], @outB[0], Length(outB));
    end;
      

  2.   

    function CTea.decrypt(inB: TByteArray; k: TByteArray): TByteArray;
    {
    /**
    * @param in
    *            需要被解密的密文
    * @param inLen
    *            密文长度
    * @param k
    *            密钥
    * @return Message 已解密的消息
    */
    }
    begin
      Result := decrypt(inB, 0, Length(inB), k);
    end;function CTea.encrypt(inB: TByteArray; offset: Integer; len: Integer; k: TByteArray): TByteArray;
    {
    /**
    * 加密
    * @param in 明文字节数组
    * @param offset 开始加密的偏移
    * @param len 加密长度
    * @param k 密钥
    * @return 密文字节数组
    */
    }
    var
      i: Integer;
    begin
      SetLength(plain, 8);
      SetLength(prePlain, 8);
      pos := 1;
      padding := 0;
      crypt := 0;
      preCrypt := 0;
      key := k;
      header := true;  // 计算头部填充字节数
      pos := (len + $A) mod 8;
      if (pos <> 0) then
        pos := 8 - pos;
      // 计算输出的密文长度
      SetLength(outB, len + pos + 10);
      // 这里的操作把pos存到了plain的第一个字节里面
      //     0xF8后面三位是空的,正好留给pos,因为pos是0到7的值,表示文本开始的字节位置
      plain[0] := byte((random(256) and $F8) or pos);  // 这里用随机产生的数填充plain[1]到plain[pos]之间的内容
      for i := 1 to pos do
        plain[i] := byte(random(256) and $FF);
      Inc(pos);
      // 这个就是prePlain,第一个8字节块当然没有prePlain,所以我们做一个全0的给第一个8字节块
      for i := 1 to 7 do
        prePlain[i] := $0;  // 继续填充2个字节的随机数,这个过程中如果满了8字节就加密之
      padding := 1;
      while (padding <= 2) do begin
        if (pos < 8) then begin
          plain[pos] := byte(random(246) and $FF);
          Inc(pos);
          Inc(padding);
        end;
        if (pos = 8) then
          encrypt8Bytes();
      end;  // 头部填充完了,这里开始填真正的明文了,也是满了8字节就加密,一直到明文读完
      i := offset;
      while (len > 0) do begin
        if (pos < 8) then begin
          plain[pos] := inB[i];
          Inc(pos);
          Inc(i);
          Dec(len);
        end;
        if (pos = 8) then
          encrypt8Bytes();
      end;        // 最后填上0,以保证是8字节的倍数
      padding := 1;
      while (padding <= 7) do begin
        if (pos < 8) then begin
          plain[pos] := $0;
          Inc(pos);
          Inc(padding);    end;
        if (pos = 8) then
          encrypt8Bytes();
      end;  //Result:=outB;
      SetLength(Result, Length(outB));
      CopyMemory(@Result[0], @outB[0], Length(outB));end;function CTea.encrypt(inB: TByteArray; k: TByteArray): TByteArray;
    {
    /**
     * @param in
     *            需要加密的明文
     * @param inLen
     *            明文长度
     * @param k
     *            密钥
     * @return Message 密文
     */
    }
    begin
      Result := encrypt(inB, 0, Length(inB), k);
    end;function CTea.encipher(inB: TByteArray): TByteArray;
    var
      loop: Integer;
      y, z, a, b, c, d, sum, delta: Cardinal;
      tmpArray, tmpOut: array[0..7] of byte;begin
      try
        // 迭代次数,16次
        loop := $10;
        // 得到明文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
        // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
        // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
        CopyMemory(@y, @inB[0], 4);
        CopyMemory(@z, @inB[4], 4);
        CopyMemory(@a, @key[0], 4);
        CopyMemory(@b, @key[4], 4);
        CopyMemory(@c, @key[8], 4);
        CopyMemory(@d, @key[12], 4);
        // 这是算法的一些控制变量,为什么delta是0x9E3779B9呢?
        // 这个数是TEA算法的delta,实际是就是sqr(5)-1 * 2^31
        sum := 0;
        delta := $9E3779B9;    // 开始迭代了,乱七八糟的,我也看不懂,反正和DES之类的差不多,都是这样倒来倒去
        while (loop > 0) do begin
          sum := sum + delta;
          y := y + ((z shl 4) + a) xor (z + sum) xor ((z shr 5) + b);
          z := z + ((y shl 4) + c) xor (y + sum) xor ((y shr 5) + d);
          Dec(loop);
        end;    // 最后,我们输出密文,因为我用的long,所以需要强制转换一下变成int
        CopyMemory(@tmpArray[0], @y, 4);
        CopyMemory(@tmpArray[4], @z, 4);
        tmpOut[0] := tmpArray[3];
        tmpOut[1] := tmpArray[2];
        tmpOut[2] := tmpArray[1];
        tmpOut[3] := tmpArray[0];
        tmpOut[4] := tmpArray[7];
        tmpOut[5] := tmpArray[6];
        tmpOut[6] := tmpArray[5];
        tmpOut[7] := tmpArray[4];    SetLength(Result, Length(tmpOut));
        CopyMemory(@Result[0], @tmpOut[0], Length(tmpOut));
      except
        Result := nil;
      end;
    end;function CTea.decipher(inB: TByteArray; offset: Integer): TByteArray;
    var
      loop: Integer;
      y, z, a, b, c, d, sum, delta: Cardinal;
      tmpArray, tmpOut: array[0..7] of byte;
    begin
      try
        // 迭代次数,16次
        loop := $10;
        // 得到密文和密钥的各个部分,注意java没有无符号类型,所以为了表示一个无符号的整数
        // 我们用了long,这个long的前32位是全0的,我们通过这种方式模拟无符号整数,后面用到的long也都是一样的
        // 而且为了保证前32位为0,需要和0xFFFFFFFF做一下位与
        CopyMemory(@y, @inB[offset], 4);
        CopyMemory(@z, @inB[offset + 4], 4);
        CopyMemory(@a, @key[0], 4);
        CopyMemory(@b, @key[4], 4);
        CopyMemory(@c, @key[8], 4);
        CopyMemory(@d, @key[12], 4);
        // 算法的一些控制变量,为什么sum在这里也有数了呢,这个sum嘛就是和迭代次数有关系了
        // 因为delta是这么多,所以sum如果是这么多的话,迭代的时候减减减,减16次,最后
        // 得到什么? Yeah,得到0。反正这就是为了得到和加密时相反顺序的控制变量,这样
        // 才能解密呀~~
        sum := $E3779B90;
        delta := $9E3779B9;
        // 迭代开始了, #_#
        while (loop > 0) do begin
          z := z - ((y shl 4) + c) xor (y + sum) xor ((y shr 5) + d);
          y := y - ((z shl 4) + a) xor (z + sum) xor ((z shr 5) + b);
          sum := sum - delta;
          Dec(loop);
        end;    // 输出明文,注意要转成int
        CopyMemory(@tmpArray[0], @y, 4);
        CopyMemory(@tmpArray[4], @z, 4);
        tmpOut[0] := tmpArray[3];
        tmpOut[1] := tmpArray[2];
        tmpOut[2] := tmpArray[1];
        tmpOut[3] := tmpArray[0];
        tmpOut[4] := tmpArray[7];
        tmpOut[5] := tmpArray[6];
        tmpOut[6] := tmpArray[5];
        tmpOut[7] := tmpArray[4];    SetLength(Result, Length(tmpOut));
        CopyMemory(@Result[0], @tmpOut[0], Length(tmpOut));
      except
        Result := nil;
      end;
    end;function CTea.decipher(inB: TByteArray): TByteArray;
    begin
      Result := decipher(inB, 0);
    end;procedure CTea.encrypt8Bytes();
    {
    /**
    * 加密8字节
    */
    }
    var
      crypted: TByteArray;
      i: integer;
    begin
      // 这部分完成我上面所说的 plain ^ preCrypt,注意这里判断了是不是第一个8字节块,如果是的话,那个prePlain就当作preCrypt用
      for pos := 0 to 7 do begin
        if (header) then
          plain[pos] := plain[pos] xor prePlain[pos]
        else
          plain[pos] := plain[pos] xor outB[preCrypt + pos];
      end;
      // 这个完成到了我上面说的 f(plain ^ preCrypt)
      crypted := encipher(plain);
      // 这个没什么,就是拷贝一下,delphi不像c,所以我只好这么干,c就不用这一步了
      for i := 0 to 7 do
      begin
        outB[crypt + i] := crypted[i];
      end;  // 这个就是完成到了 f(plain ^ preCrypt) ^ prePlain,ok,完成了,下面拷贝一下就行了
      for pos := 0 to 7 do
        outB[crypt + pos] := outB[crypt + pos] xor prePlain[pos];  prePlain := plain;
      // 完成了加密,现在是调整crypt,preCrypt等等东西的时候了
      preCrypt := crypt;
      crypt := crypt + 8;
      pos := 0;
      header := false;
    end;function CTea.decrypt8Bytes(inB: TByteArray; offset, len: integer): boolean;
    begin
      // 这里第一步就是判断后面还有没有数据,没有就返回,如果有,就执行 crypt ^ prePlain
      for pos := 0 to 7 do begin
        if (contextStart + pos >= len) then begin
          Result := true;
          Exit;
        end;
        prePlain[pos] := prePlain[pos] xor inB[offset + crypt + pos];
      end;  // 好,这里执行到了 d(crypt ^ prePlain)
      prePlain := decipher(prePlain);
      if (prePlain = nil) then begin
        Result := false;
        Exit;
      end;  // 解密完成,wait,没完成哦,最后一步没做哦?
      // 这里最后一步放到decrypt里面去做了,因为解密的步骤毕竟还是不太一样嘛
      // 调整这些变量的值先
      contextStart := contextStart + 8;
      crypt := crypt + 8;
      pos := 0;
      Result := true;
    end;
    end.