記寫 android 微信登陸的demo歷程

前言

首先看一條連接:php

https://github.com/Tencent/WeDemojava

騰訊給了一個wedemo,微信第三方登陸的例子。裏面是php和ios,ios是object寫的,php仍是原來的php。android

由於公司須要作android app微信第三方登陸,因此我得寫個android例子。內心是什麼想法呢?ios

不就是Oauth 2.0,做爲一個.net 看php也不是啥難處,寫個app也沒啥,結果遇到不少坑,好吧,我認可我是一隻菜雞。git

下面是我的開發歷程,若有思惟錯誤請指導。github

正文

我首先看到的是這張圖:apache

上面這種圖的故事告訴咱們在操做資源性api(包括登陸)以前呢,應該先創建安全通道。json

流程是這樣子的:
1.有一個32位字節的祕鑰,(psk這是個通用名詞,表示加密的key),使用的是aes,32位,那麼就是aes256了。api

//生成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 進行加密,就能夠得到數據,而後又是客戶端的解密獲取用戶信息,重複的就沒什麼坑了。

以上是我的遇到的坑和思路,也許會給剛入坑的人一點小小的幫助,若是思路哪裏很差,也望請指點。

相關文章
相關標籤/搜索