Android數(shù)據(jù)加密之MD5加簽、RSA加密

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加密、加鹽.png

被破解的數(shù)據(jù).png

加鹽之后無法破解.png

上述三張圖片可以很好的說明,單純的MD5是無法保證數(shù)據(jù)的安全性,需要通過加鹽增加破解的難度。

RSA簡介

RSA是一種常用的非對稱加密算法,所謂非對稱加密是指使用一對密鑰(公鑰和私鑰)進行加密和解密,公鑰人人都可以獲得,用于加密數(shù)據(jù),私鑰保存在服務(wù)器中,用于解密數(shù)據(jù)。

加密解密流程

RSA加密解密過程.png

優(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);
加簽、加密、解密.png

上圖即可看到加簽、加密、解密的全部流程日志

補充一個簽名拼接的方法

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>

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Questions? 我通過app向服務(wù)器發(fā)送一條登錄的請求,那么這個請求里面肯定會包含用戶名和密碼等相關(guān)的信息,...
    yaoyao妖妖閱讀 2,293評論 0 8
  • 1.計算機出現(xiàn)以前的密碼 這篇文章旨在淺顯易懂的介紹標(biāo)題所述的各個算法概念與應(yīng)用,文中沒有數(shù)學(xué)公式。在主要概念出現(xiàn)...
    ZIJIAN94閱讀 2,156評論 0 2
  • 更新:MD5加密是單向的,只能加密不能解密(破解除外)。標(biāo)題可能會引起讀者誤解,已經(jīng)改正,感謝Li_Cheng同學(xué)...
    葛高召閱讀 2,343評論 0 4
  • 《叫我第一名》 這部電影是根據(jù)真人真事改編,主人公詹姆斯·沃克。第一遍看這個電影時候,電影中許多片段打動了我...
    平平Y(jié)閱讀 1,513評論 0 2
  • 本人火象星座,太陽星座落在白羊,上升星座停留在了獅子,故脾氣暴躁,雷厲風(fēng)行,是塊前鋒的好料!因此來自「火星」? 其...
    六個壹閱讀 136評論 0 1

友情鏈接更多精彩內(nèi)容