前言
用户数据无论保存在SharePreferences,还是保存在SQLite数据库里,都有必要对关键数据进行加密。加密算法多种多样。常见的有MD5、RSA、AES、3DES。
1.MD5加密
MD5是不可逆的加密算法,也就是无法解密,主要用于客户端的用户密码加密,MD5算法的加密代码如下:
public class MD5Util{
public static String encryp(String raw){
String md5Str=raw;
try{
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(raw.getBytes());
byte[] encryContext=md.digets();
int i;
StringBuffer buf=new StringBuffer("");
for(int offset=0;offset<encryContext.length;offset++){
i=encryContext[offset];
if(i<0){
i+=256;
}
if(i<16){
buf.append("0");
}
buf.append(Integer.toHexString(i));
}
md5Str=buf.toString();
}catch(NOSuchAlgorithmException e){
e.printStackTrace();
}
return md5Str;
}
}
2.RSA加密
RSA算法在客户端使用公钥加密,在服务端使用私钥解密。这样一来,即使加密的公钥被泄露,没有私钥仍然无法解密。
下面是RSA加密的3个注意事项。
(1)需要导入加密算法的依赖包bcprov-jdk16-1.46.jar,该jar包要放在当前模块的libs目录下。
(2)RSA在加密的结果是字节数组,经过BASE64编码才能形成最终的加密字符串
(3)根据需求要对加密前的字符串做reverse倒序处理。
public class RSAUtil {
private static final String TAG = "RSAUtil";
private static final String Algorithm = "RSA";
private static String RSAKeyStore = "E:/RSAKey.txt";
生成密钥对
private static KeyPair generateKeyPair() throws Exception {
try {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(Algorithm,
new org.bouncycastle.jce.provider.BouncyCastleProvider());
这个值关系到块加密的大小,可以更改,但是不要太大,否则效率会降低
final int KEY_SIZE = 1024;
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
KeyPair keyPair = keyPairGen.generateKeyPair();
saveKeyPair(keyPair);
return keyPair;
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
private static KeyPair getKeyPair() throws Exception {
FileInputStream fis = new FileInputStream(RSAKeyStore);
ObjectInputStream oos = new ObjectInputStream(fis);
KeyPair kp = (KeyPair) oos.readObject();
oos.close();
fis.close();
return kp;
}
private static void saveKeyPair(KeyPair kp) throws Exception {
FileOutputStream fos = new FileOutputStream(RSAKeyStore);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(kp);
oos.close();
fos.close();
}
生成公钥
private static RSAPublicKey generateRSAPublicKey(byte[] modulus,
byte[] publicExponent) throws Exception {
KeyFactory keyFac = null;
try {
keyFac = KeyFactory.getInstance(Algorithm,
new org.bouncycastle.jce.provider.BouncyCastleProvider());
} catch (NoSuchAlgorithmException ex) {
throw new Exception(ex.getMessage());
}
RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(
modulus), new BigInteger(publicExponent));
try {
return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
} catch (InvalidKeySpecException ex) {
throw new Exception(ex.getMessage());
}
}
生成私钥
private static RSAPrivateKey generateRSAPrivateKey(byte[] modulus,
byte[] privateExponent) throws Exception {
KeyFactory keyFac = null;
try {
keyFac = KeyFactory.getInstance(Algorithm,
new org.bouncycastle.jce.provider.BouncyCastleProvider());
} catch (NoSuchAlgorithmException ex) {
throw new Exception(ex.getMessage());
}
RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(
modulus), new BigInteger(privateExponent));
try {
return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
} catch (InvalidKeySpecException ex) {
throw new Exception(ex.getMessage());
}
}
通过公钥byte[]将公钥还原,适用于RSA算法
private static PublicKey getPublicKey(byte[] keyBytes)
throws NoSuchAlgorithmException, InvalidKeySpecException {
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(Algorithm);
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
通过私钥byte[]将公钥还原,适用于RSA算法
private static PrivateKey getPrivateKey(byte[] keyBytes)
throws NoSuchAlgorithmException, InvalidKeySpecException {
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(Algorithm);
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
加密
private static byte[] encrypt(PublicKey pk, byte[] data) throws Exception {
try {
Cipher cipher = Cipher.getInstance(Algorithm,
new org.bouncycastle.jce.provider.BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, pk);
int blockSize = cipher.getBlockSize();
int outputSize = cipher.getOutputSize(data.length);
int leavedSize = data.length % blockSize;
int blocksSize = leavedSize != 0 ? data.length / blockSize + 1
: data.length / blockSize;
byte[] raw = new byte[outputSize * blocksSize];
int i = 0;
while (data.length - i * blockSize > 0) {
if (data.length - i * blockSize > blockSize) {
cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
} else {
cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
}
i++;
}
return raw;
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
//解密
private static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception {
try {
Cipher cipher = Cipher.getInstance(Algorithm,
new org.bouncycastle.jce.provider.BouncyCastleProvider());
cipher.init(cipher.DECRYPT_MODE, pk);
int blockSize = cipher.getBlockSize();
ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
int j = 0;
while (raw.length - j * blockSize > 0) {
bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
j++;
}
return bout.toByteArray();
} catch (Exception e) {
throw new Exception(e.getMessage());
}
}
// 使用N、e值还原公钥
private static PublicKey getPublicKey(String modulus, String publicExponent, int radix)
throws NoSuchAlgorithmException, InvalidKeySpecException {
BigInteger bigIntModulus = new BigInteger(modulus, radix);
BigInteger bigIntPrivateExponent = new BigInteger(publicExponent, radix);
RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus,
bigIntPrivateExponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
// 使用N、d值还原私钥
private static PrivateKey getPrivateKey(String modulus, String privateExponent, int radix)
throws NoSuchAlgorithmException, InvalidKeySpecException {
BigInteger bigIntModulus = new BigInteger(modulus, radix);
BigInteger bigIntPrivateExponent = new BigInteger(privateExponent, radix);
RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(bigIntModulus,
bigIntPrivateExponent);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
//加密函数
public static String encodeRSA(RSAKeyData key_data, String src) {
if (key_data == null) {
//默认的密钥对
key_data = new RSAKeyData();
key_data.public_key = "10001";
key_data.private_key = "";
key_data.modulus = "c7f668eccc579bb75527424c21be31c104bb44c921b4788ebc82cddab5042909eaea2dd706431531392d79890f9091e13714285a7e79e9d1836397f847046ef2519c9b65022b48bf157fe409f8a42155734e65467d04ac844dfa0c2ae512517102986ba9b62d67d4c920eae40b2f11c363b218a703467d342faa81719f57e2c3";
key_data.radix = 16;
}
try {
PublicKey key = getPublicKey(key_data.modulus, key_data.public_key, key_data.radix);
String rev = encodeURL(new StringBuilder(src).reverse().toString());
byte[] en_byte = encrypt(key, rev.getBytes());
String base64 = encodeURL(ConvertBytesToBase64.BytesToBase64String(en_byte));
return base64;
} catch (Exception e) {
e.printStackTrace();
return "RSA加密失败";
}
}
//URL编码
private static String encodeURL(String str) {
String encode_str = str;
try {
encode_str = URLEncoder.encode(str, "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
return encode_str;
}
//URL解码
private static String decodeURL(String str) {
String decode_str = str;
try {
decode_str = URLDecoder.decode(str, "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
return decode_str;
}
public static class RSAKeyData {
public String modulus;
public String public_key;
public String private_key;
public int radix;
public RSAKeyData() {
modulus = "";
public_key = "";
private_key = "";
radix = 0;
}
}
}
3.AES加密
AES是设计用来替换DES的高级加密算法。(该算法是可逆算法,支持对加密字符串进行解密,前提是解密是密钥必须与加密是一致)。
下面是该算法加密和解密的代码
public class AesUtil {
private static final String Algorithm = "AES";
private final static String HEX = "0123456789ABCDEF";
//加密函数。key为密钥
public static String encrypt(String key, String src) throws Exception {
byte[] rawKey = getRawKey(key.getBytes());
byte[] result = encrypt(rawKey, src.getBytes());
return toHex(result);
}
//解密函数。key值必须和加密时的key一致
public static String decrypt(String key, String encrypted) throws Exception {
byte[] rawKey = getRawKey(key.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance(Algorithm);
// SHA1PRNG 强随机种子算法, 要区别Android 4.2.2以上版本的调用方法
SecureRandom sr = null;
if (android.os.Build.VERSION.SDK_INT >= 17) {
sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
} else {
sr = SecureRandom.getInstance("SHA1PRNG");
}
sr.setSeed(seed);
kgen.init(256, sr); // 256位或128位或192位
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] key, byte[] src) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, Algorithm);
Cipher cipher = Cipher.getInstance(Algorithm);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(src);
return encrypted;
}
private static byte[] decrypt(byte[] key, byte[] encrypted) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(key, Algorithm);
Cipher cipher = Cipher.getInstance(Algorithm);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
private static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++) {
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2), 16).byteValue();
}
return result;
}
private static String toHex(byte[] buf) {
if (buf == null) {
return "";
}
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
}
4. 3DES加密
3DES(tripe DES)是三重数据加密算法,相当于对每个数据块应用3次DES加密算法。3DE算法通过增加密钥的长度防范加密数据被破解。在实际开发时候,3DES的密钥必须是24位的字节数组,过短过长在运行时都会报错java.security.InvalidKeyException。另外,3DES加密生成的是字节数组,也得通过BASE64编码位文本形式的加密字符串。
该算法的加密和解密代码如下:
public class Des3Util {
// 定义加密算法,DESede即3DES
private static final String Algorithm = "DESede";
//加密函数。key为密钥
public static String encrypt(String key, String raw) {
byte[] enBytes = encryptMode(key, raw.getBytes());
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(enBytes);
}
//解密函数。key值必须和加密时的key一致
public static String decrypt(String key, String enc) {
try {
BASE64Decoder decoder = new BASE64Decoder();
byte[] enBytes = decoder.decodeBuffer(enc);
byte[] deBytes = decryptMode(key, enBytes);
return new String(deBytes);
} catch (IOException e) {
e.printStackTrace();
return enc;
}
}
private static byte[] encryptMode(String key, byte[] src) {
try {
SecretKey deskey = new SecretKeySpec(build3DesKey(key), Algorithm);
Cipher cipher = Cipher.getInstance(Algorithm);
cipher.init(Cipher.ENCRYPT_MODE, deskey);
return cipher.doFinal(src);
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static byte[] decryptMode(String key, byte[] src) {
try {
SecretKey deskey = new SecretKeySpec(build3DesKey(key), Algorithm);
Cipher cipher = Cipher.getInstance(Algorithm);
cipher.init(Cipher.DECRYPT_MODE, deskey);
return cipher.doFinal(src);
}catch (Exception e) {
e.printStackTrace();
return null;
}
}
//根据字符串生成密钥24位的字节数组
private static byte[] build3DesKey(String keyStr) throws UnsupportedEncodingException {
byte[] key = new byte[24];
byte[] temp = keyStr.getBytes("UTF-8");
if (key.length > temp.length) {
System.arraycopy(temp, 0, key, 0, temp.length);
}else {
System.arraycopy(temp, 0, key, 0, key.length);
}
return key;
}
}
下篇将分析对APK安装包安全加固过程