注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

让一切都逝去吧

排骨炖泥菜/猪肝炒苹果/鱼籽狗肉汤/狗头薏米汤

 
 
 

日志

 
 

AES加密算法  

2013-06-04 22:10:17|  分类: Java |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
      与外部系统通讯,对方是.net的,使用aes加密。发现没有一篇说明比较详细的java aes加解密的文章,于是打算自己把找到的资料全部整理一下。java AES加密有一种简单的加解密方式,代码如下:
/**
* 加密
* @param content 需要加密的内容
* @param password 加密密码
*/
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;
}
简单是因为这一句Cipher cipher = Cipher.getInstance("AES");接着往下看可以看到这里可指定更详细的参数。
测试代码
String content = "test";  
String password = "12345678";  
//加密  
System.out.println("加密前:" + content);  
byte[] encryptResult = encrypt(content, password);  
//解密  
byte[] decryptResult = decrypt(encryptResult,password);  
System.out.println("解密后:" + new String(decryptResult));
结果是两次输出内容相同,如果想看下加密后的内容,在byte[] encryptResult = encrypt(content, password); 这句后面这样写
try {
String encryptResultStr = new String(encryptResult, "utf-8");
// 解密
byte[] decryptResult = decrypt(encryptResultStr.getBytes("utf-8"), password);
System.out.println("解密后:" + new String(decryptResult));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
会报javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher错误,不可以直接转换为字符串输出,需要这样
String encryptResultStr = parseByte2HexStr(encryptResult);
System.out.println("加密后:" + encryptResultStr);
这个例子里面的加解密方法输入参数或返回值存在byte[]类型,使用起来不方便,可以修改成更简单的方法。这里先不处理了,最后给个综合的例子。
      方法的开头,就是获得Cipher的实例,详细参数的方式获得Cipher实例是这样
byte[] raw = hexStringToBytes(getMD5Str(key));
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        String AES_CIPHER_PADDING_MODE = "AES/ECB/PKCS5Padding";//这里有很多种模式组合
       cipher = Cipher.getInstance(AES_CIPHER_PADDING_MODE);
然后加解密的方法就是使用cipher 
public String encrypt(String source) throws InvalidKeyException, IllegalBlockSizeException, 
BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(source.getBytes(UTF_8));
return bytesToHexString(encrypted);
}

public String decrypt(String source) throws InvalidKeyException, IllegalBlockSizeException, 
BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException {
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = hexStringToBytes(source);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, "UTF-8");
}
public static byte[] hexStringToBytes(String hexString) {
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }
public static String bytesToHexString(byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }
这里有两个方法hexStringToBytes,bytesToHexString,其实跟上个例子的parseHexStr2ByteparseByte2HexStr运行结果是一样。只是不同例子看到的,实现貌似有点不一样,就都抄过来了。(getMD5Str方法等之后例子有,先不放上来了)
      AES说明,在这里http://blog.sina.com.cn/s/blog_679daa6b0100zmpp.html抄个较详细点的
Java Cryptography Extension(JCE)是一组包,它们提供用于加密、密钥生成和协商以及 Message Authentication Code(MAC)算法的框架和实现。它提供对对称、不对称、块和流密码的加密支持,它还支持安全流和密封的对象。
 
JCE中AES支持五中模式:CBC,CFB,ECB,OFB,PCBC;支持三种填充:NoPadding,PKCS5Padding,ISO10126Padding。不支持SSL3Padding。不支持“NONE”模式。
其中AES/ECB/NoPadding和我现在使用的AESUtil得出的结果相同(在16的整数倍情况下)。
不带模式和填充来获取AES算法的时候,其默认使用ECB/PKCS5Padding。
 
算法/模式/填充                    16字节加密后数据长度         不满16字节加密后长度
AES/CBC/NoPadding             16                         不支持
AES/CBC/PKCS5Padding          32                         16
AES/CBC/ISO10126Padding       32                          16
AES/CFB/NoPadding             16                          原始数据长度
AES/CFB/PKCS5Padding          32                          16
AES/CFB/ISO10126Padding       32                          16
AES/ECB/NoPadding             16                          不支持
AES/ECB/PKCS5Padding          32                          16
AES/ECB/ISO10126Padding       32                          16
AES/OFB/NoPadding             16                          原始数据长度
AES/OFB/PKCS5Padding          32                          16
AES/OFB/ISO10126Padding       32                          16
AES/PCBC/NoPadding            16                          不支持
AES/PCBC/PKCS5Padding         32                          16
AES/PCBC/ISO10126Padding      32                          16
可以看到,在原始数据长度为16的整数倍时,假如原始数据长度等于16*n,则使用NoPadding时加密后数据长度等于16*n,其它情况下加密数据长度等于16*(n+1)。在不足16的整数倍的情况下,假如原始数据长度等于16*n+m[其中m小于16],除了NoPadding填充之外的任何方式,加密数据长度都等于16*(n+1);NoPadding填充情况下,CBC、ECB和PCBC三种模式是不支持的,CFB、OFB两种模式下则加密数据长度等于原始数据长度。

      可惜上面url中的例子是c实现的。NoPadding模式只支持16字节长度的字符加密,长度不够的时候可以补足再加密,解密后再trim下。ECB模式是最简单,不需要iv参数,其他的CipherMode需要iv参数。CipherMode如何选择在这里有描述
ECB should not be used if encrypting more than one block of data with the same key
CBC, OFB and CFB are identical, however OFB/CFB is better because you only need encryption and not decryption, which can save code space
CTR is used if you want good parallelization (ie. speed), instead of CBC/OFB/CFB
XTS mode is the most common if you are encoding a random accessible data (like a hard disk or RAM)
OCB is by far the best mode, as it allows encryption and authentication in a single pass. However there are patents on it in USA.

      好吧,回到实际问题,手头的例子是AES/CBC/NoPadding,原因是支持跨平台,这里有详细说明http://my.oschina.net/Jacker/blog/86383。最终搓在一起,整了个例子
public enum CipherMode {
CBC("CBC"),
ECB("ECB"),
CTR("CTR"),
OCB("OCB"),
CFB("CFB");
private String code;
private CipherMode(String code) {
this.code = code;
}
public String getCode() {
return code;
}
}
public enum PaddingMode {
Nopadding("Nopadding"),
PKCS5Padding("PKCS5Padding"),
ISO10126Padding("ISO10126Padding");
//ZeroPadding("ZeroPadding");//java 不支持ZeroPadding
private String padding;
private PaddingMode(String padding) {
this.padding = padding;
}
public String getPadding() {
return padding;
}
}
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
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.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AesGenerator {
private static final String UTF_8 = "UTF-8";
private static final String alogrithm = "AES";
private CipherMode cipherMode;
private PaddingMode paddingMode;
private SecretKeySpec skeySpec;
private IvParameterSpec ivParamSpec;
private Cipher cipher;
private String AES_CIPHER_PADDING_MODE;
private boolean simple;
/**
* ECB模式没有iv参数,NoPadding模式需对源自动补足至16位整数倍
java.security.NoSuchAlgorithmException: Cannot find any provider supporting 
AES/CBC/ZeroPadding   AES/OCB/ISO10126Padding   AES/OCB/PKCS5Padding
AES_CIPHER_PADDING_MODE AES/OCB/Nopadding
AES/CTR/NoPadding   AES/CTR/ISO10126Padding
有很多种模式组合不支持的
*/
public AesGenerator(CipherMode cipherMode, PaddingMode paddingMode, String key, String iv, boolean simple) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
super();
if (simple) {
this.simple = simple;
KeyGenerator keyGenerator = KeyGenerator.getInstance(alogrithm);
keyGenerator.init(128, new SecureRandom(key.getBytes()));
SecretKey secretKey = keyGenerator.generateKey();
byte[] raw = secretKey.getEncoded();
skeySpec = new SecretKeySpec(raw, alogrithm);
cipher = Cipher.getInstance(alogrithm);
} else {
if (cipherMode == null || paddingMode == null) {
throw new UnsupportedOperationException("CipherMode and PaddingMode must not be null");
}
this.cipherMode = cipherMode;
this.paddingMode = paddingMode;
byte[] raw = hexStringToBytes(getMD5Str(key));
       skeySpec = new SecretKeySpec(raw, "AES");
       AES_CIPHER_PADDING_MODE = alogrithm + "/" + cipherMode.getCode() + "/" + paddingMode.getPadding();
       cipher = Cipher.getInstance(AES_CIPHER_PADDING_MODE);
if (this.cipherMode.equals(CipherMode.ECB)) {
} else {
ivParamSpec = new IvParameterSpec(hexStringToBytes(getMD5Str(iv)));
}
}

}
public AesGenerator(CipherMode cipherMode, PaddingMode paddingMode, String key, String iv) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
this(cipherMode, paddingMode, key, iv, false);
}
public String encrypt(String source) throws InvalidKeyException, IllegalBlockSizeException, 
BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
if (simple) {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
if (this.cipherMode.equals(CipherMode.ECB)) {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
} else {
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivParamSpec);
}
if (this.paddingMode.equals(PaddingMode.Nopadding)) {
int len = source.getBytes("UTF-8").length;
       int m = len % 16;
       if (m != 0) {
           for (int i = 0; i < 16 - m; i++) {
            source += " ";
           }
       }
}
}
byte[] encrypted = cipher.doFinal(source.getBytes(UTF_8));
return bytesToHexString(encrypted);
}
public String decrypt(String source) throws InvalidKeyException, IllegalBlockSizeException, 
BadPaddingException, InvalidAlgorithmParameterException, UnsupportedEncodingException {
if (simple || this.cipherMode.equals(CipherMode.ECB)) {
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivParamSpec);
}
byte[] encrypted1 = hexStringToBytes(source);
byte[] original = cipher.doFinal(encrypted1);
return new String(original, "UTF-8");
}
public static byte[] hexStringToBytes(String hexString) {
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }
public static String bytesToHexString(byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }
private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }
public static String getMD5Str(String strIn) throws NoSuchAlgorithmException, UnsupportedEncodingException{
        MessageDigest messageDigest = MessageDigest.getInstance("MD5");
        messageDigest.reset();
        messageDigest.update(strIn.getBytes("UTF-8"));
        byte[] byteArray = messageDigest.digest();
        return bytesToHexString(byteArray);
    }
public static String getAlogrithm() {
return alogrithm;
}
public CipherMode getCipherMode() {
return cipherMode;
}
public PaddingMode getPaddingMode() {
return paddingMode;
}
}
用个工厂类来创建
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import javax.crypto.NoSuchPaddingException;

public class AesGeneratorFactory {
private static Map<String, AesGenerator> tools = new HashMap<String, AesGenerator>();
private AesGeneratorFactory(){}
public static AesGenerator createAesGenerator(CipherMode cipherMode, PaddingMode paddingMode, String key, String iv, boolean simple) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
String toolKey = AesGenerator.getAlogrithm() + "/" + (cipherMode == null ? "" : cipherMode.getCode()) + 
        "/" + (paddingMode == null ? "" : paddingMode.getPadding()) + "/" + key + "/" + iv;
if (tools.containsKey(toolKey)) {
return tools.get(toolKey);
}
AesGenerator generator = new AesGenerator(cipherMode, paddingMode, key, iv, simple);
tools.put(toolKey, generator);
return generator;
}
public static AesGenerator createAesGenerator(CipherMode cipherMode, PaddingMode paddingMode, String key, String iv) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
return createAesGenerator(cipherMode, paddingMode, key, iv, false);
}
//特定的创建方法
public static AesGenerator createXxxAesGenerator(String key, String iv) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
return createAesGenerator(CipherMode.CBC, PaddingMode.Nopadding, key, iv);
}
public static AesGenerator createSimpleAesGenerator(String key) 
throws NoSuchAlgorithmException, NoSuchPaddingException, UnsupportedEncodingException {
return createAesGenerator(null, null, key, null, true);
}
}
写个测试类来测试
import static org.junit.Assert.*;

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

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;

import org.junit.Test;

public class AesGeneratorTest {

@Test
public void testAesGenerator() throws Exception{
final String source = "某个json字符串";
String key = "01234567890ABCDEFG";
for (CipherMode cipherMode : CipherMode.values()) {
for (PaddingMode paddingMode : PaddingMode.values()) {
if ( (cipherMode.equals(CipherMode.CTR) && (paddingMode.equals(PaddingMode.Nopadding) || paddingMode.equals(PaddingMode.ISO10126Padding)))
|| (cipherMode.equals(CipherMode.OCB) && 
((paddingMode.equals(PaddingMode.Nopadding)) || paddingMode.equals(PaddingMode.PKCS5Padding) || paddingMode.equals(PaddingMode.ISO10126Padding)))
) {
continue;
}
System.out.println(cipherMode.getCode() + " -- " + paddingMode.getPadding());
verifyAES(cipherMode, paddingMode, source, key, key);
}
}
}

private void verifyAES(CipherMode cipherMode, PaddingMode paddingMode, final String source, String key, String iv)
throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {

AesGenerator a = AesGeneratorFactory.createAesGenerator(cipherMode, paddingMode, key, iv);
final String encryptStr = a.encrypt(source);
System.out.println(encryptStr);
final String decryptStr = a.decrypt(encryptStr);
assertEquals(source, new String(decryptStr).trim());
}

}
运行,测试通过。可以用了
  评论这张
 
阅读(12564)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017