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

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
AES/ECB/PKCS7PaddingAES/ECB/PKCS5Padding是两种常见的AES加密模式和填充方式。它们的区别在于填充方式的不同。 PKCS5Padding和PKCS7Padding都是用于填充数据块的,以确保数据块的长度满足加密算法的要求。它们的主要区别在于对于数据块长度不满足加密算法要求时的处理方式。 PKCS5Padding是针对8字节数据块的填充方式,当数据块长度不满8字节时,会使用特定的字节填充数据块,填充的字节值等于需要填充的字节数。例如,如果数据块长度为6字节,则会填充2个字节的值为0x02的字节。 PKCS7Padding是通用的填充方式,可以用于任意长度的数据块。当数据块长度不满足加密算法要求时,会使用特定的字节填充数据块,填充的字节值等于需要填充的字节数。例如,如果数据块长度为6字节,则会填充2个字节的值为0x02的字节。 因此,PKCS5Padding和PKCS7Padding的区别在于对于数据块长度不满足加密算法要求时的处理方式不同。 下面是一个示例代码,演示了AES/ECB/PKCS5PaddingAES/ECB/PKCS7Padding的使用: ```java import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AESExample { public static void main(String[] args) throws Exception { String key = "0123456789abcdef"; String plaintext = "Hello World"; // AES/ECB/PKCS5Padding Cipher cipher1 = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keySpec1 = new SecretKeySpec(key.getBytes(), "AES"); cipher1.init(Cipher.ENCRYPT_MODE, keySpec1); byte[] encrypted1 = cipher1.doFinal(plaintext.getBytes()); System.out.println("AES/ECB/PKCS5Padding Encrypted: " + new String(encrypted1)); // AES/ECB/PKCS7Padding Cipher cipher2 = Cipher.getInstance("AES/ECB/PKCS7Padding"); SecretKeySpec keySpec2 = new SecretKeySpec(key.getBytes(), "AES"); cipher2.init(Cipher.ENCRYPT_MODE, keySpec2); byte[] encrypted2 = cipher2.doFinal(plaintext.getBytes()); System.out.println("AES/ECB/PKCS7Padding Encrypted: " + new String(encrypted2)); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值