AES(Advanced Encrypt Security)加密属于对称加密,即密钥既用来加密也用来解密。所以密钥必须保存在一个非常安全的地方,如果丢失,攻击者就可以利用其进行数据解密。
AES加密属于分组加密,即一次处理若干个数据位(128bit);128bit的明文会产生同样长度的密文。一次处理一个数据位的称为流加密。
AES加密的密钥可以从0-256bit。
当然,Android还支持其他的加密方式,如 Camellia、Blowfish、Twofish;每一种加密的分组长度及密钥长度都不同。但是实现加密与解密的步骤与AES加密基本一致。
下面展示了使用AES进行加密和解密的过程:
package com.example.aesdemo;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;
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 Crypto {
private static final int KEYSIZE = 128;
public static byte[] encrypt(byte[] key,byte[] data){
SecretKeySpec secretKeySpec=new SecretKeySpec(key, "AES");
Cipher cipher;
byte[] cipherText=null;
try {
cipher=Cipher.getInstance("AES"); // create a new Cipher
//initialize cipher
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
//encrypt or decrypt according the opmode when initialize
cipherText=cipher.doFinal(data);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return cipherText;
}
public static byte[] decrypt(byte[] key,byte[] data){
SecretKeySpec secretKeySpec=new SecretKeySpec(key, "AES");
Cipher cipher;
byte[] cipherText=null;
try {
cipher=Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
cipherText=cipher.doFinal(data);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return cipherText;
}
public static byte[] generateKey(byte[] randomNumberSeed){
SecretKey secretKey=null;
try {
KeyGenerator keyGenerator=KeyGenerator.getInstance("AES");
SecureRandom random=SecureRandom.getInstance("SHA1PRNG");
random.setSeed(randomNumberSeed);
keyGenerator.init(KEYSIZE, random);
secretKey=keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return secretKey.getEncoded();
}
}
SecretKey 对称密钥。使用getEncoded()将密钥转化为字节数组。
SecretKeySpec:此类通过字节数组生成一个SecretKey:new SecretKeySpec(key,"AES")。
SecureRandom 强加密随机生成器。SecureRandom.getInstance("SHA1PRNG"),生成使用SHA1PRNG算法的加密随机生成器。random.setSeed(randomNumberSeed);重新设置此随机对象的种子。给定种子补充而不是取代现有的种子。因此,保证重复调用不会降低随机性。
KeyGenerator 对程密钥生成器。可以使用KeyGenerator.getInstance("AES")进行初始化。
keyGenerator.init(KEYSIZE, random); 初始化KeyGenerator,keysize是密钥长度,random即随机生成器。
通过keyGenerator.generateKey();来获取密钥。
Cipher 用于加密和解密的类。
Cipher.init();用来初始化。下面列举部分初始化方法:
public final void init(int opmode,Key key) throwsInvalidKeyException:
opmode:传入初始化Cipher的操作:加密、解密、密钥包装或密钥解包
key:密钥
public final void init(int opmode,Key key,SecureRandom random) throws InvalidKeyException :
random : 随机源,可用于参数生成。
public final void init(int opmode, Key key,AlgorithmParameterSpec params)throwsInvalidKeyException,InvalidAlgorithmParameterException:
params :初始化向量。
上文的代码中使用的密钥长度为128bit,但在实际应用中推荐使用算法支持的最大密钥长度,这样可以使得对你的密钥的暴力破解变得更难。
我们知道分组加密是对明文按照固定长度的分组进行加密,这就会存在一个数据填充的问题。有两种数据填充的方法:
1、补零:位数不够的用0补充。
2、PKCS5/7:取需要填充的位数作为填充内容。如,还有10位要填充,则填充内容位0A。
分组密码有以下集中模式:
ECB模式(电子密码本):一个明文数据块加密成一个密文数据块,下一个明文数据块加密成下一个密文数据块;这种加密比较简单,而且,如果存在相同的密文数据块就很容易推断出使用了ECB加密,这样破解者就只用解密部分密文,而不必解密整个密文。
CBC模式(密码分组链接):使用一个初始化向量与第一组明文进行异或操作,然后进行加密,然后再将密文作为向量与下一组明文进行异或操作。这样保证了每组密文依赖它前面的所有明文。
PCBC(填充密码分组)
CFB(密码反馈)
OFB(输出反馈)
下面介绍使用CBC模式和PKCS5Padding进行填充的加密和解密,其中,系统默认使用的是 AES/CBC/PKCS5Padding:
package com.dream.cipherdemo;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import android.content.Context;
import android.util.Log;
public class Crypto {
private static final String TAG = Crypto.class.getSimpleName();
private static final String transformation = "AES/CBC/PKCS5Padding";
private static final String algorithm = "AES";
private Context context;
public Crypto(Context context) {
super();
this.context = context;
}
private byte[] cipher(int mode,byte[] data){
byte[] result=null;
KeyManager keyManager=new KeyManager(context);
SecretKeySpec secretKeySpec=new SecretKeySpec(keyManager.getKey(),
algorithm);
IvParameterSpec params=new IvParameterSpec(keyManager.getIv());
try {
Cipher cipher=Cipher.getInstance(transformation);
cipher.init(mode, secretKeySpec, params);
result=cipher.doFinal(data);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidKeyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BadPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
public byte[] encrypt(byte[] data){
return cipher(Cipher.ENCRYPT_MODE, data);
}
public byte[] decrypt(byte[] data){
return cipher(Cipher.DECRYPT_MODE, data);
}
}
我这里将密钥和向量分别保存在文件中,密文保存在另一个文件中,这样虽然存在直接攻击的危险,但是可以使我们免受非直接攻击。