*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布
前言
HI,欢迎来到裴智飞的《每周一博》。今天是九月第四周,我给大家介绍一下安卓如何使用加解密。
加解密的方式有很多种;Base64加密,单向加密(MD5和SHA),对称加密(DES和AES),非对称加密(RSA),非数字签名等。
- Base64算法
Base64是一种基于64个基本字符,加密后的内容只包含这64个字符,加密后长度会变大。它是最简单的一种算法,一般用于加密URL,还有在进行对称和非对称加解密前也会进行一下Base64转换,保证没有特殊字符,从而进行网络传输。
// 需要引入包:java.util.Base64
// Base64加密
private static String encode(String str) {
byte[] encodeBytes = Base64.getEncoder().encode(str.getBytes());
return new String(encodeBytes);
}
// Base64解密
private static String decode(String str) {
byte[] decodeBytes = Base64.getDecoder().decode(str.getBytes());
return new String(decodeBytes);
}
- 单向加密算法
单向加密主要包括MD5(消息摘要算法)和SHA(安全散列算法),是不可逆的,只能加密不能解密,主要用于客户端。
代码实现:先初始化一个MessageDigest对象,传入MD5或SHA,该对象通过update方法获取原始数据,再调用digest方法完成哈希计算,然后把字节数组逐位转换为十六进制数,最后拼装加密字符串。
// MD5加密
private static String toMd5(String str) {
// 实例化一个指定摘要算法为MD5的MessageDigest对象
MessageDigest algorithm;
try {
algorithm = MessageDigest.getInstance("MD5");
// 重置摘要以供再次使用
algorithm.reset();
// 使用bytes更新摘要
algorithm.update(str.getBytes());
// 使用指定的byte数组对摘要进行最后更新,然后完成摘要计算
return toHexString(algorithm.digest(), "");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
// SHA加密
private static String toSHA(String str) {
// 实例化一个指定摘要算法为SHA的MessageDigest对象
MessageDigest algorithm;
try {
algorithm = MessageDigest.getInstance("SHA");
// 重置摘要以供再次使用
algorithm.reset();
// 使用bytes更新摘要
algorithm.update(str.getBytes());
// 使用指定的byte数组对摘要进行最后更新,然后完成摘要计算
return toHexString(algorithm.digest(), "");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
// 将字符串中的每个字符转换为十六进制
private static String toHexString(byte[] bytes, String separtor) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append("0");
}
hexString.append(hex).append(separtor);
}
return hexString.toString();
}
- 对称加密算法
对称加密是指加解密使用同一个秘钥,这需要双方事前都知道该秘钥。主要分为DES和AES,一般会在加解密前进行Base64转换,去除特殊字符,从而进行网络传输。
(1). DES:数据标准加密
DES算法经过16论迭代,使用56比特长度密钥加密64比特长度(分组长度)的明文获得64比特的密文。
public class DESUtil {
// 初始化向量
private static byte[] iv = { 'a', 'b', 'c', 'd', 'e', 1, 2, '*' };
// DES加密
// encryptText为原文
// encryptKey为密匙
private static String encryptDES(String encryptText, String encryptKey)
throws Exception {
// 实例化IvParameterSpec对象,使用指定的初始化向量
IvParameterSpec spec = new IvParameterSpec(iv);
// 实例化SecretKeySpec类,根据字节数组来构造SecretKeySpec
SecretKeySpec key = new SecretKeySpec(encryptKey.getBytes(), "DES");
// 创建密码器
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
// 用密码初始化Cipher对象
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
// 执行加密操作
byte[] encryptData = cipher.doFinal(encryptText.getBytes());
// 返回加密后的数据
return Base64.getEncoder().encodeToString(encryptData);
}
// 解密
private static String decryptDES(String decryptString, String decryptKey)
throws Exception {
// 先使用Base64解密
byte[] base64byte = Base64.getDecoder().decode(decryptString);
// 实例化IvParameterSpec对象,使用指定的初始化向量
IvParameterSpec spec = new IvParameterSpec(iv);
// 实例化SecretKeySpec类,根据字节数组来构造SecretKeySpec
SecretKeySpec key = new SecretKeySpec(decryptKey.getBytes(), "DES");
// 创建密码器
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
// 用密码初始化Cipher对象
cipher.init(Cipher.DECRYPT_MODE, key, spec);
// 获取解密后的数据
byte decryptedData[] = cipher.doFinal(base64byte);
// 将解密后数据转换为字符串输出
return new String(decryptedData);
}
}
(2). AES:高级加密标准
AES算法用于替代DES,保护敏感信息,AES算法的分组长度为128比特,其密钥长度分别为128比特,192比特,256比特。
public class AESUtil {
// 采用对称分组密码体制,密钥长度的最少支持为128、192、256
String key = "abcdefghijklmnop";
// 初始化向量参数,AES 为16bytes. DES 为8bytes, 16*8=128
String initVector = "0000000000000000";
IvParameterSpec iv;
SecretKeySpec skeySpec;
Cipher cipher;
private static class HOLDER {
private static AESUtil instance = new AESUtil();
}
public static AESUtil getInstance() {
return HOLDER.instance;
}
private AESUtil() {
try {
iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
// 这是CBC模式
// cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
// 默认就是ECB模式
cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
public String encrypt(String value) {
try {
// CBC模式需要传入向量,ECB模式不需要
// cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(value.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public String decrypt(String encrypted) {
try {
// CBC模式需要传入向量,ECB模式不需要
// cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] original = cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
可以看到AES和DES在SecretKeySpec和Cipher创建的时候有不同,Cipher传入的参数"AES/ECB/PKCS5PADDING"分别是“算法/工作模式/填充模式”,工作模式默认是ECB,不需要偏移,也就不需要传入向量,而CBC和OFB是带偏移的,需要传入向量。
- 非对称加密算法
非对称加密是指加解密使用不同的秘钥,即公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。比如你向银行请求将公钥公钥发给你,并使用公钥对消息加密,那么只有持有私钥的银行才能对你的消息解密。与对称加密不同的是,私钥不需要通过网络发送出去,因此安全性大大提高。
非对称加密主要是RSA算法,RSA加解密本身对明文或者密文的长度有限制,这里我们的加密算法可以支持分段加解密。
public class RSAUtil {
public static final String RSA = "RSA";
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
// 秘钥默认长度
public static final int DEFAULT_KEY_SIZE = 2048;
// 当要加密的内容超过bufferSize,则采用partSplit进行分块加密
public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes();
// 当前秘钥支持加密的最大字节数
public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;
// 随机生成RSA密钥对,密钥长度,范围:512~2048
public static KeyPair generateRSAKeyPair(int keyLength) {
try {
KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
kpg.initialize(keyLength);
return kpg.genKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
/**
* 私钥加密
* @param data 待加密数据
* @param privateKey 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {
// 得到私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 数据加密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
}
// 使用私钥进行解密
public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
// 得到私钥
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PrivateKey keyPrivate = kf.generatePrivate(keySpec);
// 解密数据
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte[] arr = cp.doFinal(encrypted);
return arr;
}
// 用公钥对字符串进行加密
public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
// 得到公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 加密数据
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(data);
}
/**
* 公钥解密
* @param data 待解密数据
* @param publicKey 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
// 得到公钥
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
KeyFactory kf = KeyFactory.getInstance(RSA);
PublicKey keyPublic = kf.generatePublic(keySpec);
// 数据解密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
return cipher.doFinal(data);
}
// 以下开始分段解密
// 使用私钥分段解密
public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted, byte[] privateKey) throws Exception {
int splitLen = DEFAULT_SPLIT.length;
if (splitLen <= 0) {
return decryptByPrivateKey(encrypted, privateKey);
}
int dataLen = encrypted.length;
List allBytes = new ArrayList(1024);
int latestStartIndex = 0;
for (int i = 0; i < dataLen; i++) {
byte bt = encrypted[i];
boolean isMatchSplit = false;
if (i == dataLen - 1) {
// 到data的最后了
byte[] part = new byte[dataLen - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPrivateKey(part, privateKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
} else if (bt == DEFAULT_SPLIT[0]) {
// 这个是以split[0]开头
if (splitLen > 1) {
if (i + splitLen < dataLen) {
// 没有超出data的范围
for (int j = 1; j < splitLen; j++) {
if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
break;
}
if (j == splitLen - 1) {
// 验证到split的最后一位,都没有break,则表明已经确认是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,则已经匹配了
isMatchSplit = true;
}
}
if (isMatchSplit) {
byte[] part = new byte[i - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPrivateKey(part, privateKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
// 私钥分段加密
public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPrivateKey(data, privateKey);
}
List allBytes = new ArrayList(2048);
int bufIndex = 0;
int subDataLoop = 0;
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
for (int i = 0; i < dataLen; i++) {
buf[bufIndex] = data[i];
if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
subDataLoop++;
if (subDataLoop != 1) {
for (byte b : DEFAULT_SPLIT) {
allBytes.add(b);
}
}
byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);
for (byte b : encryptBytes) {
allBytes.add(b);
}
bufIndex = 0;
if (i == dataLen - 1) {
buf = null;
} else {
buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
}
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
// 用公钥对字符串进行分段加密
public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPublicKey(data, publicKey);
}
List allBytes = new ArrayList(2048);
int bufIndex = 0;
int subDataLoop = 0;
byte[] buf = new byte[DEFAULT_BUFFERSIZE];
for (int i = 0; i < dataLen; i++) {
buf[bufIndex] = data[i];
if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
subDataLoop++;
if (subDataLoop != 1) {
for (byte b : DEFAULT_SPLIT) {
allBytes.add(b);
}
}
byte[] encryptBytes = encryptByPublicKey(buf, publicKey);
for (byte b : encryptBytes) {
allBytes.add(b);
}
bufIndex = 0;
if (i == dataLen - 1) {
buf = null;
} else {
buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
}
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
// 公钥分段解密
public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey) throws Exception {
int splitLen = DEFAULT_SPLIT.length;
if (splitLen <= 0) {
return decryptByPublicKey(encrypted, publicKey);
}
int dataLen = encrypted.length;
List allBytes = new ArrayList(1024);
int latestStartIndex = 0;
for (int i = 0; i < dataLen; i++) {
byte bt = encrypted[i];
boolean isMatchSplit = false;
if (i == dataLen - 1) {
// 到data的最后了
byte[] part = new byte[dataLen - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPublicKey(part, publicKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
} else if (bt == DEFAULT_SPLIT[0]) {
// 这个是以split[0]开头
if (splitLen > 1) {
if (i + splitLen < dataLen) {
// 没有超出data的范围
for (int j = 1; j < splitLen; j++) {
if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
break;
}
if (j == splitLen - 1) {
// 验证到split的最后一位,都没有break,则表明已经确认是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,则已经匹配了
isMatchSplit = true;
}
}
if (isMatchSplit) {
byte[] part = new byte[i - latestStartIndex];
System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
byte[] decryptPart = decryptByPublicKey(part, publicKey);
for (byte b : decryptPart) {
allBytes.add(b);
}
latestStartIndex = i + splitLen;
i = latestStartIndex - 1;
}
}
byte[] bytes = new byte[allBytes.size()];
{
int i = 0;
for (Byte b : allBytes) {
bytes[i++] = b.byteValue();
}
}
return bytes;
}
}
这个类包含了RSA普通加解密和支持分段加解密的8个方法,调用的时候需要公私对应,比如用公钥加密,就得用私钥解密,用私钥加密,就得用公钥解密。
这里对几种加密算法做一个总结:
单向加密算法常用于客户端,因为无法解密,也就只能进行单向验证,一般来说它是不安全的;
DES是不安全的,3DES可以代替DES使用。3DES是三重数据加密算法,相当于对每个数据块应用3次DES加密算法。因为原先DES算法的密钥长度过短,容易遭到暴力破解,所以3DES算法通过增加密钥的长度防范加密数据被破解;
对称加密的优缺点:
优点:算法公开,计算量小,加密速度快,加密效率高
缺点:秘钥的管理和分发非常困难,不够安全非对称加密的优缺点:
优点:安全性更高,公钥是公开的,秘钥是自己保存的,不需要将私钥给别人
缺点:加密和解密花费时间长,速度慢,只适合对少量数据进行加密安全性上AES建议使用128位以上,RSA建议使用2048位以上,另外对于密钥的存储,建议存在so文件里,而不要存在java代码里;
测试加解密耗时:
- 测试安全:这个需要配合抓包工具去进行测试,看看抓到的包的内容是否是加密过得,而不是unknow。
结尾:
本周给分享了安卓中的加解密的一些常用知识,侧重实现方面,理论知识不是很多,感兴趣的可以再补充一下这方面的书籍。感谢大家的阅读,我们下周再见。