天天看點

android javamail用戶端擷取慢_遊戲社群App (三):用戶端與服務端的加密處理 和 登入...

http請求資料無論是GET或者POST都可能會被抓包擷取到資料。為了避免使用者的敏感資料被竊取(比如密碼),需要對資料進行加密處理。

一、相關名詞解析

RSA:非對稱加密。

會産生公鑰和私鑰,公鑰在用戶端,私鑰在服務端。公鑰用于加密,私鑰用于解密。

優勢在于 不需要共享私鑰,避免了私鑰洩露的風險。

劣勢在于 加密效率低,資料量大時耗時也大。

AES:對稱加密。

客服端和伺服器端都使用同一個秘鑰來進行加密和解密。

優勢在于 加密效率高

缺點在于 秘鑰需要共享給用戶端,具有洩露的風險

MD5:MD5資訊摘要算法

隻能加密,不能解密。

Base64編碼:對位元組數組轉換成字元串的一種編碼方式。

二、基本邏輯

整體流程:用戶端獲得資料–>對資料進行加密–>上傳加密資料–>伺服器獲得資料–>對資料進行解密

需要考慮:

1、密碼不能明文傳輸,即需要對明文進行加密。

2、參數不能被中途截取修改,即需要對參數進行驗證。

三、對明文進行加密—Cipher

javax.crypto.Cipher 類中提供了對明文的加密和解密功能。

Cipher可以使用RSA加密方式,即非對稱加密,是以需要生成公鑰與私鑰。生成的公鑰放到用戶端中用于加密,私鑰放在伺服器上用于解密。

生成公鑰和私鑰 示例代碼:

提示:如果出現找不到Base64.encode方法時,你可能需要引入com.sun.jersey.jersey-core.jar包(提取碼:4lvj )

/**
           

用戶端明文加密 示例代碼:

private static final String ALGORITHM = "RSA"; //所使用算法
private static final String PublicKey = Info.loginPublicKey; //公鑰

public static String encrypt(String content) {
    try {
        PublicKey pubkey = getPublicKeyFromX509(ALGORITHM, PublicKey);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");//生成Cipher對象
        cipher.init(Cipher.ENCRYPT_MODE, pubkey);//操作模式為加密(Cipher.ENCRYPT_MODE),pubkey為公鑰
        byte plaintext[] = content.getBytes("UTF-8");
        byte[] output = cipher.doFinal(plaintext);//得到加密後的位元組數組
        String s = new String(android.util.Base64.encode(output, android.util.Base64.DEFAULT));
        return s;
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

private static PublicKey getPublicKeyFromX509(String algorithm, String bysKey)
        throws NoSuchAlgorithmException, Exception {
    byte[] decodedKey = android.util.Base64.decode(bysKey, android.util.Base64.DEFAULT);
    X509EncodedKeySpec x509 = new X509EncodedKeySpec(decodedKey);
    KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
    return keyFactory.generatePublic(x509);
}
           

服務端解密 示例代碼:

private static final String PrivateKey = Info.loginPrivateKey; //私鑰
public static String reEncrypt(String encryptStr) {
    try {
        PrivateKey privateKey = getPrivateKey(PrivateKey);
        Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] output =  cipher.doFinal(android.util.Base64.decode(encryptStr, android.util.Base64.DEFAULT));
        return new String(output);
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}

private static PrivateKey getPrivateKey(String priKey) throws NoSuchAlgorithmException,
        InvalidKeySpecException, NoSuchProviderException {
    byte[] keyBytes = android.util.Base64.decode(priKey, android.util.Base64.DEFAULT);
    PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
    PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
    return privateKey;
}
           

四、對參數進行驗證–請求參數的簽名sign

避免請求參數被修改,保證了請求資料的一緻性。需要用戶端和服務端約定一個簽名生成算法。

用戶端在請求接口之前調用簽名算法,根據參數生成sign值。然後把sign和請求參數一并傳給伺服器。

伺服器收到到參數和簽名之後,使用同樣的簽名算法和參數 生成sign,并比對兩個sign是否一緻。如果一緻就說明參數未被修改。

簽名算法示例代碼:

/**
* 使用MD5加密
* 對鍵進行升值排序
* 把生成的md5統一轉成大寫
*/
public static String md5Decode32(Map<String, String> data, String time) {
    String key=Info.md5secretkey; // key,和背景保持一緻
    String content = "";
    for (String keyname:data.keySet()) {
        content=content+keyname+ data.get(keyname);
    }
    content=content+time+ key; //注意:time一定要傳進來,不可以在方法中直接生成。不然時間是對不上的


    byte[] hash;
    try {
        hash = MessageDigest.getInstance("MD5").digest(content.getBytes("UTF-8"));
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("NoSuchAlgorithmException",e);
    } catch (UnsupportedEncodingException e) {
        throw new RuntimeException("UnsupportedEncodingException", e);
    }
    //對生成的16位元組數組進行補零操作
    StringBuilder hex = new StringBuilder(hash.length * 2);
    for (byte b : hash) {
        if ((b & 0xFF) < 0x10){
            hex.append("0");
        }
        hex.append(Integer.toHexString(b & 0xFF));
    }
    return hex.toString().toUpperCase();//傳回純大寫的MD5
}
           

五、登入

登入可以分為賬密登入和自動登入。

第一次賬密登入成功之後,背景可以生成并傳回一個token。後續的一段時間内,可以直接通過token自動登入。免去輸入賬号密碼的麻煩。

賬号登入示例代碼(用戶端):

@OnClick(R2.id.btn_sign_in)
void onClickSignIn(){
    if(checkForm()){ //檢查賬密格式是否正确
        //使用Cipher對密碼進行加密
        String encryptPassword;
        encryptPassword = CipherUtil.encrypt(password);

        //生成MD5 sign,避免請求參數被惡意修改。保證了請求資料的一緻性
        Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>(); //TreeMap為有序集合,預設是升序
        MD5_Parameter_Map.put("userName", name);
        MD5_Parameter_Map.put("userPassword", encryptPassword);
        String time = System.currentTimeMillis()+"";
        String sign = MD5Sign.md5Decode32(MD5_Parameter_Map,time);

        //上傳資料
        RestClient.builder()
                .url(AppInfo.API_LOGIN)
                .params("userName",name)
                .params("userPassword",encryptPassword)
                .params("signTime",time) //要把時間也傳上背景,不能直接在背景生成時間,不然生成的sign不一緻
                .params("sign",sign)
                .success(new ISuccess() {
                    @Override
                    public void onSuccess(String response) {
                    }
                })
                .failure(new IFailure() {
                    @Override
                    public void onFailure(String msg) {
                    }
                })
                .error(new IError() {
                    @Override
                    public void onError(int code, String msg) {
                    }
                })
                .build()
                .post();
    }
}
           

賬号登入示例代碼(服務端):

@RequestMapping("/login")
public ApiResult login(String userName,String userPassword,String signTime,String sign) {
    int code = 0;
    String status = "unknown error";
    UserDao data = new UserDao();
    try {
        //生成sign對請求資料的一緻性進行校驗
        Map<String, String> MD5_Parameter_Map = new TreeMap<String, String>();
        MD5_Parameter_Map.put("userName", userName);
        MD5_Parameter_Map.put("userPassword", userPassword);
        String checkSign = MD5SignUtil.md5Decode32(MD5_Parameter_Map,signTime);
        //校驗不通過直接傳回錯誤
        if(!checkSign.equals(sign)){
            ApiResult apiResult = new ApiResult();
            apiResult.setCode(ApiCallback.SignIsError);
            apiResult.setStatus(status);
            return apiResult;
        }

        //判斷sign是否超過有效時間
        long time= System.currentTimeMillis();
        long signTimeToLong = Long.parseLong(signTime);
        long interval = Math.abs(time-signTimeToLong);
        if(interval > validityTime){
            ApiResult apiResult = new ApiResult();
            apiResult.setCode(ApiCallback.SignIsTimeout);
            apiResult.setStatus("Sign is timeout");
            return apiResult;
        }

        //調用Cipher對密碼進行解密
        userPassword = CipherUtil.reEncrypt(userPassword);

        UserController.getInstance().init(userRepository);

        if(userName.isEmpty()){ //userName不能為空
            code = ApiCallback.InputUserIsNull;
            status = "User name can not be null";
        }else if(userPassword.isEmpty()){ //userPassword也不能為空
            code = ApiCallback.InputPasswordIsNull;
            status = "Password can not be null";
        } else if(!UserController.getInstance().isUserNameExist(userName)){//判斷名字是否存在
            code = ApiCallback.UserIsNotExist;
            status = "User name is not exist.";
        }else if(!UserController.getInstance().getPassword(userName).equals(userPassword)){//判斷密碼是否正确
            code = ApiCallback.EntryPasswordError;
            status = "Input password is error.";
        }else {//一切正常,更新資料庫登入資訊,并傳回token等資訊
            data = UserController.getInstance().doLogin(userName);
            code = ApiCallback.Success;
            status = "Register success";
        }
    }catch (Exception e){
        //報錯了,傳回錯誤資訊
        status=String.valueOf(e);
    }

    ApiResult apiResult = new ApiResult();
    apiResult.setCode(code);
    apiResult.setStatus(status);
    apiResult.setData(data);//傳回資料
    return apiResult;
}
           

CSDN同名部落格位址