Android中常見的加簽以及加密
通過MD5進行加簽,RSA通過公鑰加密,私鑰解密(私鑰解密、公鑰解密)
為什么要對數(shù)據(jù)進行加簽、加密?
在一個Android應(yīng)用中,客戶端與服務(wù)器之間數(shù)據(jù)通信安全是非常重要的。這就涉及到了數(shù)據(jù)的加密。
當(dāng)我們的請求報文被截取,是可以被修改里面的數(shù)據(jù)的,這就涉及到數(shù)據(jù)的加簽。
客戶端與服務(wù)端的流程
客戶端
1、先將摘要信息進行加簽
2、將摘要信息和簽名通過公鑰進行加密
服務(wù)端
1、先通過密鑰進行解密,得到明文+簽名
2、將明文通過和客戶端一樣的方式進行加簽
3、驗證簽名一致,為驗簽成功,否則驗簽失敗
加簽和加密的區(qū)別
加簽:是為了驗證數(shù)據(jù)是否被篡改
加密:是為了保證數(shù)據(jù)不被截取
MD5簡介
MD5加密是一種常見的加密算法(消息摘要算法),是一種單項加密算法,是一種不可逆的加密方式。
用途
- 一般用于文件的完整性校驗
- 可以用于對密碼進行加密,數(shù)據(jù)庫只需要存儲密文,來進行比較
- 經(jīng)常被用來驗證簽名的完整性
優(yōu)點
- 壓縮性:任意長度的數(shù)據(jù),算出的MD5值長度都是固定的。
- 容易計算:從原數(shù)據(jù)計算出MD5值很容易。
- 抗修改性:對原數(shù)據(jù)進行任何改動,所得到的MD5值都有很大區(qū)別。
- 強抗碰撞:已知原數(shù)據(jù)和其MD5值,想找到一個相同MD5值得數(shù)據(jù)是非常困難的
缺點
- 可以通過數(shù)據(jù)庫碰撞來暴力破解密文(比如彩虹表)
解決方案
1、通過對明文多次進行加密之后再次進行MD5加密
2、MD5加鹽,通過設(shè)定一個固定唯一的標(biāo)識來作為固定的鹽值,進行加鹽
為什么要加鹽?
因為現(xiàn)在只要是相同的明文,通過md5進行加密,之后的密文都是一樣的,如果被劫取到密文,通過撞庫來把破解明文。
這里放上我使用的工具類
/**
* @Author: DongBo
* @Date: 2020/4/2 10:32
*/
public class Md5Utils {
/**
* 生成含有鹽的密碼
*
* @param password 要加密的密碼
* @return String 含有鹽的密碼
*/
public static String getSaltMD5(Context context, String password) {
StringBuilder sBuilder = new StringBuilder(16);
sBuilder.append(Settings.System.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID));
int len = sBuilder.length();
if (len < 16) {
for (int i = 0; i < 16 - len; i++) {
sBuilder.append("0");
}
}
// 生成最終的加密鹽
String salt = sBuilder.toString();
password = md5Hex(password + salt);
char[] cs = new char[48];
for (int i = 0; i < 48; i += 3) {
cs[i] = password.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = password.charAt(i / 3 * 2 + 1);
}
return String.valueOf(cs);
}
/**
* 獲取十六進制字符串形式的MD5摘要
*/
public static String md5Hex(String src) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(src.getBytes());
return new String(new Hex().encodeHex(bs));
} catch (Exception e) {
return null;
}
}
/**
* 通過Md5進行加簽
*
* @param message
* @return
*/
public static String encryptMd5(String message) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bytes = md5.digest(message.getBytes());
return bytesToHex(bytes);
} catch (Exception e) {
return null;
}
}
/**
* 將加密后的字節(jié)數(shù)組轉(zhuǎn)換成字符串
*
* @param bytes 字節(jié)數(shù)組
* @return
*/
public static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() < 2) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
}



上述三張圖片可以很好的說明,單純的MD5是無法保證數(shù)據(jù)的安全性,需要通過加鹽增加破解的難度。
RSA簡介
RSA是一種常用的非對稱加密算法,所謂非對稱加密是指使用一對密鑰(公鑰和私鑰)進行加密和解密,公鑰人人都可以獲得,用于加密數(shù)據(jù),私鑰保存在服務(wù)器中,用于解密數(shù)據(jù)。
加密解密流程

優(yōu)點
非對稱加密使用一對秘鑰,一個用來加密,一個用來解密,而且公鑰是公開的,私鑰保留在服務(wù)端,即使加密的公鑰泄漏,也不會造成問題。
缺點
非對稱加密的缺點是加密和解密花費時間長、速度慢,只適合對少量數(shù)據(jù)進行加密
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;//秘鑰默認(rèn)長度
public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes(); // 當(dāng)要加密的內(nèi)容超過bufferSize,則采用partSplit進行分塊加密
public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;// 當(dāng)前秘鑰支持加密的最大字節(jié)數(shù)
/**
* 隨機生成RSA密鑰對
*
* @param keyLength 密鑰長度,范圍:512~2048
* 一般1024
* @return
*/
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 原文
*/
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);
// 加密數(shù)據(jù)
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.ENCRYPT_MODE, keyPublic);
return cp.doFinal(data);
}
/**
* 私鑰加密
*
* @param data 待加密數(shù)據(jù)
* @param privateKey 密鑰
* @return byte[] 加密數(shù)據(jù)
*/
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);
// 數(shù)據(jù)加密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
return cipher.doFinal(data);
}
/**
* 公鑰解密
*
* @param data 待解密數(shù)據(jù)
* @param publicKey 密鑰
* @return byte[] 解密數(shù)據(jù)
*/
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);
// 數(shù)據(jù)解密
Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
cipher.init(Cipher.DECRYPT_MODE, keyPublic);
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);
// 解密數(shù)據(jù)
Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
cp.init(Cipher.DECRYPT_MODE, keyPrivate);
byte[] arr = cp.doFinal(encrypted);
return arr;
}
/**
* 用公鑰對字符串進行分段加密
*/
public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPublicKey(data, publicKey);
}
List<Byte> allBytes = new ArrayList<Byte>(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;
}
/**
* 分段加密
*
* @param data 要加密的原始數(shù)據(jù)
* @param privateKey 秘鑰
*/
public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
int dataLen = data.length;
if (dataLen <= DEFAULT_BUFFERSIZE) {
return encryptByPrivateKey(data, privateKey);
}
List<Byte> allBytes = new ArrayList<Byte>(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;
}
/**
* 公鑰分段解密
*
* @param encrypted 待解密數(shù)據(jù)
* @param publicKey 密鑰
*/
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<Byte> allBytes = new ArrayList<Byte>(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,則表明已經(jīng)確認(rèn)是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,則已經(jīng)匹配了
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;
}
/**
* 使用私鑰分段解密
*/
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<Byte> allBytes = new ArrayList<Byte>(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,則表明已經(jīng)確認(rèn)是split段
isMatchSplit = true;
}
}
}
} else {
// split只有一位,則已經(jīng)匹配了
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;
}
}
Map<String, String> parm = new HashMap<>();
parm.put("name", "董博");
parm.put("age", "24");
parm.put("mobile", "15011111111");
String sign = genAppSign(parm);
Log.e("簽名", sign);
keyPair = RSAUtil.generateRSAKeyPair(RSAUtil.DEFAULT_KEY_SIZE);
// 公鑰
publicKey = (RSAPublicKey) keyPair.getPublic();
// 私鑰
privateKey = (RSAPrivateKey) keyPair.getPrivate();
//公鑰加密
byte[] encryptBytes = new byte[0];
try {
encryptBytes = RSAUtil.encryptByPublicKeyForSpilt(sign.getBytes(), publicKey.getEncoded());
} catch (Exception e) {
e.printStackTrace();
}
encryStr = Base64.encode(encryptBytes);
Log.e("RSA", "加密后的數(shù)據(jù):" + encryStr);
//私鑰解密
byte[] decryptBytes = new byte[0];
try {
decryptBytes = RSAUtil.decryptByPrivateKeyForSpilt(Base64.decode(encryStr), privateKey.getEncoded());
} catch (Exception e) {
e.printStackTrace();
}
String decryStr = new String(decryptBytes);
Log.e("RSA", "解密后的數(shù)據(jù):" + decryStr);

上圖即可看到加簽、加密、解密的全部流程日志
補充一個簽名拼接的方法
public String genAppSign(Map<String, String> params) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
sb.append(entry.getKey());
sb.append('=');
sb.append(entry.getValue());
sb.append('&');
}
String test = sb.toString();
sb.append("sign=");
sb.append(Md5Utils.getSaltMD5(this, Md5Utils.encryptMd5(test)));
return sb.toString();
}
RSA加密遇到的問題
- 填充方式不同
/**
* Android 加密填充方式
*/
public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";
/**
* java 后端的加密算法RSA
*/
public static final String KEY_ALGORITHM = "RSA";
/**
* Android 用這個方法生成公私鑰對
*/
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
/**
*Android RSA加解密的時候用
*/
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
總結(jié)
本人也不是專業(yè)的加密算法工程師,只是一個Android小白,項目中需要加密,網(wǎng)上查找學(xué)習(xí)總結(jié)這篇文章,若有哪里寫的有問題,希望大家在評論區(qū)說出來,我們來共同討論下!?。?/p>