AWS s3 V4簽名演算法

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原創,轉載請註明:http://www.jianshu.com/p/a6a8c3c2cead

一、開篇說明:

以下思考方向,是以Android端為出發點(IOS同理)

AWS:Amazon Web Services (亞馬遜雲端服務)

AWS s3 API文檔:https://aws.amazon.com/cn/documentation/s3/


Minio :(具體的解釋自行百度吧)一個基於 golang 語言開發的 AWS S3 儲存協議的開源實現,並附帶 web ui 介面,可以通過 Minio 搭建私人的相容 AWS S3 協議的儲存伺服器。

二、需求分析

項目需求:最近公司需要搭建一個檔案伺服器,讓移動端(Android、ios)用來儲存圖片等檔案。這個檔案伺服器後台使用minio搭建的。由於minio是基於AWS S3 儲存協議,所以我們移動端也需要實現相同的協議來上傳。移動端,我們確定了三種可行方案(方案優劣僅是基於對APK打包大小的影響程度來確定的):

方案一:基於AWS S3 儲存協議自己實現檔案上傳(最佳方案,最後項目引入檔案最小----大約100K,不影響apk大小)

方案二:使用AWS SDK 實現檔案上傳(中間方案,需要引入項目的jar包1.5M檔案略大)

方案三:使用minio SDK 實現檔案上傳(最差方案,需要引入6M jar包)

三、代碼實現

由於項目需求不同,供大家自行選擇,我將這三種方案實現一一說明。

方案三:

demo下載:https://github.com/Nergal1/minio-demo

1.Android端引入minio SDK jar包會和原生的發生衝突,會報一些安全錯誤。

引入時,去除衝突的包,com.fasterxml.jackson.core和com.google.code.findbugs:

dependencies {

                       compile ('io.minio:minio:3.0.4'){

                             excludegroup:'com.fasterxml.jackson.core'

                             excludegroup:'com.google.code.findbugs'

                        }

}

2.上傳代碼:

/*上傳圖片

bucketName:服務端隱藏檔夾,必須先建立

objectName:服務端隱藏檔名

inputStream:上傳檔案流

*/

minioClient.putObject(bucketName,objectName,inputStream,inputStream.available(),"application/octet-stream");

提醒:別忘了開啟網路許可權

3.簡易源碼流程圖,數字代表行號:


minio上傳源碼簡易流程圖



方案二:

demo下載:https://github.com/Nergal1/AWS-S3-demo

1.由於AWS SDK源碼中是開啟Service,然後建立線程池實現非同步上傳、下載功能。

資訊清單檔要註冊service:

<service android:name="com.amazonaws.mobileconnectors.s3.transferutility.TransferService"

android:enabled="true"/>

許可權:

android.permission.INTERNET

android.permission.ACCESS_NETWORK_STATE

android.permission.READ_EXTERNAL_STORAGE

android.permission.WRITE_EXTERNAL_STORAGE

Android 6.0+檔案讀寫權限需要代碼中即時擷取,demo中未作相容。如有檔案問題,請自行添加。

2.引入jar包:

aws-android-sdk-core-2.4.2.jar

aws-android-sdk-s3-2.4.2.jar

3.先配置Constants.java中的ENDPOINT、ACCESSKEY、SecretKey、BUCKET_NAME

上傳代碼見UploadActivity.java(下載請自行查看DownloadActivity):

TransferUtility transferUtility= Util.getTransferUtility(this);

//上傳

TransferObserver observer =transferUtility.upload(Constants.BUCKET_NAME,file.getName(),

file);

//設定上傳監聽

observer.setTransferListener(new UploadListener());

//監聽類

private class UploadListener implements TransferListener {

// Simply updates the UI list when notified. 上傳失敗監聽

@Override

public voidonError(intid,Exception e) {

Log.e(TAG,"Error during upload: "+ id,e);

updateList();

}

//上傳進度監聽

@Override

public voidonProgressChanged(intid, longbytesCurrent, longbytesTotal) {

Log.d(TAG,String.format("onProgressChanged: %d, total: %d, current: %d",

id,bytesTotal,bytesCurrent));

updateList();

}

//上傳狀態監聽

@Override

public voidonStateChanged(intid,TransferState newState) {

Log.d(TAG,"onStateChanged: "+ id +", "+ newState);//例如:UploadActivity: onStateChanged: 9, FAILD 失敗狀態

//            //根據id 查詢檔案路徑

//            TransferObserver transferObserver = transferUtility.getTransferById(id);

//            Log.d(TAG, "檔案地址---"+transferObserver.getAbsoluteFilePath());

updateList();

}

}

4.源碼簡易流程圖,數字代表行號:


aws sdk上傳源碼簡易流程圖

方案三:

demo下載:https://github.com/Nergal1/nergal-AWS-demo

1.添加許可權:

android.permission.INTERNET

android.permission.ACCESS_NETWORK_STATE

android.permission.READ_EXTERNAL_STORAGE

android.permission.WRITE_EXTERNAL_STORAGE

Android 6.0+檔案讀寫權限需要代碼中即時擷取,demo中已相容。

2.配置Constants.java中的ENDPOINT、ACCESSKEY、SecretKey、BUCKET_NAME、BUCKET_REGION

上傳代碼:

AWSTransferUtility utility = AWSTransferUtility.getInstance();

//***********檔案上傳,先設定監聽,後上傳檔案*************若需要用其他網路架構實現上傳,請自行實現BaseHttpClient,然後utility.setHttpClient

utility.setUploadListener(new MAWSUploadListener()).upload(file,Constants.BUCKET_NAME,file.getName());

//監聽類實現如下

/**

*@author zhangchen

*@date 2017/5/24 上午8:32

*@Description 檔案上傳監聽方法

*/

class MAWSUploadListener implements AWSUploadListener {

@Override

public voidonComplite(File file) {

System.out.println("onComplite---"+ file.getAbsolutePath());

}

@Override

public voidonError(File file,Throwable e) {

System.out.println("onError---file--"+ file.getAbsolutePath());

System.out.println("onError---Exception--"+ e.toString());

}

@Override

public voidonProgressChanged(File file, longbytesCurrent, longbytesTotal) {

System.out.println("onProgressChanged----"+ bytesCurrent);

}

}

3.哈哈,囉嗦了一大堆,終於到重點了,實現原理分析:

實際上AWS S3 儲存協議只不過是添加一些跟服務端協商的要求標頭,要求標頭的value是用AWS s3 V4簽名演算法算出來的。所以,上傳時帶上指定的要求標頭,以及合法的簽名演算法,其他地方跟普通上傳沒區別。服務端拿到要求標頭後,根據合法的簽名演算法,計算簽名值,然後跟用戶端要求標頭裡的簽名進行校正,成功後,服務端才允許上傳。

首先我們要解決兩個問題:

1.指定的要求標頭有哪些?

首先我們來看一個檔案上傳的請求:

--------------------------要求標頭--------------------------

PUT /test/IMG_20170519_165644_1.jpg HTTP/1.1

Content-MD5: 6PN5Zj06z+D5UkSG1ZxhNA==

x-amz-decoded-content-length: 2566214

x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD

Content-Type: image/jpeg

X-Amz-Date: 20170522T024116Z

User-Agent: aws-sdk-android/2.4.2 Linux/3.10.86-g6be8ceb Dalvik/2.1.0/0 zh_CN TransferService/2.4.2

aws-sdk-retry: 0/0

Accept-Encoding: identity

aws-sdk-invocation-id: 148858f7-e319-4578-b9f8-1b220f6af380

Authorization: AWS4-HMAC-SHA256 Credential=1NA5K80UU85NMPK4BPEW/20170522/us-east-1/s3/aws4_request,SignedHeaders=content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length,Signature=db4e0abd4290730ed6fd27867d2fa342942372b88f1b5ad49b113ab9c77d6cc9

Content-Length: 2568100

Host: fsst.anbanggroup.com

Connection: Keep-Alive

--------------------------------------請求體--------------

20000;chunk-signature=0cfa38451a889b866dbf43907ce3a72ee85f6b176168fb875903e8353366628e

。。。二進位檔案流。。。

-------------------------------------------

一大堆要求標頭,實際上根據 S3 API 文檔,Authorization要求標頭才是必要的

而Authorization的value中SignedHeaders是規定了使用哪些要求標頭來計算本次請求的簽名值。也就是說content-md5;host;x-amz-content-sha256;x-amz-date;x-amz-decoded-content-length這些要求標頭計算出來Signature的值。根據S3 API 文檔,SignedHeaders中host;x-amz-content-sha256;x-amz-date是必須存在的。

那為什麼本次請求還要加上content-md5,x-amz-decoded-content-length這兩個值?

我們來想想簽名的作用:

a.驗證請求身份

ACCESSKEY、SecretKey就是用來驗證身份的,這兩個參數也是計算Authorization的value中Signature的值所必須的。

b.防止篡改

SignedHeaders就是用來設定防止篡改的要求標頭的,content-md5是防止篡改請求內容(body),x-amz-decoded-content-length防止篡改請求內容長度的。

官方文檔中,SignedHeaders必須的host;x-amz-content-sha256;x-amz-date這幾個也就可以理解了,防止篡改請求地址,請求內容的sha256值,請求時間戳記

c.防止請求籤名被盜用

根據AWS S3 儲存協議,時間戳記(x-amz-date或Date要求標頭)15分鐘內有效,沒有許可權的使用者t通過截獲已簽名的request,可以篡改SignedHeaders中沒有包含的部分,所以官方建議,簽名所有要求標頭和請求體(也就是說SignedHeaders要盡量包含所有),還有最好用Https.

總結:囉嗦一大堆,必要的要求標頭有哪些:Authorization、SignedHeaders中包含的(必須包含的有host;x-amz-content-sha256;x-amz-date),host,x-amz-content-sha256,x-amz-date這些要求標頭是服務端校正簽名必須的。

2.簽名演算法是如何計算的?(即Authorization中的Signature)

有兩種簽名演算法:

第一種,單塊傳輸(本人demo中使用的這種方式)

檔案記憶體讀取兩次(一次計算檔案sha256時,一次檔案上傳時)

單塊上傳要求標頭:

x-amz-content-sha256: 32e820d03db121caf97206f8cbcc6202cf25bf59246e8d6b0e8f6e3502d68f66

或:x-amz-content-sha256: UNSIGNED-PAYLOAD(這種是單塊不進行簽名內容的寫法)

a.


單塊上傳簽名計算流程

功能函數說明:

Lowercase():字串轉成小寫

Hex():base 16編碼(全部小寫)

SHA256Hash():計算請求體body(上傳檔案時,body中只有檔案,即計算檔案的SHA256)的SHA256,然後base64

HMAC-SHA256():通過將key使用SHA256計算 HMAC

java方法:

public static byte[]sumHmac(byte[] key, byte[] data)

throwsNoSuchAlgorithmException,InvalidKeyException {

Mac mac = Mac.getInstance("HmacSHA256");

mac.init(newSecretKeySpec(key,"HmacSHA256"));

mac.update(data);

returnmac.doFinal();

}

Trim():去空格

UriEncode():

a.保持'A'-'Z','a'-'z', '0'-'9', '-', '.', '_',  '~'不變

b.空白字元必須轉成"%20" (而不是 "+")

c.byte編譯成“%”後面跟兩位的十六進位(字母大寫)

d.如果檔案名稱(object name)類似“photos/Jan/sample.jpg”,其中包含“/”,不進行轉義。

e.方法實現:

public static String UriEncode(CharSequence input, boolean

encodeSlash) {

StringBuilder result = new StringBuilder();

for (int i = 0; i < input.length(); i++) {

char ch = input.charAt(i);

if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a'

&& ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_' ||

ch == '-' || ch == '~' || ch == '.') {

result.append(ch);

} else if (ch == '/') {

result.append(encodeSlash ? "%2F" : ch);

} else {

result.append(toHexUTF8(ch));

}

}

return result.toString();

}

第二種,多塊傳輸

多塊上傳要求標頭:

x-amz-content-sha256: STREAMING-AWS4-HMAC-SHA256-PAYLOAD

把有效荷載(請求body)分成幾塊,固定或可變大小的塊。通過上傳塊,避免讀取整個檔案的有效荷載來計算簽名.

a.第一塊,計算所有的要求標頭的簽名,空body請求

b.第二塊,將第一個塊的簽名和有效載荷一起計算簽名

c.第n塊,將第n-1塊的簽名和有效載荷一起計算簽名

d.最後,發送0 bytes的塊包含第n塊的簽名。

要求標頭Authorization中的Signature計算同單塊上傳:


多塊上傳簽名計算流程圖

請求體中塊簽名計算:


請求體中比單塊上傳多了這些



chunk-signature的簽名計算流程圖

終於,說完了,第一次寫這麼長,講的有不清楚的多多包涵,有什麼問題可以給我留言,也可以發我郵箱18514689920@163.com.

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.