baidu云存储java sdk源码阅读之安全

         本身想写一个例子程序的,发现官方的文档比较简单,所以想写一系列的java sdk阅读文章加强对bcs api和参数的认识,然后再写一个例子出来,今天我们先看一下操作的安全部分的代码实现,它位于:com.baidu.inf.iis.bcs.auth包下面,类也很简单,但是之前对java安全和加解密不熟悉,所以还花了点时间,今天将这个阅读过程记录下来。

       首先我来看最简单的三个类,即就是BCSCredentials.java、BCSSignCondition.java、SigningAlgorithm.java,这个就不详细讲解了,BCSCredentials里面包含AK和SK是对这连个属性的封装,BCSSignCondition从命名来看,就是Sign生成的条件,里面包含时间、ip、和size,至于这个size,到目前不懂是什么玩意,是什么的大小,从官方给的sdk来看实际上是condition对象整个没使用,最后一个就是加密算法的一个枚举类,里面有两种最常用的消息摘要算法HmacSHA1和HmacSHA256。关于这两种算法我在这里也简单的介绍一下,也是今天刚了解,算是现学现卖。

       无论是HmacSHA1还是HmacSHA256均是HMAC算法家族的成员之一,HMAC(Hash Message Authentication Code),散列消息鉴别码,基于密钥的Hash算法的认证协议,它的原理是用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等。认证过程如下所示:


baidu云存储java sdk源码阅读之安全
 

            如果大家待会还有疑问的,看完代码之后就能彻底的明白,谁是密钥,谁是公共函数,谁是数据等等。

       直接上代码吧,这个在网上没找到代码,只能反编译后看,虽然累点,但是还能看出来点东西。

      

package com.baidu.inf.iis.bcs.auth;

import com.baidu.inf.iis.bcs.http.BCSHttpRequest;
import com.baidu.inf.iis.bcs.http.DefaultBCSHttpRequest;
import com.baidu.inf.iis.bcs.http.HttpMethodName;
import com.baidu.inf.iis.bcs.model.BCSClientException;
import com.baidu.inf.iis.bcs.request.BaiduBCSRequest;
import com.baidu.inf.iis.bcs.utils.ServiceUtils;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class BCSSigner
{
  public static void main(String[] paramArrayOfString)
    throws URISyntaxException
  {
    1 local1 = new BaiduBCSRequest("bucket", "object", HttpMethodName.GET)
    {
    };
    BCSSignCondition localBCSSignCondition = new BCSSignCondition();
    localBCSSignCondition.setIp("192.168.1.1");
    localBCSSignCondition.setSize(Long.valueOf(1234L));
    localBCSSignCondition.setTime(Long.valueOf(4321L));

    BCSCredentials localBCSCredentials = new BCSCredentials("akakak", "sksksk");

    DefaultBCSHttpRequest localDefaultBCSHttpRequest = new DefaultBCSHttpRequest();
    localDefaultBCSHttpRequest.setHttpMethod(local1.getHttpMethod());
    localDefaultBCSHttpRequest.setEndpoint("10.81.2.114:8685");

    sign(local1, localDefaultBCSHttpRequest, localBCSCredentials, localBCSSignCondition);
    System.out.println(localDefaultBCSHttpRequest.toString());
  }

  public static void sign(BaiduBCSRequest paramBaiduBCSRequest, BCSHttpRequest paramBCSHttpRequest, BCSCredentials paramBCSCredentials) {
    sign(paramBaiduBCSRequest, paramBCSHttpRequest, paramBCSCredentials, null);
  }

  public static void sign(BaiduBCSRequest paramBaiduBCSRequest, BCSHttpRequest paramBCSHttpRequest, BCSCredentials paramBCSCredentials, BCSSignCondition paramBCSSignCondition) {
    StringBuilder localStringBuilder1 = new StringBuilder();
    StringBuilder localStringBuilder2 = new StringBuilder();

    if (null == paramBaiduBCSRequest.getHttpMethod()) {
      throw new BCSClientException("Sign failed! Param: httpMethod, bucket, object can not be empty!");
    }
    if (null == paramBaiduBCSRequest.getBucket()) {
      throw new BCSClientException("Sign failed! Param: httpMethod, bucket, object can not be empty!");
    }
    if ((null == paramBaiduBCSRequest.getObject()) || (0 == paramBaiduBCSRequest.getObject().length())) {
      throw new BCSClientException("Sign failed! Param: httpMethod, bucket, object can not be empty!");
    }
    localStringBuilder1.append("MBO");
    localStringBuilder2.append("Method=").append(paramBaiduBCSRequest.getHttpMethod().toString()).append("\n");
    localStringBuilder2.append("Bucket=").append(paramBaiduBCSRequest.getBucket()).append("\n");
    localStringBuilder2.append("Object=").append(paramBaiduBCSRequest.getObject()).append("\n");

    if (paramBCSSignCondition != null) {
      if (0 != paramBCSSignCondition.getIp().length()) {
        localStringBuilder1.append("I");
        localStringBuilder2.append("Ip=").append(paramBCSSignCondition.getIp()).append("\n");
      }
      if (paramBCSSignCondition.getTime().longValue() > 0L) {
        localStringBuilder1.append("T");
        localStringBuilder2.append("Time=").append(paramBCSSignCondition.getTime()).append("\n");
        paramBCSHttpRequest.addParameter("time", String.valueOf(paramBCSSignCondition.getTime()));
      }
      if (paramBCSSignCondition.getSize().longValue() > 0L) {
        localStringBuilder1.append("S");
        localStringBuilder2.append("Size=").append(paramBCSSignCondition.getSize()).append("\n");
        paramBCSHttpRequest.addParameter("size", String.valueOf(paramBCSSignCondition.getSize()));
      }
    }
    localStringBuilder2.insert(0, "\n");
    localStringBuilder2.insert(0, localStringBuilder1.toString());

    byte[] arrayOfByte = new byte[0];
    try {
      SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramBCSCredentials.getSecretKey().getBytes(), SigningAlgorithm.HmacSHA1.toString());

      Mac localMac = Mac.getInstance(localSecretKeySpec.getAlgorithm());
      localMac.init(localSecretKeySpec);
      arrayOfByte = localMac.doFinal(ServiceUtils.toByteArray(localStringBuilder2.toString()));
      paramBCSHttpRequest.addParameter("sign", ":" + paramBCSCredentials.getAccessKey() + ":" + ServiceUtils.toBase64(arrayOfByte));
    }
    catch (NoSuchAlgorithmException localNoSuchAlgorithmException) {
      throw new BCSClientException("NoSuchAlgorithmException. Sign bcs failed!", localNoSuchAlgorithmException);
    } catch (InvalidKeyException localInvalidKeyException) {
      throw new BCSClientException("InvalidKeyException. Sign bcs failed!", localInvalidKeyException);
    } catch (RuntimeException localRuntimeException) {
      throw new BCSClientException("Sign bcs failed!", localRuntimeException);
    }
  }
}

           从代码结构来看就sign函数在做事,我们来看,首先构造了两个StringBuffer用于盛放后来生成的东西,从意图上来看,第一个buffer放的是一些标志位,第一个buffer放的是签名的内容。 从代码来看每次操作的三个参数是必须的,bucket、object、httpMethod,从代码来看httpMethod就是标准的HTTP方法。

 

package com.baidu.inf.iis.bcs.http;

public enum HttpMethodName
{
  GET, POST, PUT, DELETE, HEAD;
}
          我们继续来看,这个是比较简单的,首先会放一个标志MBO,我们终于知道MBO是什么东西,Method、Object、Bucket。即就是
buffer1里面得数据是:MBO
buffe2里面得数据是:
      Method=GET
      Bucket=bucket
      Object=object
      我们注意到作者在这里面特意用了x平台的换行符,而不是system.newline,估计也是为了和服务器端实现的简单性。
        后面紧接着有个判断,paramBCSSignCondition如果不为null的时候进下面的一个代码块,当遇到ip数据的时候,给buffer1添加一个标志:I,buffer2里面放数据。举个例子,condition的数据如果是下面的数据:
       
BCSSignCondition condition = new BCSSignCondition();
condition.setIp("192.168.1.1");
condition.setSize(1234L);
condition.setTime(4321L);
         那么现在buffer1和buffer2里面的数据将如下所示:
        buffer1: MBOIST
buffe2里面得数据是:
       Method=GET
       Bucket=bucket
       Object=object
        Ip=192.168.1.1
       Time=4321
       Size=1234
        我们接着来看,实际上是对buffer2的数据做了变换,在buffer2的开头插入了buffer1的数据: 那么现在buffer1的数据是不动的,buffer2里面得数据将会变成下面的内容:
       MBOITS
       Method=GET
       Bucket=bucket
       Object=object
        Ip=192.168.1.1
      Time=4321
       Size=1234
        我们接着来看,最后实现了加密:
  SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramBCSCredentials.getSecretKey().getBytes(), SigningAlgorithm.HmacSHA1.toString());
           这个实际上是根据AK和HmacSHA1算法生成密钥。
        
 Mac localMac = Mac.getInstance(localSecretKeySpec.getAlgorithm());
 localMac.init(localSecretKeySpec);
              这个是根据指定的算法生成mac对象,当然了,这块代码作者写的并不是很好,我在看的时候就有点晕,为什么要根据生成密钥的算法生成mac对象呢,如果这个地方写成 localMac.init(SigningAlgorithm.HmacSHA1.toString());就比较好看了,还有发现作者对枚举都没有实现toString而是在外面调用toString,这个习惯也不大好。 下面的init方法实际上是根据密钥初始化mac。
        
arrayOfByte = localMac.doFinal(ServiceUtils.toByteArray(localStringBuilder2.toString()));
 paramBCSHttpRequest.addParameter("sign", ":" + paramBCSCredentials.getAccessKey() + ":" + ServiceUtils.toBase64(arrayOfByte));
 
         最后这个代码实际上是利用doFinal加密函数对buffer2里面得数据进行加密,并生成一个20位的mac。当然这里又使用base64进行了编码,最后将sign作为http的一个参数传递过去。
        我们在利用例子说明一下:假设buffer1和buffer2里面的数据如下:
        buffer1 里面得数据是:
           MBOIST
       buffe2里面得数据是:
       Method=GET
       Bucket=bucket
       Object=object
       Ip=192.168.1.1
       Time=4321
       Size=1234
       最后生成的sign是: MBOITS:akakak:52sIR4vwpC4FKKOERHLHJGS0vYk=
      最前面是MBO和里面相应的参数,中间是一个冒号,冒号后面是AK,又是一个冒号,最后是根据SK作为密钥,采用HmacSHA1生成的一个20位的mac再经过BASE64编码后的结果,好了这个整个方法我就分析完了。
       在最后我们再来看一下这个方法,这个方法是void类型,说明是没有返回值的,有四个参数BaiduBCSRequest,BCSHttpRequest,BCSCredentials,BCSSignCondition。其中BCSSignCondition不是必须得, BaiduBCSRequest就是我们通常操作的request对象,在这个函数里面要用它的bucket、object、httpMethodName属性, BCSCredentials里面携带的是AK和FK信息,Condition里面是生成sign的额外的一些非必填的参数。返回值是通过 BCSHttpRequest的addParameter进行隐身返回的。总共传递了三个参数,sign是必须得,time和size参数如果有的时候会填写。
      
httpRequest.addParameter("time", String.valueOf(signCondition.getTime()));
httpRequest.addParameter("size", String.valueOf(signCondition.getSize()));
httpRequest.addParameter("sign", signFlags.append(":").append(credentials.getAccessKey()).append(":").append(ServiceUtils.toBase64(data))
					.toString());
         我们可能要发问了,为什么IP存在的时候没有向服务器端传递呢,我想可能服务器端为了避免出错,可能从请求列表里面获的了ip信息了,不过在目前复杂的网络条件下,这种方式是不是可取呢,我感觉应该还是信任客户端的ip,然后根据传递过来的ip数据进行数据签名验证,否则极有可能导致验证不通过。
        最后在附近里面我上传了bcs的jar和反编译工具,希望对大家有所帮助。对于这个过程官方文档讲的也很明白,感兴趣的可以看看:官方文档        

你可能感兴趣的:(验证,BCS,BCSCredentials,BCSSigner)