首頁>科技>

引言

在日常開發中我們對HTTP資料傳輸並不陌生,前端需要與後端進行資料互動往往需要呼叫後端某個API,然而在前端與後端的請求過程中資料真的安全嗎?下面了解一件關於0.01購買phone事件;

事件

在之前報導一個新聞,某個電商APP應用因為程式Bug,被灰色產業0.01元擼走千臺iPhone手機,損失近千萬,後來了解到也並非高手所為,其實就是該APP在購買介面中對資料傳輸缺少安全防範,通過HTTP抓包工具對傳輸中的資料進行修改,將原本幾千元的iPhone手機價格修改為0.01進行購買;

案例

看完以上事件部分人可能還是不太抓包的概念,所謂抓包就是依賴某個工具,對客戶端與服務端進行請求、或者對服務端對客戶端響應時過程中進行攔截,可以將其請求資料或響應資料進行篡改;

為了能更好了解,這裡我準備了一個模擬購買介面:

這裡模擬前端正常呼叫購買介面;

http://localhost:8080/test?money=6000

按理後端應該會輸出6000,這裡我利用fiddle抓包工具對該請求進行攔截,將money引數改為1;

成功將合法價格改為非法價格,如果後端介面驗證不注意即會按照該金額生成訂單,則支付1元即可買到高額商品;

原由

導致以上事件發生無非就兩種情況:

1.對敏感資料使用明文傳輸;

2.在前端進行校驗後,後端介面並未對資料第二次校驗;

如何解決?

在資料傳輸過程中對敏感資料進行加密,即使請求被截獲,也只能獲取到密文資訊,無法篡改具體需要修改的資料;

傳輸過程中使用的加密方式通常分為兩類:對稱加密/非對稱加密;

對稱加密:市場上用的比較多的對稱加密有DES、AES、3DES等,對稱加密的流程是通過生成一把祕鑰,分別儲存客戶端以及服務端各自一份,客戶端在請求服務端前先將傳輸的資料通過該祕鑰進行加密,服務端接收到請求之後,先將資料用該祕鑰進行解密再進行處理,從而避免資料在傳輸中被篡改;

缺點:該祕鑰在客戶端加密與服務端解密使用的是同一把,而儲存在客戶端的祕鑰並無法避免洩漏的風險,通過反編譯等手段獲取到祕鑰,那麼傳輸過程中攔截請求則照樣可以解密密文資料進行篡改;

Des加密案例:

public class DesDecrypt {    //加密欄位     private static String src = "Java資料社群"; \tpublic static void main(String[] args) {     jdkDES();     bcDES();      }   /*JDK實現*/   public static void jdkDES() {   try {      //生成KEY               KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");      keyGenerator.init(56);     SecretKey secretKey = keyGenerator.generateKey();      byte[] bytesKey = secretKey.getEncoded();      //KEY轉換           DESKeySpec desKeySpec = new DESKeySpec(bytesKey);        SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");               Key convertSecretKey = factory.generateSecret(desKeySpec);              //加密            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");         cipher.init(Cipher.ENCRYPT_MODE, convertSecretKey);        byte[] result = cipher.doFinal(src.getBytes());         System.out.println("jdk des encrypt : " + new String(Hex.encode(result)));         //解密          cipher.init(Cipher.DECRYPT_MODE, convertSecretKey);          result = cipher.doFinal(result);      System.out.println("jdk des decrypt : " + new String(result).toString());   } catch (Exception e) {            e.printStackTrace();     }   }   /*BC實現*/  public static void bcDES() {    try {      Security.addProvider(new BouncyCastleProvider());       //生成KEY      KeyGenerator keyGenerator = KeyGenerator.getInstance("DES", "BC");          keyGenerator.getProvider();             keyGenerator.init(56);        SecretKey secretKey = keyGenerator.generateKey();        byte[] bytesKey = secretKey.getEncoded();         //KEY轉換            DESKeySpec desKeySpec = new DESKeySpec(bytesKey);             SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");           Key convertSecretKey = factory.generateSecret(desKeySpec);         //加密         Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");      cipher.init(Cipher.ENCRYPT_MODE, convertSecretKey);       byte[] result = cipher.doFinal(src.getBytes());           System.out.println("bc des encrypt : " + new String(Hex.encode(result)));         //解密            cipher.init(Cipher.DECRYPT_MODE, convertSecretKey);           result = cipher.doFinal(result);      System.out.println("bc des decrypt : " + new String(result).toString());   } catch (Exception e) {       e.printStackTrace();        }    }}//執行結果//jdk des encrypt : ab6afc9a81584573cf2f50ceccd28628bd10885ebd84f37c//jdk des decrypt : Java資料社群//bc des encrypt : 34fd3ed3d0b6d5ab7baf6db300420f3c4094a2061bb2dd34//bc des decrypt : Java資料社群

非對稱加密:市場上用的比較多的通常為RSA加密,RSA加密的流程與對稱加密不同,相對於更加安全,通過生成兩把祕鑰(私鑰/公鑰),公鑰通常用來對傳輸過程中的資料進行加密,私鑰則用來對加密資料進行解密,公鑰加密的內容只能對應的私鑰才能解密,私鑰加密的內容只能由對應的公鑰解密,通常客戶端儲存公鑰、服務端則儲存私鑰,這樣,即使公鑰被洩漏也沒法破解密文資訊;

缺點:相對對稱加密效能方面要慢很多,非敏感資訊不建議使用;

RSA案例:

public class RSAUtils {    protected static final Log log = LogFactory.getLog(RSAUtils.class);    private static String KEY_RSA_TYPE = "RSA";    private static int KEY_SIZE = 1024;//JDK方式RSA加密最大隻有1024位    private static int ENCODE_PART_SIZE = KEY_SIZE/8;    public static final String PUBLIC_KEY_NAME = "public";    public static final String PRIVATE_KEY_NAME = "private";    public static void main(String[] args) {        Map<String,String> map=RSAUtils.createRSAKeys(); //建立公私鑰        String str="Java資料社群";        String enStr=encode(str,map.get("public"));//公鑰加密        System.out.println("加密後:"+enStr);        String deStr=decode(enStr,map.get("private"));        System.out.println("解密後:"+deStr);    }    /**     * 建立公鑰祕鑰     * @return     */    public static Map<String,String> createRSAKeys(){        Map<String,String> keyPairMap = new HashMap<>();//裡面存放公私祕鑰的Base64位加密        try {            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE);            keyPairGenerator.initialize(KEY_SIZE,new SecureRandom());            KeyPair keyPair = keyPairGenerator.generateKeyPair();             //獲取公鑰祕鑰            String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded());            String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());             //存入公鑰祕鑰,以便以後獲取            keyPairMap.put(PUBLIC_KEY_NAME,publicKeyValue);            keyPairMap.put(PRIVATE_KEY_NAME,privateKeyValue);        } catch (NoSuchAlgorithmException e) {            log.error("當前JDK版本沒找到RSA加密演算法!");            e.printStackTrace();        }        return keyPairMap;    }     /**     * 公鑰加密     * 描述:     *     1位元組 = 8位;     *     最大加密長度如 1024位私鑰時,最大加密長度為 128-11 = 117位元組,不管多長資料,加密出來都是 128 位元組長度。     * @param sourceStr     * @param publicKeyBase64Str     * @return     */    public static String encode(String sourceStr,String publicKeyBase64Str){        byte [] publicBytes = Base64.decodeBase64(publicKeyBase64Str);        //公鑰加密        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);        List<byte[]> alreadyEncodeListData = new LinkedList<>();         int maxEncodeSize = ENCODE_PART_SIZE - 11;        String encodeBase64Result = null;        try {            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE);            cipher.init(Cipher.ENCRYPT_MODE,publicKey);            byte[] sourceBytes = sourceStr.getBytes("utf-8");            int sourceLen = sourceBytes.length;            for(int i=0;i<sourceLen;i+=maxEncodeSize){                int curPosition = sourceLen - i;                int tempLen = curPosition;                if(curPosition > maxEncodeSize){                    tempLen = maxEncodeSize;                }                byte[] tempBytes = new byte[tempLen];//待加密分段資料                System.arraycopy(sourceBytes,i,tempBytes,0,tempLen);                byte[] tempAlreadyEncodeData = cipher.doFinal(tempBytes);                alreadyEncodeListData.add(tempAlreadyEncodeData);            }            int partLen = alreadyEncodeListData.size();//加密次數             int allEncodeLen = partLen * ENCODE_PART_SIZE;            byte[] encodeData = new byte[allEncodeLen];//存放所有RSA分段加密資料            for (int i = 0; i < partLen; i++) {                byte[] tempByteList = alreadyEncodeListData.get(i);                System.arraycopy(tempByteList,0,encodeData,i*ENCODE_PART_SIZE,ENCODE_PART_SIZE);            }            encodeBase64Result = Base64.encodeBase64String(encodeData);        } catch (Exception e) {            e.printStackTrace();        }        return encodeBase64Result;    }     /**     * 私鑰解密     * @param sourceBase64RSA     * @param privateKeyBase64Str     */    public static String decode(String sourceBase64RSA,String privateKeyBase64Str){        byte[] privateBytes = Base64.decodeBase64(privateKeyBase64Str);        byte[] encodeSource = Base64.decodeBase64(sourceBase64RSA);        int encodePartLen = encodeSource.length/ENCODE_PART_SIZE;        List<byte[]> decodeListData = new LinkedList<>();//所有解密資料        String decodeStrResult = null;        //私鑰解密        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);        try {            KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);            Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE);            cipher.init(Cipher.DECRYPT_MODE,privateKey);            int allDecodeByteLen = 0;//初始化所有被解密資料長度            for (int i = 0; i < encodePartLen; i++) {                byte[] tempEncodedData = new byte[ENCODE_PART_SIZE];                System.arraycopy(encodeSource,i*ENCODE_PART_SIZE,tempEncodedData,0,ENCODE_PART_SIZE);                byte[] decodePartData = cipher.doFinal(tempEncodedData);                decodeListData.add(decodePartData);                allDecodeByteLen += decodePartData.length;            }            byte [] decodeResultBytes = new byte[allDecodeByteLen];            for (int i = 0,curPosition = 0; i < encodePartLen; i++) {                byte[] tempSorceBytes = decodeListData.get(i);                int tempSourceBytesLen = tempSorceBytes.length;                System.arraycopy(tempSorceBytes,0,decodeResultBytes,curPosition,tempSourceBytesLen);                curPosition += tempSourceBytesLen;            }            decodeStrResult = new String(decodeResultBytes,"UTF-8");        }catch (Exception e){            e.printStackTrace();        }        return decodeStrResult;    }}//執行結果//加密後:bapvkf7RsocUqpr7JJ5yMDQSrMAKnWVuHG2buJDhV0DGMTlAKezcy7Qyc8f5DVVHralZ5I0nSZdQOVaIG7ndP/bvtNcQVJaaigRSaQTfmf7xiNNuWQf71nQJmiUlHdzijUWB0HbilWBeZ71FZT9djoV1X6EZDB3oH1DQwwOmvow=//解密後:Java資料社群

經過以上加密之後,基本上能很大程度解決資料在傳輸當中資料被洩漏的機率,但是並沒法保證絕對的安全,RSA加密中如果公鑰被洩漏,攻擊者照樣有辦法能夠根據洩漏的公鑰,重新模擬一份加密資料傳輸到服務端,而服務端並沒法辨別中途是否被篡改過,那麼簽名技術就是用於驗證、防止中途是否被篡改;

簽名:通過使用兩對RSA祕鑰 (A[公鑰、私鑰],B[公鑰、私鑰]) ,通常客戶端儲存A公鑰、B私鑰,服務端則儲存A私鑰、B公鑰,A用於加解密,B用於簽名以及驗籤;

流程:客戶端用A公鑰加密過後,再通過B私鑰將加密過後的資料進行簽名,最後將加密資料與簽名一起傳送到服務端,服務端接收到資料之後,首先通過B公鑰對該加密資料以及簽名進行對比驗籤,如果驗證不一致則代表中途被篡改過,否則正常;

RSA簽名案例:

  public class RSASignature {    public static void main(String[] args) {        /*建立兩份公私鑰,map1用於加解密、map2用於簽名*/        Map<String,String> map1=RSAUtils.createRSAKeys();        Map<String,String> map2=RSAUtils.createRSAKeys();        String str="Java資料社群";        String enStr=RSAUtils.encode(str,map1.get("public"));//map1公鑰加密        System.out.println("加密後:"+enStr);        String sign=sign(enStr,map2.get("private"));//map2私鑰簽名        System.out.println("簽名:"+sign);        //enStr+="1"; //模擬中途被篡改,則驗籤false        boolean flag=doCheck(enStr,sign,map2.get("public"));//map2公鑰驗籤        System.out.println("驗簽結果:"+flag);    }    /**     * 簽名演算法     */    public static final String SIGN_ALGORITHMS = "SHA1WithRSA";    /**     * RSA簽名     */    public static String sign(String content, String privateKey) {        try {            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));            KeyFactory keyf = KeyFactory.getInstance("RSA");            PrivateKey priKey = keyf.generatePrivate(priPKCS8);            java.security.Signature signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);            signature.initSign(priKey);            signature.update(content.getBytes());            byte[] signed = signature.sign();            return Base64.encode(signed);        } catch (Exception e) {            e.printStackTrace();        }        return null;    }    /**     * RSA驗籤     */    public static boolean doCheck(String content, String sign, String publicKey) {        try {            KeyFactory keyFactory = KeyFactory.getInstance("RSA");            byte[] encodedKey = Base64.decode(publicKey);            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));            java.security.Signature signature = java.security.Signature                    .getInstance(SIGN_ALGORITHMS);            signature.initVerify(pubKey);            signature.update(content.getBytes());            boolean bverify = signature.verify(Base64.decode(sign));            return bverify;        } catch (Exception e) {            e.printStackTrace();        }        return false;    }}//執行結果://加密後:Nv1kTCukFAziwsCYPZQDQ8WqII1v5DKlTaFlcgkXQACuO01rFuvPAu/PXlmVuHN0i2Xx5B4ZjsKIs2YJHUzIunuqkPchKZM29b52jv7TLPaeZUeQFmC5gKEpEHSTWfUK9T7YF20+kK6Ey0rWRgOBd3ZoVPjvCoNXAlhyEYkBx7Q=//簽名:fAkREdyQsioXeDi/CLaBQDP+V0K6W8DGXTBYZsH5GS8O30+ZKCfyTwvGNIZ++XIekt0P6xo1T6L7BRdT/it5qNwcqudxoolgf8KkhkSRFCI6LjJ6TYxFfCnvtMv7dxXDkR30E0AR9jyqNCUVE6ljUDsSL7PvUFpqOBDTcd+l2uA=//驗簽結果:true

總結

1.對稱加密效率效能高,由於祕鑰存在洩漏的可能,並不能保證加密資料不被破解,建議對普通資料進行使用,對於敏感資料(金額、身份證等等)避免使用;

2.非對稱加密效率效能低,分為公私鑰兩把,即使公鑰洩漏,也能保證資料不被破解,建議對敏感資料進行使用;

3.通過非對稱加密+簽名能夠很大程度防止傳輸過程中資訊被篡改,但是並不是絕對的安全;

如果大家覺得有用 請分享出去 讓更多人看到!予人玫瑰手有餘香

最新評論
  • 整治雙十一購物亂象,國家再次出手!該跟這些套路說再見了
  • 馬化騰“無奈”你們為什麼都放棄了QQ等級?