犀渠玉剑良家子,白马金羁侠少年。这篇文章主要讲述腾讯对象存储服务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()))));
}
}
最后附上工具类代码: 【腾讯对象存储服务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的使用和加密签名校验等就分享完毕了, 谢谢大家的观看!
推荐阅读
- 安卓获取多媒体(包括 视频音频图片)数据
- Android实现文章+评论(MVP,RxJava,Dagger2,ButterKnife)
- 浅谈Android多屏幕的事
- 一名Android开发者的微信小程序填坑之路
- §1.2 Android项目结构及“Hello World”应用解析
- §1.1 创建Android项目
- android array根据一个或多个属性排序问题
- Android Studio 运行出现 Error:Execution failed for task ':app:transformResourcesWithMergeJavaResForD
- 刚知道的android属性