騰訊對象存儲服務COS加密簽名上傳文件與下載文件的剖析,福利提供給所有使用Android的小夥伴們!

在做一些用戶需求的時候,公司往往需要工程師採集到更多有用的關於用戶的個人信息,然後對用戶羣進行分析,今天我不是來分析這些的,今天我主要是說

騰訊推出的款雲產品,那就是對象存儲服務COS,這個產品面向所有開發者,新用戶都有免費享有10G的使用權,10G可能對於做方案的工程師來說可能是微不

足道的,比如後視鏡和車載方案,會常常需要用到視頻的存儲與雲分享,當然這裏不是隻本地存儲哦,我指的是用戶在使用方案商的方案的時候,比如他開車

的時候錄了一段視頻需要分享到某個域,共享給大家看,比如微信,這時候他肯定需要將這個視頻存儲在一個服務器上,這時候問題來了,如果你們公司夠有

錢夠任性可以買更好的服務器和配置,那就不需要用到這個COS對象存儲產品了,但是就app的和後臺的性能來說,這個就很有必要了


比如我目前就在寫一個大型的app,初步用戶數大概在50萬以上,舉個簡單的小例子,加入今天有2000+個用戶同時一起更換用戶頭像,那這時候對後臺的來說

就是影響效率的操作,當然牛逼的服務器配置我們就不再此對比了,那是超大公司的配置,但我們可以將這個樣的負荷轉移到別的地方,比如COS對象存儲,

我們客戶端只需要將用戶更換頭像的圖片地址放到後臺上傳就行了,跟服務器沒有任何交互,一但COS存儲完畢,我們會收到Response這時候如果是

Success,那我們再向我們自己服務器的後臺插入一個COS的頭像地址,下次用戶重新登錄或者登錄超時或更換IOS/Android登錄,後臺返回一個遠程COS頭

像地址,我們客戶端後臺獲取,輕鬆完美!當然還有一個小缺陷就是,如果用戶是拍照獲取頭像的話,因爲是超清晰的所以比較大,上傳可能會異常的慢,而且設置頭像的

時候還可能OOM,但這些都是小問題,因爲一個頭像本來可見範圍就那麼一點點,我們客戶端是可以採取措施的,比如壓縮可裁剪,像我就是寫了一個裁剪依賴,對較大的

超過1.5MB的頭像首先進行壓縮,確保它不會失真就得大於30%的壓縮率,然後在裁剪,最後把所有用戶頭像均控制在1MB以內,輕鬆搞定!


下面開始分享COS的使用和加密簽名算法


COS免費10G地址:https://www.qcloud.com/product/cos.html


進入這個鏈接以後,點擊立即使用,隨後完成準備工作開始創建 bucket


創建完畢之後,獲取API的一些密鑰,點擊獲取API密鑰或密鑰管理

拿到這些必須的參數以後,就要開始編寫校驗簽名加密程序了,用來訪問COS

Android SDK 地址:https://www.qcloud.com/doc/product/227/3391

SDK地址下載下來的Demo是可用的,但是對簽名加密那一塊壓根就沒提,甚是噁心,簽名都是從自己後臺的PHP接口生成返回到客戶端的,而且騰訊官方也沒有提供相應

的demo,太不負責任了。。。

於是我開始動手寫算了,太坑爹了,加密簽名需要用 

HMAC-SHA1 對拼接參數進行簽名,然後簽名串需要使用 Base64 編碼

官方給出的提示就如下,其他什麼都沒有了。。。

SignTmp = HMAC-SHA1(SecretKey, orignal)

Sign = Base64(SignTmp.orignal)

其中SecretKey爲2.1節獲取的項目Secret Key,orignal爲2.2節中拼接好的簽名串,首先對orignal使用HMAC-SHA1算法進行簽名,然後將orignal附加到簽名結果的末尾,再進行Base64編碼,得到最終的sign。

注:此處使用的是標準的Base64編碼,不是urlsafe的Base64編碼,請注意。

這裏我還是做一下解釋吧,要不然大家都看不懂,HMAC-SHA1函數需要兩個參數,一個是SecretKey,這個在API密鑰裏面獲得,另一個是original,這個是需要拼接所有

需要用到的參數,比如:

String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f=";

a: Appid ,可在API密鑰中獲取

b: 空間名稱bucket,比如筆者上圖的 "rmtonline"

k: Secret ID ,可在API密鑰中獲取

e: 簽名的有效期,一個UNIX Epoch時間戳,即簽名多久的使用期,最長3個月,從年精確到秒

t:  當前時間的UNIX Epoch時間戳,即簽名開始生效的日期,也精確到秒,而且e>t,因爲到期日必須大於生效日期

r: 隨機串,無符號10進制整數,用戶需自行生成,最長10位

f: 這個參數的值可以不填

比如:a=200001&b=newbucket&k=AKIDUfLUEUigQiXqm7CVSspKJnuaiIKtxqAv&e=1438669115&t=1436077115&r=11162&f=

下面開始進行編寫簽名和加密函數

	private static final String MAC_NAME = "HmacSHA1";
	private static final String ENCODING = "UTF-8";

	/**
	 * 
	 * @param SecretKey
	 *            密鑰
	 * @param EncryptText
	 *            簽名串
	 * @return
	 * @throws Exception
	 */
	public static byte[] HmacSHA1Encrypt(String SecretKey, String EncryptText)
			throws Exception {
		byte[] data = SecretKey.getBytes(ENCODING);
		SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
		Mac mac = Mac.getInstance(MAC_NAME);
		mac.init(secretKey);
		byte[] text = EncryptText.getBytes(ENCODING);
		return mac.doFinal(text);
	}
該函數返回一個byte[],SecretKey 爲你的secretKey 下面開始拼接original

	public static String getSignOriginal() {

		return String.format(TencentUpload.Original,
				ParamPreference.TENCENT_COS_APPID,
				ParamPreference.TENCENT_COS_BUCKET,
				ParamPreference.TENCENT_COS_SECRET_ID,
				String.valueOf(getFurureLinuxDate()),
				String.valueOf(getLinuxDateSimple()), getRandomTenStr());
	}

TencentUpload.Original 爲待通配字符串:String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f="

ParamPreference.TENCENT_COS_APPID 爲常量字符串,即你的appid

ParamPreference.TENCENT_COS_BUCKET 爲常量字符串,即你的bucket 名稱

ParamPreference.TENCENT_COS_SECRET_ID 爲常量字符串,即你的 secretID

String.valueOf(getFurureLinuxDate()) 和 String.valueOf(getLinuxDateSimple()) 爲簽名生效期和到期日的Linux時間戳

getRandomTenStr() 爲隨即無符號的int 5-8 位 開頭不爲0 後面爲0-9的轉字符串拼接

	@SuppressLint("SimpleDateFormat")
	public static long getFurureLinuxDate() {
		try {
			String futureTime = ParamPreference.TENCENT_COS_FUTURE_LINUXTIME;
			Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
					.parse(futureTime);
			long unixTimestamp = date.getTime() / 1000L;
			return unixTimestamp;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}


	public static long getLinuxDateSimple() {
		try {
			long unixTimestamp = System.currentTimeMillis() / 1000L;
			return unixTimestamp;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}


	private static String getRandomTenStr() {
		String randomstr = null;
		randomstr = String.valueOf(new Random().nextInt(8) + 1);
		int random = new Random().nextInt(3) + 5;
		for (int i = 0; i < random; i++) {
			randomstr += String.valueOf(new Random().nextInt(9));
		}
		return randomstr;
	}

隨後需要將他們簽名和加密,最後轉成標準Base64編碼格式,需要注意的是,獲取 SIGN 需要將 secretKey 和 original 傳入HmacSHA1Encrypt(String SecretKey, String EncryptText)函數,獲取到簽名的byte[]結果後,還需要在其後面

追加 original 最後再進行Base64編碼轉碼得的 SIGN 

編寫代碼如下:

		public String getTencentSign() {
			try {

				String Original = TencentUtils.getSignOriginal();

				byte[] HmacSHA1 = TencentUtils.HmacSHA1Encrypt(
						ParamPreference.TENCENT_COS_SECRET_KEY, Original);

				byte[] all = new byte[HmacSHA1.length
						+ Original.getBytes(ENCODING).length];

				System.arraycopy(HmacSHA1, 0, all, 0, HmacSHA1.length);

				System.arraycopy(Original.getBytes(ENCODING), 0, all,
						HmacSHA1.length, Original.getBytes(ENCODING).length);

				if (DEBUG) {
					Log.v(TencentUpload.TAG, "getTencentSign() Original:\n"
							+ Original);
				}

				String SignData = Base64Util.encode(all);

				if (DEBUG) {
					Log.v(TencentUpload.TAG, "getTencentSign() SignData:\n"
							+ SignData);
				}
				return SignData;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return "get sign failed";
		}
ParamPreference.TENCENT_COS_SECRET_KEY 爲常量字符串,即你的 secretKey

Base64Util:

package com.rmt.online.tools;

import java.io.ByteArrayOutputStream;

public class Base64Util {

	private static final char[] base64EncodeChars = new char[] { 'A', 'B', 'C',
			'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
			'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c',
			'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
			'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2',
			'3', '4', '5', '6', '7', '8', '9', '+', '/' };
	private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1,
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
			-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
			-1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59,
			60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
			10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1,
			-1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
			38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1,
			-1, -1 };

	private Base64Util() {
	}

	public static String encode(byte[] data) {
		StringBuffer sb = new StringBuffer();
		int len = data.length;
		int i = 0;
		int b1, b2, b3;
		while (i < len) {
			b1 = data[i++] & 0xff;
			if (i == len) {

				sb.append(base64EncodeChars[b1 >>> 2]);
				sb.append(base64EncodeChars[(b1 & 0x3) << 4]);

				sb.append("==");
				break;
			}
			b2 = data[i++] & 0xff;
			if (i == len) {
				sb.append(base64EncodeChars[b1 >>> 2]);
				sb.append(base64EncodeChars[((b1 & 0x03) << 4)
						| ((b2 & 0xf0) >>> 4)]);
				sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
				sb.append("=");
				break;
			}
			b3 = data[i++] & 0xff;
			sb.append(base64EncodeChars[b1 >>> 2]);
			sb.append(base64EncodeChars[((b1 & 0x03) << 4)
					| ((b2 & 0xf0) >>> 4)]);

			sb.append(base64EncodeChars[((b2 & 0x0f) << 2)
					| ((b3 & 0xc0) >>> 6)]);
			sb.append(base64EncodeChars[b3 & 0x3f]);
		}
		return sb.toString();
	}

	public static byte[] decode(String str) {
		byte[] data = str.getBytes();
		int len = data.length;
		ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
		int i = 0;
		int b1, b2, b3, b4;
		while (i < len) { /* b1 */
			do {
				b1 = base64DecodeChars[data[i++]];
			} while (i < len && b1 == -1);
			if (b1 == -1) {
				break;
			} /* b2 */
			do {
				b2 = base64DecodeChars[data[i++]];
			} while (i < len && b2 == -1);
			if (b2 == -1) {
				break;
			}
			buf.write((int) ((b1 << 2) | ((b2 & 0x30) >>> 4))); /* b3 */
			do {
				b3 = data[i++];
				if (b3 == 61) {
					return buf.toByteArray();
				}
				b3 = base64DecodeChars[b3];
			} while (i < len && b3 == -1);
			if (b3 == -1) {
				break;
			}
			buf.write((int) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2))); /* b4 */
			do {
				b4 = data[i++];
				if (b4 == 61) {
					return buf.toByteArray();
				}
				b4 = base64DecodeChars[b4];
			} while (i < len && b4 == -1);
			if (b4 == -1) {
				break;
			}
			buf.write((int) (((b3 & 0x03) << 6) | b4));
		}
		return buf.toByteArray();
	}

	public static void main(String[] args) throws Exception {
		System.out.println(encode("liao".getBytes()));
		System.out.println(new String(decode(encode("1".getBytes()))));
	}
}

最後附上工具類代碼:

package com.rmt.online.tools;

/**
 * class:  TencentUtils
 * author: Engineer-Jsp
 * use:    Tencent cos cdn upload and download utils
 * date:   2016/09/14
 * */

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import com.rmt.online.preference.ParamPreference;
import com.tencent.download.Downloader;
import com.tencent.download.core.DownloadResult;
import com.tencent.upload.UploadManager;
import com.tencent.upload.Const.FileType;
import com.tencent.upload.task.IUploadTaskListener;
import com.tencent.upload.task.ITask.TaskState;
import com.tencent.upload.task.data.FileInfo;
import com.tencent.upload.task.impl.FileUploadTask;

public class TencentUtils {

	public static boolean DEBUG = true;
	private static final String MAC_NAME = "HmacSHA1";
	private static final String ENCODING = "UTF-8";

	/**
	 * 
	 * @param SecretKey
	 *            密鑰
	 * @param EncryptText
	 *            簽名串
	 * @return
	 * @throws Exception
	 */
	public static byte[] HmacSHA1Encrypt(String SecretKey, String EncryptText)
			throws Exception {
		byte[] data = SecretKey.getBytes(ENCODING);
		SecretKey secretKey = new SecretKeySpec(data, MAC_NAME);
		Mac mac = Mac.getInstance(MAC_NAME);
		mac.init(secretKey);
		byte[] text = EncryptText.getBytes(ENCODING);
		return mac.doFinal(text);
	}

	public static long getLinuxDateSimple() {
		try {
			long unixTimestamp = System.currentTimeMillis() / 1000L;
			return unixTimestamp;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}

	@SuppressLint("SimpleDateFormat")
	public static long getFurureLinuxDate() {
		try {
			String futureTime = ParamPreference.TENCENT_COS_FUTURE_LINUXTIME;
			Date date = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")
					.parse(futureTime);
			long unixTimestamp = date.getTime() / 1000L;
			return unixTimestamp;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return -1;
	}

	private static String getRandomTenStr() {
		String randomstr = null;
		randomstr = String.valueOf(new Random().nextInt(8) + 1);
		int random = new Random().nextInt(3) + 5;
		for (int i = 0; i < random; i++) {
			randomstr += String.valueOf(new Random().nextInt(9));
		}
		return randomstr;
	}

	public static String getSignOriginal() {

		return String.format(TencentUpload.Original,
				ParamPreference.TENCENT_COS_APPID,
				ParamPreference.TENCENT_COS_BUCKET,
				ParamPreference.TENCENT_COS_SECRET_ID,
				String.valueOf(getFurureLinuxDate()),
				String.valueOf(getLinuxDateSimple()), getRandomTenStr());
	}

	public static class TencentUpload {

		public static final String TAG = "TencentUpload";
		private static UploadManager mFileUploadManager = null;
		private FileUploadTask fileUploadTask = null;
		public static String Original = "a=%s&b=%s&k=%s&e=%s&t=%s&r=%s&f=";
		private static TencentUpload mTencentUpload = null;
		private static Context mContext = null;

		public static TencentUpload initTencentUpload(Context context) {
			mContext = context;
			if (mTencentUpload == null) {
				mTencentUpload = new TencentUpload();
			}
			if (mFileUploadManager == null) {
				mFileUploadManager = new UploadManager(context,
						ParamPreference.TENCENT_COS_APPID, FileType.File,
						ParamPreference.TENCENT_COS_PERSISTENCEID);
			}
			return mTencentUpload;
		}

		public void statrtUpLoad(String loacFilePath, String filename) {

			if (DEBUG) {
				Log.v(TAG, "DEBUG:" + "\nbucket:"
						+ ParamPreference.TENCENT_COS_BUCKET + "\nsrcFilePath:"
						+ loacFilePath + "\ndestPath:" + "/" + filename);
			}

			fileUploadTask = new FileUploadTask(
					ParamPreference.TENCENT_COS_BUCKET, loacFilePath, "/"
							+ filename, "", false, new IUploadTaskListener() {
						@Override
						public void onUploadFailed(final int errorCode,
								final String errorMsg) {

							Log.v(TAG, "上傳失敗 ret:" + errorCode + " msg:"
									+ errorMsg);

						}

						@Override
						public void onUploadProgress(final long totalSize,
								final long sendSize) {

							long p = (long) ((sendSize * 100) / (totalSize * 1.0f));
							Log.v(TAG, "上傳中: " + p + "%");

						}

						@Override
						public void onUploadStateChange(TaskState arg0) {

						}

						@Override
						public void onUploadSucceed(final FileInfo result) {

							Log.v(TAG, "上傳成功,下載路徑: " + result.url);

							if (DEBUG) {
								if (mContext == null) {
									return;
								}
								TencentDownload.initTencentDownload(mContext)
										.startDownLoad(result.url);
							}

						}
					});

			try {
				fileUploadTask.setAuth(getTencentSign());
				mFileUploadManager.upload(fileUploadTask);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		public String getTencentSign() {
			try {

				String Original = TencentUtils.getSignOriginal();

				byte[] HmacSHA1 = TencentUtils.HmacSHA1Encrypt(
						ParamPreference.TENCENT_COS_SECRET_KEY, Original);

				byte[] all = new byte[HmacSHA1.length
						+ Original.getBytes(ENCODING).length];

				System.arraycopy(HmacSHA1, 0, all, 0, HmacSHA1.length);

				System.arraycopy(Original.getBytes(ENCODING), 0, all,
						HmacSHA1.length, Original.getBytes(ENCODING).length);

				if (DEBUG) {
					Log.v(TencentUpload.TAG, "getTencentSign() Original:\n"
							+ Original);
				}

				String SignData = Base64Util.encode(all);

				if (DEBUG) {
					Log.v(TencentUpload.TAG, "getTencentSign() SignData:\n"
							+ SignData);
				}
				return SignData;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return "get sign failed";
		}
	}

	public static class TencentDownload {

		private static final String TAG = "TencentDownload";
		private static TencentDownload mTencentDownload = null;
		private static Downloader mDownloader = null;

		public static TencentDownload initTencentDownload(Context context) {
			if (mTencentDownload == null) {
				mTencentDownload = new TencentDownload();
			}
			if (mDownloader == null) {
				mDownloader = new Downloader(context,
						ParamPreference.TENCENT_COS_APPID,
						ParamPreference.TENCENT_COS_PERSISTENCEID);
			}
			return mTencentDownload;
		}

		public void startDownLoad(String downloadUrl) {
			mDownloader.download(downloadUrl,
					new Downloader.DownloadListener() {
						@Override
						public void onDownloadSucceed(final String url,
								final DownloadResult result) {

							String file_path = result.getPath();
							Bitmap bmp = decodeSampledBitmap(file_path, 2);
							Log.v(TAG, "下載完成:" + bmp.toString());
						}

						@Override
						public void onDownloadProgress(String url,
								long totalSize, final float progress) {

							long nProgress = (int) (progress * 100);
							Log.i(TAG, "下載進度: " + nProgress + "%");

						}

						@Override
						public void onDownloadFailed(String url,
								DownloadResult result) {

						}

						@Override
						public void onDownloadCanceled(String url) {

						}

					});
		}
	}

	public static Bitmap decodeSampledBitmap(String path, int sample) {
		final BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		options.inSampleSize = sample;
		options.inJustDecodeBounds = false;
		return BitmapFactory.decodeFile(path, options);
	}

}

工具類代碼裏面附帶了上傳文件和下載文件的函數,以及打印日誌,日誌調試開啓,將 DEBUG 設爲 true 即可,DEBUG 爲 true 默認開啓下載之前上

傳成功的哪一個文件,文件上傳默認的是覆蓋模式,即出現同名的文件執行覆蓋操作

工具類函數解析:

new FileUploadTask(ParamPreference.TENCENT_COS_BUCKET, loacFilePath, "/"+ filename, "", false, new IUploadTaskListener()...);

ParamPreference.TENCENT_COS_BUCKET 爲你的 bucket 名稱

loacFilePath 爲你的需要上傳的文件的絕對路徑

"/"+ filename 爲你需要上傳文件的相對路徑 如:/test.png

第四個參數爲空

第五個爲回調接口實例

至此關於騰訊存儲對象COS的使用和加密簽名校驗等就分享完畢了,謝謝大家的觀看!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章