一、 需求驱动功能开发,项目的需求是文件先经过服务端加密,客户端下载完成后再进行解密后使用,最终确认的方案是对文件分段加密,加密后再拼接成新的文件,最后在实现的时候遇到过一些坑<内存问题>。该篇只是介绍我们项目中使用的文件加密方案,并不代表移动端开发主流文件加密的方案
</br>
二、实现:对大文件进行分段切割
首先想到的就是OC自带的文件处理类NSFileHandle,不过在使用的时候遇到了一个运存无法消除的问题,虽然可以用
NSFilehandle
的readDataOfLength
和seekToFileOffset
的两个方法达到分段读取文件的功能,但是每次调用的时候程序的内存是叠加的,文件多大需要消耗的内存就有多大,当需要的内存大于程序的上限的时候,就会造成程序的闪退。
OC的方法虽然能实现功能,但是性能有不满足要求,因为C语言自带标准I/O库,就试着用C语言写了个文件分段读取的方法,并实现了内存的即时的释放,保证了App的性能需求。调用的方法解释可以参考:C语言文件操作函数|c语言文件读写函数,fopen方法参数说明参见百科,具体方法实现如下:
/**
* 解密视文件,主要思路是先找到待解密的文件,将解密的文件存放与临时文件下,
* 解密完成将加密的文件删除再将解密完成的文件替换成原文件的名字
*
* @param mediaPath 文件路径
* @param index 文件路径下标
*
* @return 是否解密成功
*/
- (BOOL)addDecryptFileWithMediaPath:(NSString *)mediaPath index:(NSInteger)index header:(NSInteger)header key:(NSInteger)key{
NSFileManager *manager = [NSFileManager defaultManager];
NSString *subPath = mediaPath;
// 文件类型
NSString *pathExtension = [subPath pathExtension];
// 文件路径的前路径
NSString *prePath = [subPath stringByDeletingLastPathComponent];
// 当前文件名 主要采用的是创建一个
NSString *tempfilePath = [NSString stringWithFormat:@"%@/ios_temp_%ld.%@",prePath,index,pathExtension];
[manager removeItemAtPath:tempfilePath error:nil];
[manager createFileAtPath:tempfilePath contents:nil attributes:nil];
// 创建读写FILE
FILE *readFile;
FILE *writeFile;
//打开发文件
readFile = fopen([subPath cStringUsingEncoding:NSUTF8StringEncoding], "rb+");
writeFile = fopen([tempfilePath cStringUsingEncoding:NSUTF8StringEncoding], "a+");
//获取文件属性信息
NSDictionary *attributes = [manager attributesOfItemAtPath:subPath error:nil];
//获取文件字节总长度
self.fileLength = ((NSNumber *)[attributes objectForKey:@"NSFileSize"]).integerValue;
int decodeKey = (int)key;
//根据我们的加密头和加密参数判断是否需要解密
if (self.fileLength > header) {
//如果符合解密条件将当前初始解密文件长度设置成头的长度<header是加密的时候添加上的在解密的时候需要
先将header的长度去除再进行解密>
self.currnentLength = header;
//调用解密方法
BOOL isSuc = decodeFile(readFile, self.fileLength, self.currnentLength, writeFile,decodeKey);
if (isSuc) {
if([manager removeItemAtPath:mediaPath error:nil]){
isSuc = [manager moveItemAtPath:tempfilePath toPath:mediaPath error:nil];
}else{
isSuc = NO;
}
}
return isSuc;
}else{
return NO;
}
}
=============================我是分割线============================
/**
* C函数解密文件的方法
*
* @param file 待解密的文件File
* @param fileLength 文件长度
* @param currentLegth 当前解密的文件长度
* @param direcFile 目标存储目录
*/
BOOL decodeFile(FILE *file,long fileLength,long currentLegth,FILE *direcFile,int key){
BOOL isSucessed;
//分段最大读取长度 1MB
NSInteger readLength = 1024 * 1024 * 1;
// 如果文件长度减去当前解密到的文件长度还大于分段最大读取长度则采用递归调用解密
if (fileLength - currentLegth > readLength) {
// 指针指向首字节
//读取文件
// 成功,返回0,失败返回-1
int set = fseek(file, currentLegth, SEEK_SET);
if (set == 0) {
//设置buffer保存分段读取文件的Data值
Byte *buffer = malloc(sizeof(Byte) * readLength);
fread(buffer, readLength, sizeof(Byte), file);
//读取成功后设置当前解密长度
currentLegth += readLength;
//设置读取位置
// 解密方法 可以在此基础上采用其他的加解密<RSA/AES>
for (int i = 0 ; i < readLength; i++) {
*(buffer + i ) -= key;
}
// 将解密后的文件流写入解密目标文件
size_t writedData = fwrite(buffer, sizeof(Byte), readLength, direcFile);
if (writedData == readLength) {
isSucessed = YES;
}else{
isSucessed = NO;
}
// 释放内存
free(buffer);
if (isSucessed) {
// 递归调用
return decodeFile(file, fileLength, currentLegth,direcFile,key);
}else{
return isSucessed;
}}}else{//如果文件长度减去当前长度小于最小分段读取长度 则跳出递归完成解密
// 大于0的话继续解密
if(fileLength - currentLegth > 0){
long length = fileLength - currentLegth;
int set = fseek(file, currentLegth, SEEK_SET);
if (set == 0) {
Byte *buffer = malloc(sizeof(Byte) * length);
fread(buffer, length, sizeof(Byte), file);
currentLegth += length;
//设置读取位置
for (int i = 0 ; i < length; i++) {
*(buffer + i ) -= key;
}
size_t writedData = fwrite(buffer, sizeof(Byte), length, direcFile);
printf("最终-fseek--%d",set);
if (writedData == length) {
isSucessed = YES;
}else{
isSucessed = NO;
}
// 释放内存
free(buffer);
// 关闭文件
fclose(file);
fclose(direcFile);
}
}
}
return isSucessed;
}