从文件加密到到视频文件进度条播放揭秘
Posted 汪小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从文件加密到到视频文件进度条播放揭秘相关的知识,希望对你有一定的参考价值。
文件加密
使用 Cipher CipherInputStream CipherOutputStream 实现对文件的加解密
每个文件使用一个秘钥 String aesKey = UUID.randomUUID().toString().replace("-","");
可以通过uuid or 其他的途径生成一个唯一的秘钥。
private static final String ALGORITHM_STREAM = "AES/ECB/PKCS5Padding";
/**
* 加密数据
*
* @param input
* @param key
* @return
* @throws Exception
*/
public static InputStream encodeStream(InputStream input, String key) throws Exception
SecretKey secretKey = generateAesKey(key);
Cipher c = Cipher.getInstance(ALGORITHM_STREAM);
c.init(1, secretKey);
return new CipherInputStream(input, c);
/**
* 解密文件流信息
*
* @param input
* @param key
* @return
* @throws Exception
*/
public static InputStream decodeStream(InputStream input, String key) throws Exception
SecretKey secretKey = generateAesKey(key);
Cipher c = Cipher.getInstance(ALGORITHM_STREAM);
c.init(2, secretKey);
return new CipherInputStream(input, c);
private static SecretKey generateAesKey(String key)
try
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
keyGenerator.init(128, secureRandom);
return keyGenerator.generateKey();
catch (GeneralSecurityException e)
throw new RuntimeException(e);
视频分段渐进式播放
样例eg: http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4
当前这个视频播放实现随机播放、实现分块下载等等能力,一般情况下后端下载视频
http://localhost:8080/web/file-upload/common-file-download?fileId=ab87ef175dc1419b922acb35dd3ad58e
提供类似的URL地址 后端直接写流到浏览器 IOUtils.copy(encodeInputStream, response.getOutputStream());
当点击视频中进度条的时候永远都不行,点击进度条相当于重新请求、视频流信息重0开始,直接破坏了进度条的规则。
查询了一些资料,其实视频播放的时候通过Range 进行了分段的请求、我们可以对于流量进行分段的下载处理
206 状态码 & Content-Range 分段的响应下载,读取当前流信息中指定开始到指定长度的流信息
IOUtils.copyLarge(encodeInputStream, response.getOutputStream(), startByte, contentLength, IOUtils.byteArray());
@GetMapping(value = "/common-file-download")
@ResponseBody
public void commonFileDownload(@RequestParam String fileId, HttpServletRequest request, HttpServletResponse response) throws Exception
File desc = new File(System.getProperty("user.dir") + "/file/" + fileId);
//获取从那个字节开始读取文件
try (FileInputStream inputStream = new FileInputStream(desc))
InputStream encodeInputStream = AesUtils.decodeStream(inputStream, fileId);
int fSize = FILE_LENGTH;
if (this.haveRanges(request))
// 断点续传
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
MutablePair<Integer, Integer> rangeInfo = this.getRangeInfo(request.getHeader(HttpHeaders.RANGE), fSize, 5 * 1024 * 1024);
//开始下载位置
int startByte = rangeInfo.getRight();
//结束下载位置
int endByte = rangeInfo.getLeft();
//要下载的长度
int contentLength = endByte - startByte + 1;
//Content-Length 表示资源内容长度,即:文件大小
response.setHeader(HttpHeaders.CONTENT_LENGTH, contentLength + "");
//Content-Range 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + "-" + endByte + "/" + fSize);
String fileName = URLEncoder.encode("test.mp4", StandardCharsets.UTF_8);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + fileName);
String mimetype = Mimetypes.getInstance().getMimetype(fileName);
response.setContentType(mimetype);
IOUtils.copyLarge(encodeInputStream, response.getOutputStream(), startByte, contentLength, IOUtils.byteArray());
return;
String fileName = URLEncoder.encode("test.mp4", StandardCharsets.UTF_8);
String mimetype = Mimetypes.getInstance().getMimetype(fileName);
response.setContentType(mimetype);
IOUtils.copy(encodeInputStream, response.getOutputStream());
catch (ClientAbortException e)
log.debug("ignore ", e.getMessage());
catch (Exception e)
log.error("error", e);
finally
IOUtils.close(response.getOutputStream());
/**
* 获取 range 的长度信息
*
* @param range
* @param fileSize
* @param defaultRangeLengthSize
* @return
*/
public MutablePair<Integer, Integer> getRangeInfo(String range, int fileSize, int defaultRangeLengthSize)
MutablePair<Integer, Integer> rangeInfo = new MutablePair<>();
rangeInfo.setLeft(fileSize - 1);
rangeInfo.setRight(0);
if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-"))
range = range.substring(range.lastIndexOf("=") + 1).trim();
String[] ranges = range.split("-");
int startByte = 0;
int endByte = fileSize - 1;
try
//根据range解析下载分片的位置区间
if (ranges.length == 1)
//情况1,如:bytes=-1024 从开始字节到第1024个字节的数据
if (range.startsWith("-"))
endByte = Integer.parseInt(ranges[0]);
//情况2,如:bytes=1024- 第1024个字节到最后字节的数据
else if (range.endsWith("-"))
startByte = Integer.parseInt(ranges[0]);
//增加一个默认的信息
endByte = startByte + defaultRangeLengthSize;
if (endByte >= fileSize - 1)
endByte = fileSize - 1;
//情况3,如:bytes=1024-2048 第1024个字节到2048个字节的数据
else if (ranges.length == 2)
startByte = Integer.parseInt(ranges[0]);
endByte = Integer.parseInt(ranges[1]);
catch (NumberFormatException e)
startByte = 0;
endByte = fileSize - 1;
rangeInfo.setRight(startByte);
rangeInfo.setLeft(endByte);
return rangeInfo;
/**
* 是否有Range
*
* @param request
* @return
*/
public boolean haveRanges(HttpServletRequest request)
String range = request.getHeader(HttpHeaders.RANGE);
if (StringUtils.isNotBlank(range) && range.contains("bytes=") && range.contains("-"))
return true;
return false;
感觉一切都很好了… 本地文件也可以了…
块存储文件服务器加密文件分块下下载
第一个版本 【进度条点击越靠后的时候 响应的时间越来越长】
下载非常的慢
http://localhost:8080/web/file-upload/version1-file-download
为什么?主要是下面这个代码每次都需要下载跳过的流文件进行解密,随着长度的加深 速度越来越慢
IOUtils.copyLarge(encodeInputStream, response.getOutputStream(), startByte, contentLength, IOUtils.byteArray())
/**
* 下载比较慢
*
* @param fileId
* @param request
* @param response
* @throws Exception
*/
@GetMapping(value = "/version1-file-download")
@ResponseBody
public void version1(@RequestParam(required = false) String fileId, HttpServletRequest request, HttpServletResponse response) throws Exception
File desc = new File(System.getProperty("user.dir") + "/file/" + fileId);
fileId = "Qq87r0SidfdM9i5QDrtKLTbRpcGam0qd";
S3Object object = s3.getObject(BUCK_NAME, fileId);
try (InputStream inputStream = object.getObjectContent())
InputStream encodeInputStream = AesUtils.decodeStream(inputStream, fileId);
int fSize = FILE_LENGTH;
if (this.haveRanges(request))
// 断点续传
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
MutablePair<Integer, Integer> rangeInfo = this.getRangeInfo(request.getHeader(HttpHeaders.RANGE), fSize, 5 * 1024 * 1024);
//开始下载位置
int startByte = rangeInfo.getRight();
//结束下载位置
int endByte = rangeInfo.getLeft();
//要下载的长度
int contentLength = endByte - startByte + 1;
//Content-Length 表示资源内容长度,即:文件大小
response.setHeader(HttpHeaders.CONTENT_LENGTH, contentLength + "");
//Content-Range 表示响应了多少数据,格式为:[要下载的开始位置]-[结束位置]/[文件总大小]
response.setHeader(HttpHeaders.CONTENT_RANGE, "bytes " + startByte + "-" + endByte + "/" + fSize);
String fileName = URLEncoder.encode("test.mp4", StandardCharsets.UTF_8);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline;filename=" + fileName);
String mimetype = Mimetypes.getInstance().getMimetype(fileName);
response.setContentType(mimetype);
IOUtils.copyLarge(encodeInputStream, response.getOutputStream(), startByte, contentLength, IOUtils.byteArray());
return;
String fileName = URLEncoder.encode("test.mp4", StandardCharsets.UTF_8);
String mimetype = Mimetypes.getInstance().getMimetype(fileName);
response.setContentType(mimetype);
IOUtils.copy(encodeInputStream, response.getOutputStream());
catch (ClientAbortException e)
log.debug("ignore ", e.getMessage());
catch (Exception e)
log.error("error", e);
finally
IOUtils.close(response.getOutputStream());
第二个版本 偶尔出行错误 & aes 解密 没有16块 & 播放偶现突然停止
http://localhost:8080/web/file-upload/version2-file-download
为了解决下载响应非常慢的问题,需要通过s3 支持的分块下载
支持通过header 进行分块下载…
GetObjectRequest getObjectRequest = new GetObjectRequest(BUCK_NAME, fileId);
int startOffset = rangeInfo.getRight();
getObjectRequest.setRange(startOffset);
object = s3.getObject(getObjectRequest);
感觉好像好了,其实还有问题,任意拖住进度条一会就不能播放了…自然就停止了…
看到报错,不过这个问题很关键 下载的内容长度没有16的整数倍
java.io.IOException: javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/javax.crypto.CipherInputStream.getMoreData(CipherInputStream.java:128)
at java.base/javax.crypto.CipherInputStream.read(CipherInputStream.java:242)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1384)
at org.apache.commons.io.IOUtils.copyLarge(IOUtils.java:1342)
**
* 下载偶尔失败
*
* @param fileId
* @param request
* @param response
* @throws Exception
*/
@GetMapping(value = "/version2-file-download")
@ResponseBody
public void version2(@RequestParam(required = false) String fileId, HttpServletRequest request, HttpServletResponse response) throws Exception
File desc = new File(System.getProperty("user.dir") + "/file/" + fileId);
fileId = "Qq87r0SidfdM9i5QDrtKLTbRpcGam0qd";
S3Object object = null;
boolean isRange3s = false;
if (haveRanges(request))
GetObjectRequest getObjectRequest = new GetObjectRequest(BUCK_NAME, fileId);
MutablePair<Integer, Integer> rangeInfo = this.getRangeInfo(request.getHeader(HttpHeaders.RANGE), FILE_LENGTH, 5 * 1024 * 1024);
int startOffset = rangeInfo.getRight();
getObjectRequest.setRange(startOffset);
object = s3.getObject(getObjectRequest);
isRange3s = true;
else
object = s3.getObject(BUCK_NAME, fileId);
try (InputStream inputStream = object.getObjectContent())
InputStream encodeInputStream = AesUtils.decodeStream(inputStream, fileId);
int fSize = FILE_LENGTH;
if (this.haveRanges(request))
// 断点续传
response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
MutablePair<Integer, Integer> rangeInfo = this.getRangeInfo(request.getHeader(HttpHeaders.RANGE), fSize, 5 * 1024 * 1024);
//开始下载位置
int startByte = rangeInfo.getRight();
//结束下载位置
int endByte = rangeInfo.getLeft();
//要下载的长度
int contentLength = endByte - startByte + 1;
//Content-Length 表示资源内容长度,即以上是关于从文件加密到到视频文件进度条播放揭秘的主要内容,如果未能解决你的问题,请参考以下文章