腾讯对象存储服务COS加密签名上传文件与下载文件的剖析,福利提供给所有使用Android的小伙伴们!

犀渠玉剑良家子,白马金羁侠少年。这篇文章主要讲述腾讯对象存储服务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

腾讯对象存储服务COS加密签名上传文件与下载文件的剖析,福利提供给所有使用Android的小伙伴们!

文章图片


创建完毕之后, 获取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())))); } }


最后附上工具类代码: 【腾讯对象存储服务COS加密签名上传文件与下载文件的剖析,福利提供给所有使用Android的小伙伴们!】

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的使用和加密签名校验等就分享完毕了, 谢谢大家的观看!



    推荐阅读