前言
首先看一條連結:
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 進行加密,就可以獲得資料,然後又是客戶端的解密獲取使用者資訊,重複的就沒什麼坑了。
結
以上是個人遇到的坑和思路,也許會給剛入坑的人一點小小的幫助,如果思路哪裡不好,也望請指點。