前言
首先看一條鏈接:
https://github.com/Tencent/WeDemo
騰訊給了一個wedemo,微信第三方登錄的例子。里面是php和ios,ios是object寫的,php還是原來的php。
因為公司需要做android app微信第三方登錄,所以我得寫個android例子。心里是什么想法呢?
不就是Oauth 2.0,作為一個.net 看php也不是啥難處,寫個app也沒啥,結果遇到很多坑,好吧,我承認我是一只菜雞。
下面是個人開發歷程,如有思維錯誤請指導。
正文
我首先看到的是這張圖:
上面這種圖的故事告訴我們在操作資源性api(包括登錄)之前呢,應該先建立安全通道。
流程是這樣子的:
1.有一個32位字節的秘鑰,(psk這是個通用名詞,表示加密的key),使用的是aes,32位,那么就是aes256了。
//生成key
public static byte[] getAES256Key () throws NoSuchAlgorithmException
{
KeyGenerator kg = KeyGenerator.getInstance("AES");
kg.init(256);
SecretKey sk = kg.generateKey();
//隨機生成32位加密key
return sk.getEncoded();
}
2.把這個生成32位字節的秘鑰去用公鑰加密,這個公鑰是寫死在app中的,然后傳給服務器。
private String encryptedRSA(byte[] content) throws NoSuchPaddingException, NoSuchAlgorithmException, BadPaddingException, IllegalBlockSizeException, InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, ShortBufferException {
//base64編碼的公鑰
RSAPublicKey pubKey=getPublicKey();
//RSA加密
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
Map<String, String> param = Maps.newHashMap();
param.put("psk", new String(content));
String outStr = Base64.encodeBase64String(cipher.doFinal(com.alibaba.fastjson.JSON.toJSONString(param).getBytes()));
return outStr;
}
這里有個需要注意的就是要使用RSA/ECB/OAEPWithSHA-1AndMGF1Padding,因為服務端使用的是:OPENSSL_PKCS1_OAEP_PADDING,這個加密用的少,OAEP這種模式還是第一次聽說,然后去查java的,原來是RSA/ECB/OAEPWithSHA-1AndMGF1Padding,
對我這種加解密不熟的人來說,算是一個小坑。
傳這個流即可:
base64(public_encrypted(32秘鑰))
這里有個非常值得注意的是,android app的base64和php的base64實現方式不一樣,當時我調了好久(1個小時),然后通過打斷點才知道base64實現不一樣。
后來我就用庫了,庫的名稱是:org.apache.commons.codec
這個庫會產生沖突,需要把源碼拿下來,然后改空間名,然后打包jar,最好還是網上找個吧,當時我是為了保險。
3.服務器去用私鑰解開,然后保存psk(aes的key)。
4.將psk作為秘鑰進行temp_uni加密傳給客戶端。
第四步,如果不看源碼估計會被坑。
php關鍵源碼:
public function AES_encode($data, $key)
{
$data = json_encode($data);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encode = $this->AES256_cbc_encrypt($data, $key, $iv);
// echo $encode;
$mac_server = hash_hmac('sha256', $encode, $key, true); // 計算mac_server
$encode = base64_encode($iv . $encode . $mac_server); // 加密后輸出的格式為IV+AES密文+SHA256對AES密文進行哈希后的值
return $encode;
}
里面作為幾件事:
1.生成一個16位的iv
2.用我們穿的key,和生成的iv,然后加密temp_in
3.對$encode和key進行hmac摘要。
4.iv和$encode還有hmac進行拼接,然后使用base64加密,發給客戶端。
那么客戶端需要做的就是下面幾件事。
1.用base64解密開。
2.去處$encode,進行同樣的hmac,得到的值和傳過來的hmac比較,查看是否被串改數據。
3.使用保存在客戶端的key和取下來的iv進行$encode解密,會得到一個json。
{'base_resp':{'errcode':$errcode,'errmsg':$errmsg},tmp_uin:'xxx'}
要取得就是tmp_uin。
好的,那么開始下一步。
取得了tmp_uin。那么用戶就可以進行微信登錄了。
用戶點擊后,會跳轉到微信授權取得code。
那么客戶端需要做的就是?因為這個圖實在不清晰,那么我們來看下服務端做了啥,然后反推客戶端應該干啥吧。
public function AES_decode($data, $key, $to_type = '')
{
$data = base64_decode($data);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = substr($data, 0, $iv_size);
$mac_client = substr($data, -32);
$encode = substr($data, $iv_size, -32);
$mac_server = hash_hmac('sha256', $encode, $key, true); // 計算mac_server
$decode = $this->AES256_cbc_decrypt($encode, $key, $iv);
// 檢測包的合法性
if ($mac_client == $mac_server){
$decode = $this->AES256_cbc_decrypt($encode, $key, $iv);
if (!$decode) {
return null;
}
if ($to_type == 'json') {
$decode = json_decode($decode, true);
}
return $decode;
} else {
return null;
}
}
服務端解密模式和加密模式相對應,客戶端應該做的是:
1.生成一個iv 16字節
2.使用原來的key,和生成的iv,對code進行加密,這里標注為encode。
3.生成一個hmac,數字摘要模式為sha256,也就是32字節的摘要。
4.拼接iv+encode+hmac進行base64位加密,然后發送為服務器端。
格式為:
{
"uin" : 3161321213,//上一步取得的temp_uni
"req_buffer" : "xxxx"//上文加密的數據
}
然后就會返回給我們正式通信后的內容,格式為:
Response: {
errcode : 0,
"resp_buffer" :"xxxx"//加密的數據
}
resp_buffer 里面包括了loginTicket和uni,作為以后和服務器的溝通憑據。
resp_buffer 進行解密的規則:和上文aes解密規則一致,這時候才真正的建立起正式的安全信道,
比如說獲取用戶信息:
按照上文的aes方法加密吧正式把uni和loignTicket 進行加密,就可以獲得數據,然后又是客戶端的解密獲取用戶信息,重復的就沒什么坑了。
結
以上是個人遇到的坑和思路,也許會給剛入坑的人一點小小的幫助,如果思路哪里不好,也望請指點。