记写 android 微信登录的demo历程

前言

首先看一条链接:

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 进行加密,就可以获得数据,然后又是客户端的解密获取用户信息,重复的就没什么坑了。

以上是个人遇到的坑和思路,也许会给刚入坑的人一点小小的帮助,如果思路哪里不好,也望请指点。

posted @ 2020-08-20 17:26  敖毛毛  阅读(652)  评论(0编辑  收藏  举报