iOS开发加解密算法-基础篇(3)<大文件加解密>

一、 需求驱动功能开发,项目的需求是文件先经过服务端加密,客户端下载完成后再进行解密后使用,最终确认的方案是对文件分段加密,加密后再拼接成新的文件,最后在实现的时候遇到过一些坑<内存问题>。该篇只是介绍我们项目中使用的文件加密方案,并不代表移动端开发主流文件加密的方案

</br>

二、实现:对大文件进行分段切割

首先想到的就是OC自带的文件处理类NSFileHandle,不过在使用的时候遇到了一个运存无法消除的问题,虽然可以用NSFilehandlereadDataOfLengthseekToFileOffset的两个方法达到分段读取文件的功能,但是每次调用的时候程序的内存是叠加的,文件多大需要消耗的内存就有多大,当需要的内存大于程序的上限的时候,就会造成程序的闪退。


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;
}

Demo地址

如有不当之处欢迎指正。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 项目也快两年了,项目这么长时间下来经历了各种加解密算法,坑也踩过不少.现在把项目中使用过一些常用的加解密算法总结一...
    踏遍青山阅读 6,771评论 1 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 135,798评论 19 139
  • 首先罗列一些知识点: 1.加密算法通常分为对称性加密算法和非对称性加密算法:对于对称性加密算法,信息接收双方都需事...
    JonesCxy阅读 5,282评论 2 4
  • 嘟哝嘟哝:最近接到一个任务:在客户端动态生成RSA密钥对,然后向服务器发送这个密钥对中的公钥字符串,由服务器进行公...
    TimmyR阅读 12,538评论 19 21
  • 忘掉今天犯的错误,其实是对自己的一种宽恕。是人就会犯错误,有时候就是当时心念一转的瞬间,于是犯下一些自己铭记的错误...
    夏烟阅读 1,293评论 0 0