版权声明:本文为博主原创文章,未经博主允许不得转载。
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;
- 加密前: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
由于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;
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;
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