我們?cè)谧鯝PP開發(fā)的時(shí)候,APP的網(wǎng)絡(luò)安全是極其重要,我們有必要對(duì)請(qǐng)求的API進(jìn)行加密和防篡改。HTTPS是一個(gè)很好的傳輸加密的方式。如果APP的請(qǐng)求API地址和參數(shù)被泄露,我們還是可能會(huì)被惡意請(qǐng)求。
所以我們有必要實(shí)現(xiàn) URL簽名,對(duì)請(qǐng)求的參數(shù)進(jìn)行校驗(yàn),在客戶端生成URL簽名,在服務(wù)端對(duì)簽名進(jìn)行校驗(yàn),如果客戶端的URL簽名算法保密做得好,就可以避免非法請(qǐng)求,簽名算法的加密需要使用 C++編寫,這里就不介紹,只介紹URL簽名算法的實(shí)現(xiàn)。
原理
在 APP 端,對(duì)請(qǐng)求的參數(shù)+時(shí)間戳+密鑰,進(jìn)行MD5生成一個(gè)值,并和時(shí)間戳、參數(shù)一起傳到服務(wù)器,在服務(wù)器也進(jìn)行相同的方式生成一個(gè)MD5,對(duì)比兩個(gè)MD5是否一致,如果一致就說明這個(gè)請(qǐng)求是從APP發(fā)起的,否則就是非法請(qǐng)求。
算法
算法的實(shí)現(xiàn)有很多方式,這里介紹一個(gè)比較通用的算法。假設(shè)參與參數(shù)簽名計(jì)算的請(qǐng)求參數(shù)分別是“k1”、“k2”、“k3”,它們的值分別是“v1”、“v2”、“v3”,則參數(shù)簽名計(jì)算方法如下:
- 每一個(gè)請(qǐng)求都必須帶有timestamp參數(shù),長(zhǎng)度為10的時(shí)間戳;
- 將請(qǐng)求參數(shù)格式化為“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
- 將格式化好的參數(shù)鍵值對(duì)以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
- 在拼接好的字符串末尾追加上一個(gè)密鑰(需要注意保密,保存在服務(wù)端和客戶端);
- 對(duì)上述字符串進(jìn)行MD5,即為簽名的值,并在。
- 在 HTTP Header 增加字段 sign,傳遞上述的MD5值。
服務(wù)器驗(yàn)證
- 對(duì)所有接收到的參數(shù)也以上面的方式拼接,并加上密鑰生成MD5;
- 取出 Header中的 sign 字段進(jìn)行對(duì)比。如果一樣就說明沒有被篡改,如果不一樣就返回錯(cuò)誤;
- 對(duì)參數(shù)中的 timestamp 字段和服務(wù)器的時(shí)間進(jìn)行對(duì)比,如果誤差超過 5分鐘就說明請(qǐng)求已經(jīng)過期,就應(yīng)該返回錯(cuò)誤。
- 生成的簽名需要臨時(shí)保存校驗(yàn),不允許URL重復(fù)請(qǐng)求。
代碼
/**
* 簽名生成算法
* @param HashMap<String,String> params 請(qǐng)求參數(shù)集,所有參數(shù)必須已轉(zhuǎn)換為字符串類型
* @param String secret 簽名密鑰
* @return 簽名
* @throws IOException
*/
public static String getSignature(HashMap<String,String> params, String secret) throws IOException
{
// 先將參數(shù)以其參數(shù)名的字典序升序進(jìn)行排序
Map<String, String> sortedParams = new TreeMap<String, String>(params);
Set<Entry<String, String>> entrys = sortedParams.entrySet();
// 遍歷排序后的字典,將所有參數(shù)按"key=value"格式拼接在一起
StringBuilder basestring = new StringBuilder();
for (Entry<String, String> param : entrys) {
basestring.append(param.getKey()).append("=").append(param.getValue());
}
basestring.append(secret);
// 使用MD5對(duì)待簽名串求簽
byte[] bytes = null;
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
bytes = md5.digest(basestring.toString().getBytes("UTF-8"));
} catch (GeneralSecurityException ex) {
throw new IOException(ex);
}
// 將MD5輸出的二進(jìn)制結(jié)果轉(zhuǎn)換為小寫的十六進(jìn)制
StringBuilder sign = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() == 1) {
sign.append("0");
}
sign.append(hex);
}
return sign.toString();
}