記寫 android 微信登入的demo歷程

人形巨獸發表於2020-08-20

前言

首先看一條連結:

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 進行加密,就可以獲得資料,然後又是客戶端的解密獲取使用者資訊,重複的就沒什麼坑了。

以上是個人遇到的坑和思路,也許會給剛入坑的人一點小小的幫助,如果思路哪裡不好,也望請指點。

相關文章