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

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

  • 相关阅读:
    Security基础(二):SELinux安全防护、加密与解密应用、扫描与抓包分析
    Security基础(一):Linux基本防护措施、使用sudo分配管理权限、提高SSH服务安全
    勤奋之致,功成之始
    Database基础(七):部署集群基础环境、MySQL-MMM架构部署、MySQL-MMM架构使用
    Database基础(六):实现MySQL读写分离、MySQL性能调优
    Database基础(五):使用binlog日志、XtraBackup备份工具、MySQL AB复制
    Database基础(四):密码恢复及设置、 用户授权及撤销、数据备份与恢复、MySQL管理工具
    Database基础(三):SQL数据导入/导出、 操作表记录、查询及匹配条件
    vue-打包上线
    vue报错-Object(...) is not a function
  • 原文地址:https://www.cnblogs.com/aoximin/p/13536317.html
Copyright © 2011-2022 走看看