AES For Delphi And Java, AES/ECB/PKCS5Padding(一)

         Java刚学不久,很多东西都不知道,走了很多弯路。

         最近项目要用AES加密算法,服务端是Java,客户端是Delphi。网上找了很多AES算法的代码,单独加密和解密都可以,一旦交互加密解密就不行了,例如:Java加密,用Delphi就解密不了。

网上找到的Java端AES代码如下:

package encryption;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AESTest {
	public static void main(String args[]){
        String content = "test";
        String password = "12345678";
        //加密
        System.out.println("加密前:" + content);
        byte[] encryptResult = encrypt(content, password);
        System.out.println("加密后:" + parseByte2HexStr(encryptResult));
        //解密
        byte[] decryptResult = decrypt(encryptResult,password);
        System.out.println("解密后:" + new String(decryptResult));
	}
	
    /**
     * 加密
     * 
     * @param content 需要加密的内容
     * @param password  加密密码
     * @return
     */
    public static byte[] encrypt(String content, String password) {
            try {           
                    KeyGenerator kgen = KeyGenerator.getInstance("AES");
                    kgen.init(128, new SecureRandom(password.getBytes()));
                    SecretKey secretKey = kgen.generateKey();
                    byte[] enCodeFormat = secretKey.getEncoded();
                    SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
                    Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                    byte[] byteContent = content.getBytes("utf-8");
                    cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
                    byte[] result = cipher.doFinal(byteContent);
                    return result; // 加密
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                    e.printStackTrace();
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                    e.printStackTrace();
            } catch (BadPaddingException e) {
                    e.printStackTrace();
            }
            return null;
    }
    
    /**解密
     * @param content  待解密内容
     * @param password 解密密钥
     * @return
     */
    public static byte[] decrypt(byte[] content, String password) {
            try {
                     KeyGenerator kgen = KeyGenerator.getInstance("AES");
                     kgen.init(128, new SecureRandom(password.getBytes()));
                     SecretKey secretKey = kgen.generateKey();
                     byte[] enCodeFormat = secretKey.getEncoded();
                     SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");            
                     Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                    cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
                    byte[] result = cipher.doFinal(content);
                    return result; // 加密
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                    e.printStackTrace();
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                    e.printStackTrace();
            } catch (BadPaddingException e) {
                    e.printStackTrace();
            }
            return null;
    }
    
    /**将二进制转换成16进制
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < buf.length; i++) {
                    String hex = Integer.toHexString(buf[i] & 0xFF);
                    if (hex.length() == 1) {
                            hex = '0' + hex;
                    }
                    sb.append(hex.toUpperCase());
            }
            return sb.toString();
    }
    
    /**将16进制转换为二进制
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
            if (hexStr.length() < 1)
                    return null;
            byte[] result = new byte[hexStr.length()/2];
            for (int i = 0;i< hexStr.length()/2; i++) {
                    int high = Integer.parseInt(hexStr.substring(i*2, i*2+1), 16);
                    int low = Integer.parseInt(hexStr.substring(i*2+1, i*2+2), 16);
                    result[i] = (byte) (high * 16 + low);
            }
            return result;
    }
}


java端结果如下:

加密前:test
加密后:73C58BAFE578C59366D8C995CD0B9D6D
解密后:test


网上找到的delphi端AES代码就很少了,主要是用ELAES.pas来加解密的,以128bit加密为例:

{  --  字符串加密函数 默认按照 128 位密匙加密 --  }
function EncryptString(Value: string; Key: string;
  KeyBit: TKeyBit = kb128): string;
var
  SS, DS: TStringStream;
  Size: Int64;
  AESKey128: TAESKey128;
  AESKey192: TAESKey192;
  AESKey256: TAESKey256;
begin
  Result := '';
  SS := TStringStream.Create(Value);
  DS := TStringStream.Create('');
  try
    Size := SS.Size;
    DS.WriteBuffer(Size, SizeOf(Size));
    {  --  128 位密匙最大长度为 16 个字符 --  }
    if KeyBit = kb128 then
    begin
      FillChar(AESKey128, SizeOf(AESKey128), 0 );
      Move(PChar(Key)^, AESKey128, Min(SizeOf(AESKey128), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey128, DS);
    end;
    {  --  192 位密匙最大长度为 24 个字符 --  }
    if KeyBit = kb192 then
    begin
      FillChar(AESKey192, SizeOf(AESKey192), 0 );
      Move(PChar(Key)^, AESKey192, Min(SizeOf(AESKey192), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey192, DS);
    end;
    {  --  256 位密匙最大长度为 32 个字符 --  }
    if KeyBit = kb256 then
    begin
      FillChar(AESKey256, SizeOf(AESKey256), 0 );
      Move(PChar(Key)^, AESKey256, Min(SizeOf(AESKey256), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey256, DS);
    end;
    Result := StrToHex(DS.DataString);
  finally
    SS.Free;
    DS.Free;
  end;
end;

{  --  字符串解密函数 默认按照 128 位密匙解密 --  }
function DecryptString(Value: string; Key: string;
  KeyBit: TKeyBit = kb128): string;
var
  SS, DS: TStringStream;
  Size: Int64;
  AESKey128: TAESKey128;
  AESKey192: TAESKey192;
  AESKey256: TAESKey256;
begin
  Result := '';
  SS := TStringStream.Create(HexToStr(Value));
  DS := TStringStream.Create('');
  try
    Size := SS.Size;
    SS.ReadBuffer(Size, SizeOf(Size));
    {  --  128 位密匙最大长度为 16 个字符 --  }
    if KeyBit = kb128 then
    begin
      FillChar(AESKey128, SizeOf(AESKey128), 0 );
      Move(PChar(Key)^, AESKey128, Min(SizeOf(AESKey128), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey128, DS);
    end;
    {  --  192 位密匙最大长度为 24 个字符 --  }
    if KeyBit = kb192 then
    begin
      FillChar(AESKey192, SizeOf(AESKey192), 0 );
      Move(PChar(Key)^, AESKey192, Min(SizeOf(AESKey192), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey192, DS);
    end;
    {  --  256 位密匙最大长度为 32 个字符 --  }
    if KeyBit = kb256 then
    begin
      FillChar(AESKey256, SizeOf(AESKey256), 0 );
      Move(PChar(Key)^, AESKey256, Min(SizeOf(AESKey256), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey256, DS);
    end;
    Result := DS.DataString;
  finally
    SS.Free;
    DS.Free;
  end;
end;
delphi结果如下:

加密前:test
加密后:0400000000000000C83DC726BB173EA77305C62E92892F51
解密后:test
通过以上结果对比,同样的“明文”和“密钥”,加密后的密文不一样,密文不一样,当然不能互相加密解密。

由于delphi关于AES可用代码太少,就先从java下手,首先要了解Java默认的AES加密模式和填充模式是哪一种?

AES加密模式和填充方式()
01 算法/模式/填充                16字节加密后数据长度        不满16字节加密后长度
02 AES/CBC/NoPadding             16                          不支持
03 AES/CBC/PKCS5Padding          32                          16
04 AES/CBC/ISO10126Padding       32                          16
05 AES/CFB/NoPadding             16                          原始数据长度
06 AES/CFB/PKCS5Padding          32                          16
07 AES/CFB/ISO10126Padding       32                          16
08 AES/ECB/NoPadding             16                          不支持
09 AES/ECB/PKCS5Padding          32                          16
10 AES/ECB/ISO10126Padding       32                          16
11 AES/OFB/NoPadding             16                          原始数据长度
12 AES/OFB/PKCS5Padding          32                          16
13 AES/OFB/ISO10126Padding       32                          16
14 AES/PCBC/NoPadding            16                          不支持
15 AES/PCBC/PKCS5Padding         32                          16
16 AES/PCBC/ISO10126Padding      32                          16
更多关于加密模式内容:http://blog.sina.com.cn/s/blog_679daa6b0100zmpp.html
此处参考地址: http://www.2cto.com/kf/201211/165570.html
由于java刚学不久,很多东西不知道,加了好几个java群和算法群,都没人搭理我,没办法,笨人笨办法,直接看与AES算法有关的java源码了,主要的jar包是javax.crypto和com.sun.crypto.provider。推荐一个网站,这个网站能在线看源码: http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/com/sun/crypto/provider/AESCipher.java?av=f

表示很笨,看了很久,才知道AES默认的加密和填充模式是AES/ECB/PKCS5Padding

后来自己也测试了一下

Cipher cipher = Cipher.getInstance("AES");// 创建密码器
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 创建密码器
这二者加密出来的结果一样。

继续查找资料,发现了一个AES在线加密网站:http://www.seacha.com/tools/aes.html?src=test&mode=ECB&keylen=128&key=12345678&iv=&bpkcs=pkcs5padding&session=5jusjIojNQIZIKIGNBV4&aes=ec354375b36ae7401c19d4bdb5cb8822&encoding=hex&type=0

心想,在线加密网站应该是标准的AES算法,就开始向网站靠拢。

我用使用的加密方式是128bit的,由于不知道在线网站密钥的填充方式,就直接把密钥定位1234567812345678,正好128bit,这样就不会有密钥填充了。

明文:test

密钥:1234567812345678

网站加密后的结果是

ba572c602f340fd8be26038a0b79f107

 

Java加密后的结果是

加密前:test
加密后:B4598BD96E21C84575FB86FB6CB64D19
解密后:test</span>
    

和java端还是不一样,继续看java代码,查找资料。网上找了好久,突然在csdn上看到这么一句话,Java中AES算法的实现肯定是标准的,要看看加密模式、填充模式、明文和密钥一不一样。一语惊醒梦中人,赶紧检查java代码,发现java端在生成密钥的时候,用了SecureRandom,加密前特地把密钥输出出来一看,果然和之前的不一样。坑爹啊!问题竟然出现在这儿,赶紧把这段代码屏蔽。

修正后的代码如下:

/**
     * 加密
     * 
     * @param content 需要加密的内容
     * @param password  加密密码
     * @return
     */
    public static byte[] encrypt(String content, String password) {
            try {           
                    /*KeyGenerator kgen = KeyGenerator.getInstance("AES");
                    kgen.init(128, new SecureRandom(password.getBytes()));
                    SecretKey secretKey = kgen.generateKey();
                    byte[] enCodeFormat = secretKey.getEncoded();
                    SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");*/
            		SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
                    Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                    byte[] byteContent = content.getBytes("utf-8");
                    cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
                    byte[] result = cipher.doFinal(byteContent);
                    return result; // 加密
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                    e.printStackTrace();
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                    e.printStackTrace();
            } catch (BadPaddingException e) {
                    e.printStackTrace();
            }
            return null;
    }
    
    /**解密
     * @param content  待解密内容
     * @param password 解密密钥
     * @return
     */
    public static byte[] decrypt(byte[] content, String password) {
            try {
                     /*KeyGenerator kgen = KeyGenerator.getInstance("AES");
                     kgen.init(128, new SecureRandom(password.getBytes()));
                     SecretKey secretKey = kgen.generateKey();
                     byte[] enCodeFormat = secretKey.getEncoded();
                     SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");*/ 
            	     SecretKeySpec key = new SecretKeySpec(password.getBytes(), "AES");
                     Cipher cipher = Cipher.getInstance("AES");// 创建密码器
                    cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
                    byte[] result = cipher.doFinal(content);
                    return result; // 加密
            } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                    e.printStackTrace();
            } catch (InvalidKeyException e) {
                    e.printStackTrace();
            } catch (IllegalBlockSizeException e) {
                    e.printStackTrace();
            } catch (BadPaddingException e) {
                    e.printStackTrace();
            }
            return null;
    }

加密后的结果:

加密前:test
加密后:BA572C602F340FD8BE26038A0B79F107
解密后:test

终于和网站加密的结果一样了,感觉这几天的努力没白费啊,终于有点小成果,看到胜利的曙光了。

搞定Java后,开始研究Delphi,delphi关于AES的代码好少,基本上都是AES.pas和ELAES.pas,资料只有多了,只能自己摸索了。

首先,delphi加密出来的密文长度比java加密出来的长,这肯定是不正常的,查看AES.pas发现加解密的时候,把明文的长度也写进去了,把这句代码屏蔽。

修改后的代码如下:

{  --  字符串加密函数 默认按照 128 位密匙加密 --  }
function EncryptString(Value: string; Key: string;
  KeyBit: TKeyBit = kb128): string;
var
  SS, DS: TStringStream;
  Size: Int64;
  AESKey128: TAESKey128;
  AESKey192: TAESKey192;
  AESKey256: TAESKey256;
begin
  Result := '';
  SS := TStringStream.Create(Value);
  DS := TStringStream.Create('');
  try
    //Size := SS.Size;
    //DS.WriteBuffer(Size, SizeOf(Size));
    {  --  128 位密匙最大长度为 16 个字符 --  }
    if KeyBit = kb128 then
    begin
      FillChar(AESKey128, SizeOf(AESKey128), 0 );
      Move(PChar(Key)^, AESKey128, Min(SizeOf(AESKey128), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey128, DS);
    end;
    {  --  192 位密匙最大长度为 24 个字符 --  }
    if KeyBit = kb192 then
    begin
      FillChar(AESKey192, SizeOf(AESKey192), 0 );
      Move(PChar(Key)^, AESKey192, Min(SizeOf(AESKey192), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey192, DS);
    end;
    {  --  256 位密匙最大长度为 32 个字符 --  }
    if KeyBit = kb256 then
    begin
      FillChar(AESKey256, SizeOf(AESKey256), 0 );
      Move(PChar(Key)^, AESKey256, Min(SizeOf(AESKey256), Length(Key)));
      EncryptAESStreamECB(SS, 0, AESKey256, DS);
    end;
    Result := StrToHex(DS.DataString);
  finally
    SS.Free;
    DS.Free;
  end;
end;

{  --  字符串解密函数 默认按照 128 位密匙解密 --  }
function DecryptString(Value: string; Key: string;
  KeyBit: TKeyBit = kb128): string;
var
  SS, DS: TStringStream;
  Size: Int64;
  AESKey128: TAESKey128;
  AESKey192: TAESKey192;
  AESKey256: TAESKey256;
begin
  Result := '';
  SS := TStringStream.Create(HexToStr(Value));
  DS := TStringStream.Create('');
  try
   // Size := SS.Size;
   // SS.ReadBuffer(Size, SizeOf(Size));
    {  --  128 位密匙最大长度为 16 个字符 --  }
    if KeyBit = kb128 then
    begin
      FillChar(AESKey128, SizeOf(AESKey128), 0 );
      Move(PChar(Key)^, AESKey128, Min(SizeOf(AESKey128), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey128, DS);
    end;
    {  --  192 位密匙最大长度为 24 个字符 --  }
    if KeyBit = kb192 then
    begin
      FillChar(AESKey192, SizeOf(AESKey192), 0 );
      Move(PChar(Key)^, AESKey192, Min(SizeOf(AESKey192), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey192, DS);
    end;
    {  --  256 位密匙最大长度为 32 个字符 --  }
    if KeyBit = kb256 then
    begin
      FillChar(AESKey256, SizeOf(AESKey256), 0 );
      Move(PChar(Key)^, AESKey256, Min(SizeOf(AESKey256), Length(Key)));
      DecryptAESStreamECB(SS, SS.Size - SS.Position, AESKey256, DS);
    end;
    Result := DS.DataString;
  finally
    SS.Free;
    DS.Free;
  end;
end;
修正后,长度正常了,加密结果还是不一样,继续查找delphi关于AES/ECB/PKCS5Padding的资料,找到很久没找到。不过想起之前同事跟我说过的一句话,他那儿有delphi和java下通用的DES算法,AES算是DES算法的升级版啊,对照着看看,应该会有灵感。网上搜了搜,资料还挺好找的,使用的加密和填充方式是DES/ECB/PKCS5Padding,填充方式是PKCS5Padding?正好是我想找的,下载源码,找到了delphi下PKCS5Padding的实现。

PKCS5Padding的实现代码如下:

type
  TKeyBit = (kb128, kb192, kb256);
  TPaddingType = (PKCS5Padding,PKCS7Padding);
function StrPadding(SourceStr:string;paddingType:TPaddingType = PKCS5Padding):string;
var
 DestStr:string;
 strRemainder,i:Integer;
begin
  DestStr := SourceStr;
  if paddingType = PKCS5Padding then
  begin
    strRemainder :=Length(DestStr) mod 16;
    strRemainder := 16 - strRemainder;
    for i:= 1 to strRemainder do
    begin
      DestStr := DestStr + Chr(strRemainder);
    end;
  end;
  Result := DestStr;
end;

function StrDelPadding(SourceStr:string;paddingType:TPaddingType = PKCS5Padding):string;
var
 DestStr:string;
 PaddingLen:Integer;
begin
  DestStr := SourceStr;
  if paddingType = PKCS5Padding then
  begin
    PaddingLen :=  Ord(DestStr[Length(DestStr)]);
    DestStr := Copy(DestStr,1,Length(DestStr)-PaddingLen);
  end;
  Result := DestStr;
end;
从DES转移到AES的过程中间还有个小插曲,DES的加密块长度是64位,AES是128位,所以填充的时候就发生问题了。

AES的PKCS5Padding的填充规则就是,填充字符串由一个字节序列组成,每个字节填充该字节序列的长度。字符串后面缺多少位,就补多少位所缺字符串的长度。

以test为例,只有4个byte,要补齐到16byte,就要填充12个12。

test补齐前:[116, 101, 115, 116,]

test补齐后:[116, 101, 115, 116, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]

搞定delphi的PKCS5Padding填充规则之后,运行测试程序,结果如下:

加密前:test
加密后:BA572C602F340FD8BE26038A0B79F107
解密后:test

呜呜呜,终于搞定了,测试了一下和java加解密,都能成功。真的搞定了吗?如果密钥不是16位呢,该怎么填充?请看下一篇。

附上这次的Java和Delphi代码,下载地址http://download.csdn.net/detail/kunlun122/7465045

已标记关键词 清除标记
相关推荐
最近做一个接口,与JAVA的关于DES/CBC/PKCS5Padding 互相解密。在网上找了很多资料,摸索了3天才摸索出来。同样的明文,用JAVA加密的密文死活都跟用DELPHI加密的不相等,有时候少于8个字符的就正常,多了8个字符的就有问题,原来是有个7把7改成8就可以了。害人啊,, function EncryDes(const str:string;const keystr:string;const ivstr:string):string ; var key:tkey64; Context:TDESContext; Block,iv:TDESBlock; i,j,len,posnum:smallint; poschar,xx:char; xuhuan:integer; begin for i:=0 to 7 do begin if i > (length(keystr)-1) then key[i] :=0 else key[i] := byte(keystr[i+1]); end; for i:=0 to 7 do begin if i > (length(ivstr)-1) then iv[i]:=0 else iv[i] := byte(ivstr[i+1]); end; InitEncryptDES(Key, Context, true); len := length(AnsiString(str)); xx:= char( 8- (len mod 8)); if len<=8 then xuhuan:=0 else xuhuan:=round(len/8); for i:=0 to xuhuan do begin for j:=0 to 7 do begin if ((i*8+j+1)<=len) then //关键这一步,网上好多参考资料都是((i*7+j+1)<=len),而不是((i*8+j+1)<=len) 害人啊,害得我摸索了3天,,哎 begin poschar:=str[i*8+j+1]; block[j]:=byte(poschar); end else block[j]:=byte(xx); end; EncryptDESCBC(Context, IV, Block); for j:= 0 to 7 do begin posnum:=block[j]; result := result + inttohex(posnum,2); end; iv:=block; end; end; //完整代码如下 unit dmdes; {*********************************************************} {* DELPHI、PHP、C#通用DES编码解码单元 *} {* 由TurboPower LockBox部分代码改写 *} {* 滕州市东鸣软件工作室制作 ZWF 2011-12-27 *} {*********************************************************} {EncryDes为编码函数,DecryDes为解码函数,keystr为密码,ivstr为偏移量, 一般设置keystr,ivstr相同,内容为八位字节长度的字符串,编码结果为十六进制字串} interface uses Windows,SysUtils; type PKey64 = ^TKey64; TKey64 = array [0..7] of Byte; type TDESBlock = array[0..7] of Byte; TDESContext = packed record TransformedKey : array [0..31] of LongInt; Encrypt : Boolean; end; function EncryDes(const str:string;const keystr:string;const ivstr:string):string ; function DecryDes(const str:string;const keystr:string;const ivstr:string):string ; function DecryDessec(const str:string;const keystr:string;const ivstr:string):string ; implementation procedure XorMemPrim(var Mem1; const Mem2; Count :
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页