golang 解压带密码的zip包

目录

  • Zip文件详解
    • ZIP 文件格式主要特性
    • 常用算法
    • Zip格式结构图总览
    • Zip文件结构详解
      • 数据区
        • 本地文件头
        • 文件数据
        • 文件描述
      • 中央目录记录区(核心目录记录区 )
      • 中央目录记录尾部区
    • 压缩包解压过程
      • 方式1 通过解析中央目录区来解压
      • 方式2 通过读取本地文件头来解压
      • 两种解压方式对比
  • golang解压zip包
    • 官方archive/zip
    • 第三方包github.com/yeka/zip
    • 自己实现解压加密的zip包

Zip文件详解

ZIP 文件格式是一种常用的压缩和归档格式,用于将多个文件和目录打包到一个单独的文件中,同时对其内容进行压缩以减少文件大小。ZIP 文件格式的设计旨在支持多种压缩算法、加密和数据完整性校验。以下是 ZIP 文件格式的主要特性和常用算法:

ZIP 文件格式主要特性

  1. 文件头

    • 每个文件都有一个本地文件头和一个中央目录文件头。文件头包含文件名、压缩方法、时间戳、CRC-32 校验和、压缩前后的大小等信息。
  2. 数据描述符

    • 可选的后缀结构,包含文件的 CRC-32 校验和、压缩大小和未压缩大小。
  3. 中央目录

    • ZIP 文件的末尾包含一个中央目录记录,列出了 ZIP 文件中的所有文件和目录的文件头信息,用于快速定位和访问。
  4. 结束记录

    • ZIP 文件末尾的“中央目录结束记录”标识了中央目录的结束,包含中央目录的偏移量和大小等信息。

一个简单的 ZIP 文件可以包含多个文件的本地文件头、压缩数据、中央目录和结束记录。解压工具通过读取中央目录找到各个文件的偏移量和大小,然后根据这些信息读取和解压文件数据。

以下是一个 ZIP 文件结构的简化示例:

[本地文件头1] [文件数据1] [本地文件头2] [文件数据2] ... [中央目录] [中央目录结束记录]

ZIP 文件格式因其广泛的支持和高效的压缩性能,广泛应用于文件归档和传输。DEFLATE 算法是其最常用的压缩算法,提供了良好的平衡点。

常用算法

  1. 压缩算法

    • DEFLATE:这是 ZIP 文件中最常用的压缩算法,由 Phil Katz 发明。它结合了 LZ77 算法和 Huffman 编码,提供了良好的压缩比和解压速度。
    • STORE:不进行任何压缩,仅用于存储数据。适用于已经被压缩过的数据,如 JPEG 图像或 MP3 音频文件。
    • 其他压缩方法:ZIP 规范还支持其他压缩方法,如 BZIP2、LZMA 和 PPMd,但这些方法在实际使用中较为少见。
  2. 加密算法

    • 传统 ZIP 加密:早期的 ZIP 文件使用一种相对简单的对称加密方法,但这种方法的安全性较弱。
    • AES 加密:一些现代的 ZIP 工具支持使用高级加密标准 (AES) 进行加密,提供了更强的安全性。
  3. 校验算法

    • CRC-32:用于每个文件的数据完整性校验。每个文件都有一个 CRC-32 校验和,用于检测数据传输或存储过程中的错误。

Zip格式结构图总览

在这里插入图片描述

Zip文件结构详解

zip格式压缩包主要由三大部分组成:数据区、中央目录记录区(也有叫核心目录记录)、中央目录记录尾部区。

数据区

数据区是由一系列本地文件记录组成,本地文件记录主要是记录了压缩前后文件的元数据以及存放压缩后的文件,组成部分也分为三大部分:本地文件头文件数据文件描述

本地文件头

在这里插入图片描述

local file header signature     4 bytes  (0x04034b50)
version needed to extract       2 bytes
general purpose bit flag        2 bytes
compression method              2 bytes
last mod file time              2 bytes
last mod file date              2 bytes
crc-32                          4 bytes
compressed size                 4 bytes
uncompressed size               4 bytes
file name length                2 bytes
extra field length              2 bytesfile name (variable size)
extra field (variable size)

本地文件头主要是记录了压缩文件的元数据:

  1. loca file header signature:0~3,4个字节,用来存放本地文件头标识,一般为固定值0x04034b50,用于解压时候,读取判断文件头的开始;

  2. version needed to extract:4~5,2个字节,记录解压缩文件所需的最低支持的ZIP规范版本,apk压缩版本默认是20, 即Deflate压缩方式。该字段值=解压所需的最低ZIP规范版本*10,比如,最低支持的ZIP规范版本是2.0,那么该字段的值就是20。每个版本定义如下。

    当前最低功能版本定义如下:(压缩包记录的解压版本都是需要版本*10,比如:2.0 * 10 = 201.0 – 默认值
    1.1 – 文件是卷标
    2.0 – 文件是一个文件夹(目录)
    2.0 – 使用 Deflate 压缩来压缩文件
    2.0 – 使用传统的 PKWARE 加密对文件进行加密
    2.1 – 使用 Deflate64™ 压缩文件
    2.5 – 使用 PKWARE DCL Implode 压缩文件
    2.7 – 文件是补丁数据集
    4.5 – 文件使用 ZIP64 格式扩展
    4.6 – 使用 BZIP2 压缩文件压缩
    5.0 – 文件使用 DES 加密
    5.0 – 文件使用 3DES 加密
    5.0 – 使用原始 RC2 加密对文件进行加密
    5.0 – 使用 RC4 加密对文件进行加密
    5.1 – 文件使用 AES 加密进行加密
    5.1 – 使用更正的 RC2 加密对文件进行加密
    5.2 – 使用更正的 RC2-64 加密对文件进行加密
    6.1 – 使用非 OAEP 密钥包装对文件进行加密
    6.2 – 中央目录加密
    
  3. genaral purpose bit flag:6~7,2个字节,记录通用标志位,第0位为1时(即二进制:00000000 00000001),表示文件被加密,解压时候需要解密;第3位为1时候(即二进制:00000000 00000100),表示有数据描述部分,本地文件头中的 CRC-32、压缩大小和未压缩大小字段都被设置为0(虽然zip规范是这么定义,但是发现有些压缩包即使声明有数据描述部分,但是本地文件头的CRC-32、压缩大小和未压缩大小依然还是设置为真实值) , 正确的值被放在紧跟在压缩数据之后的数据描述部分,apk的通用标志位默认传0即可,也有传2048、2056,目前第15位是PKWARE保留位。

  4. compression method:8~9,2个字节,记录压缩包所用到的压缩方式,apk默认Deflate压缩,传8即可, 要是传0 ,则是不压缩,各种压缩方式对应数值如下:

    0 – The file is stored (no compression)
    1 – The file is Shrunk
    2 – The file is Reduced with compression factor 1
    3 – The file is Reduced with compression factor 2
    4 – The file is Reduced with compression factor 3
    5 – The file is Reduced with compression factor 4
    6 – The file is Imploded
    7 – Reserved for Tokenizing compression algorithm
    8 – The file is Deflated
    9 – Enhanced Deflating using Deflate64™
    10 – PKWARE Data Compression Library Imploding
    11 – Reserved by PKWARE
    12 – File is compressed using BZIP2 algorithm
    
  5. last mod file time:10~11,2个字节,记录文件最后修改时间,是MS-DOS格式编码的时间
    在这里插入图片描述

  6. last mod date time:12~13,2个字节,记录文件最后修改日期,是MS-DOS格式编码的日期

  7. crc-32:14~17,4个字节,记录文件未压缩时的CRC-32校验码

  8. compressed size:18~21,4个字节,记录文件压缩后的大小

  9. uncompressed size:22~25,4个字节,记录文件未压缩的大小

  10. file name length:26~27,2个字节,记录文件名的长度(假设文件名长度为n)

  11. extra field length:28~29,2个字节,记录扩展区的长度(假设扩展区长度为m)

  12. file name:30~30+n,n个字节,记录文件名

  13. extral field:30+n~30+n+m,m个字节,记录扩展数据

文件数据

文件数据紧跟在本地文件头之后,一般是压缩后的文件数据或压缩方式选择不压缩时候,用来存储未压缩文件数据。

文件描述

文件描述符仅在通用位标志的第 3 位被设置为1时才存在。 它是字节对齐的,紧跟在文件数据的最后一个字节之后。当且仅当无法在 .ZIP 文件中查找时才使用此描述符,例如:当输出 的.ZIP 文件是标准输出或不可查找设备时使用文件描述,换句话说,正常情况下都不需要使用。

在这里插入图片描述
( 数据描述符标识不一定有,因为一开始规范是没有的,后面才加上去的)

中央目录记录区(核心目录记录区 )

中心目录区的结构如下。

[file header 1]
.
.
. 
[file header n]
[digital signature]
central file header signature   4 bytes  (0x02014b50)
version made by                 2 bytes
version needed to extract       2 bytes
general purpose bit flag        2 bytes
compression method              2 bytes
last mod file time              2 bytes
last mod file date              2 bytes
crc-32                          4 bytes
compressed size                 4 bytes
uncompressed size               4 bytes
file name length                2 bytes
extra field length              2 bytes
file comment length             2 bytes
disk number start               2 bytes
internal file attributes        2 bytes
external file attributes        4 bytes
relative offset of local header 4 bytesfile name (variable size)
extra field (variable size)
file comment (variable size)

中央目录记录区是有一系列中央目录记录所组成,一条中央目录记录对应数据区中的一个压缩文件记录,中央目录记录由以下部分构成:(中央目录区通常由多个文件头(file header)组成,每一个被压缩的文件都有一个对应的file header(注意,这里不是local file header),用于标识和定位该文件在ZIP文件中的位置。这个文件头和本地文件头类似,记录了被压缩文件的元数据信息,包括文件原始大小,压缩之后的大小,文件注释等。)

在这里插入图片描述

  1. central file header signature:0~3,4个字节,记录核心目录文件头标识,固定值:0x02014b50,用于解压时候,查找判断是否是中央目录的开始位置
  2. version made by:4~5,2个字节,记录压缩所用的版本,同数据区本地文件头的解压所需版本,apk设置20
  3. 6~7:2个字节,记录解压所需的最小版本,同数据区本地文件头的解压所需版本,apk设置20
  4. 8~9:2个字节,通用位标记,同数据区本地文件头的通用位标记
  5. 压缩方法、文件最后修改时间、文件最后修改日期、CRC-32校验码、压缩后大小、未压缩大小、文件名长度、扩展区长度,这几个字段的含义都等同于数据区本地文件头对应字段的含义
  6. file comment length:32~33,2个字节,记录文件注释的长度
  7. disk number start:34~35,2个字节,记录文件开始位置的磁盘编号,一般传0即可
  8. 36~41:内部文件属性、外部文件属性,一般也是传0即可
  9. 42~45:4个字节,记录数据区本地文件头相对于压缩包开始位置的偏移量

数据签名(digital signature):

header signature                4 bytes  (0x05054b50)
size of data                    2 bytes
signature data (variable size)
  • header signature:数字签名起始标识,固定值为0x05054b50。
  • size of data:数字签名数据大小。
  • signature data :签名数据

中央目录记录尾部区

中央目录记录尾部主要作用是用来定位中央目录记录区的开始位置,同时记录压缩包的注释内容:
在这里插入图片描述

end of central dir signature    4 bytes  (0x06054b50)
number of this disk             2 bytes
number of the disk with the
start of the central directory  2 bytes
total number of entries in the
central directory on this disk  2 bytes
total number of entries in
the central directory           2 bytes
size of the central directory   4 bytes
offset of start of central
directory with respect to
the starting disk number        4 bytes
.ZIP file comment length        2 bytes
.ZIP file comment       (variable size)
  1. end of central dir signature:0~3,4个字节,中央目录记录尾部开头标记,固定值:0x06054b50,用于解压时,查找判断中央目录尾部的起始位置
  2. number of this disk:4~5,2个字节,记录中央目录记录尾部区所在磁盘编号
  3. number of the disk with the start of the central directory:6~7,2个字节,记录中央目录开始位置所在的磁盘编号
  4. total number of entries in the central directory on this disk:8~9,2个字节,该磁盘上所记录的核心目录数量
  5. total number of entries in the central directory:10~11,2个字节,zip压缩包中的文件总数
  6. size of the central directory:12~15,4个字节,整个中央目录的大小(以字节为单位)
  7. offset of start of central directory with respect to the starting disk number:16~19,4个字节,中央目录开始位置相对位移
  8. ZIP file comment length:20~21,2个字节,注释内容的长度(假设长度为n)
  9. ZIP file comment:22~22+n,n个字节,注释内容

中央目录结束标识是ZIP文件解压的入口。通过读取中央目录结束标识,解压缩软件可以快速地找到中央目录,并据此解析整个ZIP文件的结构和内容。通过里面的中央核心目录区的大小可以找到对应的中央目录模块,然后根据中央目录文件头中的本地文件头偏移(relative offset of local header)可以寻址到对应的文件,并进行解压。

每个压缩文件都必须且仅有一个中央目录结束标识。如果ZIP文件损坏或结构不正确,可能会导致中央目录结束标识丢失或损坏,从而使得解压缩软件无法正确读取和解析ZIP文件。

压缩包解压过程

方式1 通过解析中央目录区来解压

通过ZIP文件的结构我们发现,ZIP文件的中央目录区保存了所有的文件信息。所以,可以通过中央目录区拿到所有的文件信息并进行解压,步骤如下所示。

在这里插入图片描述

  1. 首先在 ZIP 文件末尾通过中央目录结束标识 (0x06054b50)找到中央目录结束标识数据块。
  2. 通过中央目录结束标识中的中央目录区开始位置偏移找到中央目录区数据块。
  3. 根据中央目录区的File Header中的 local file header的偏移量找到对应的local file header。
  4. 根据 local file header找到对应的file data
  5. 解密 file data(如果需要);
  6. 解压 file data;

方式2 通过读取本地文件头来解压

根据 ZIP 文件格式标准可知,除了 中央目录区, 本地文件头中也包含了每个文件的相关信息。因此,可以基于本地文件头去解压文件数据,其解压流程就可以变为:

  1. 从头开始,通过本地文件头标识搜索对应的 local file header
  2. 读取 local file header并找到file data;
  3. 解密 file data(如果需要);
  4. 解压 file data;

两种解压方式对比

通过两种解压方式可以明显看出,两种解压方式适用的场景不同。

方式1适用场景:

  • 适用于在解压文件已经存在于磁盘上,并且需要解压压缩包中所有的文件。

方式2适用场景:

  • 当文件不在磁盘上,比如从网络接收的数据,想边接收边解压;

  • 需要顺序解压ZIP文件前面的一小部分文件,可以使用这种方式,因为方式1读中央目录区会带来额外的耗时;

  • ZIP文件中的中央目录区遭到损坏;

golang解压zip包

官方archive/zip

golang zip包的解压有官方的zip包(archive/zip),但是官方给的zip解压包代码只有解压不带密码的zip包。

下面给出解压操作的封装:

func Unzip(src, dst string) error {// zip.NewReader() 适合从stream中读取字节序列zf, err := zip.OpenReader(src)if err != nil {return err}defer zf.Close()for _, file := range zf.File {// fmt.Println(file.Name)path := filepath.Join(dst, file.Name)// 如果是目录则创建目录if file.FileInfo().IsDir() {if err = os.MkdirAll(path, 0o644); err != nil {return err}continue}f, err := os.Create(path)if err != nil {return err}reader, err := file.Open()if err != nil {return err}_, err = io.Copy(f, reader)if err != nil {return err}_ = f.Close()_ = reader.Close()}return nil
}func main() {log.Println(Unzip("Go.zip", "./static"))
}

在这里插入图片描述
通常情况下,目录权限设为 0755,文件权限设为 0644 更为合适。

在 Unix 和 Unix-like 操作系统(如 Linux)中,文件权限使用三位八进制数来表示,每一位分别表示所有者(Owner)、组(Group)和其他人(Others)的权限。每一位的权限可以是 1(执行权限),2(写入权限),4(读取权限)的组合。它们的组合值表示特定的权限设置。

权限位的含义:

  • 1:执行权限 (Execute)
  • 2:写入权限 (Write)
  • 4:读取权限 (Read)

这些权限可以相加来组合权限。例如:

  • 7 (4+2+1):读取、写入和执行权限 (Read + Write + Execute)
  • 6 (4+2):读取和写入权限 (Read + Write)
  • 5 (4+1):读取和执行权限 (Read + Execute)
  • 4:读取权限 (Read)
  • 3 (2+1):写入和执行权限 (Write + Execute)
  • 2:写入权限 (Write)
  • 1:执行权限 (Execute)
  • 0:无权限 (No permissions)

权限设置示例:

权限设置通常以三位八进制数表示,例如 0755,每一位代表不同用户类别的权限:

  • 第一位(Owner 权限):7,表示所有者有读取、写入和执行权限。
  • 第二位(Group 权限):5,表示组用户有读取和执行权限。
  • 第三位(Others 权限):5,表示其他用户有读取和执行权限。
rwxr-xr-x
  • r 代表读取权限
  • w 代表写入权限
  • x 代表执行权限
  • - 代表没有该权限

示例解释:

  • 0755:

    • 所有者(Owner)权限:7 (rwx) 读取、写入和执行
    • 组(Group)权限:5 (r-x) 读取和执行
    • 其他人(Others)权限:5 (r-x) 读取和执行
  • 0644:

    • 所有者(Owner)权限:6 (rw-) 读取和写入
    • 组(Group)权限:4 (r–) 读取
    • 其他人(Others)权限:4 (r–) 读取

第三方包github.com/yeka/zip

使用go get github.com/yeka/zip安装后,代码都不需要改变,只是导入的zip包替换为第三方即可:
在这里插入图片描述
在这里插入图片描述
https://github.com/yeka/zip,关于这个库里面的整个代码是在官方的zip库的基础上做了一些修改,并且这个库里面的代码抛弃了注册的功能,直接把解密代码写在了open文件里,废弃了一个很好用的功能。

在这里插入图片描述
在这里插入图片描述

自己实现解压加密的zip包

golang官方的zip代码库,https://golang.google.cn/pkg/archive/zip/#pkg-examples有两个注册接口,一个是压缩和一个是解压的:

在这里插入图片描述
在这里插入图片描述
官方注册压缩器方法示例:

package mainimport ("archive/zip""bytes""compress/flate""io"
)func main() {// Override the default Deflate compressor with a higher compression level.// Create a buffer to write our archive to.buf := new(bytes.Buffer)// Create a new zip archive.w := zip.NewWriter(buf)// Register a custom Deflate compressor.w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {return flate.NewWriter(out, flate.BestCompression)})// Proceed to add files to w.
}

类比这个案例可以写出:

func main() {zf, _ := zip.OpenReader("")zf.RegisterDecompressor(zip.Deflate, func(r io.Reader) io.ReadCloser {// TODO:解密算法实现return flate.NewReader(r)})
}

接下来实现解密算法(官方链接:https://support.pkware.com/pkzip/appnote):

在这里插入图片描述
zip标准文件:https://pkwaredownloads.blob.core.windows.net/pem/APPNOTE.txt,温馨提示:不要机器翻译成中文。

在这里插入图片描述

ZIP 文件加密算法通常使用一种简单的流加密方法,称为 ZipCrypto。解密过程包括初始化三个 32 位整数 key0, key1, 和 key2,并根据密码和加密的字节数据更新这些值。加密和解密使用同样的逻辑,只是加密是将明文转换为密文,而解密是将密文转换为明文。

type ZipCrypto struct {password []byteKeys     [3]uint32
}func NewZipCrypto(passphrase []byte) *ZipCrypto {z := &ZipCrypto{}z.password = passphrasez.init()return z
}func (z *ZipCrypto) init() {z.Keys[0] = 0x12345678z.Keys[1] = 0x23456789z.Keys[2] = 0x34567890for i := 0; i < len(z.password); i++ {z.updateKeys(z.password[i])}
}func (z *ZipCrypto) updateKeys(byteValue byte) {z.Keys[0] = crc32update(z.Keys[0], byteValue)z.Keys[1] += z.Keys[0] & 0xffz.Keys[1] = z.Keys[1]*134775813 + 1z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24))
}func (z *ZipCrypto) magicByte() byte {var t uint32 = z.Keys[2] | 2return byte((t * (t ^ 1)) >> 8)
}func (z *ZipCrypto) Encrypt(data []byte) []byte {length := len(data)chiper := make([]byte, length)for i := 0; i < length; i++ {v := data[i]chiper[i] = v ^ z.magicByte()z.updateKeys(v)}return chiper
}func (z *ZipCrypto) Decrypt(chiper []byte) []byte {length := len(chiper)plain := make([]byte, length)for i, c := range chiper {v := c ^ z.magicByte()z.updateKeys(v)plain[i] = v}return plain
}func crc32update(pCrc32 uint32, bval byte) uint32 {return crc32.IEEETable[(pCrc32^uint32(bval))&0xff] ^ (pCrc32 >> 8)
}

实现加密解密算法后写一个小例子来测试下:

func main() {password := "generalzy"zc := NewZipCrypto(password)// 示例数据data := []byte("Hello, ZipCrypto!")fmt.Printf("Original: %s\n", data)// 加密数据encrypted := zc.Encrypt(data)fmt.Printf("Encrypted: %x\n", encrypted)// 初始化解密器zcDecrypt := NewZipCrypto(password)// 解密数据decrypted := zcDecrypt.Decrypt(encrypted)fmt.Printf("Decrypted: %s\n", decrypted)
}

在这里插入图片描述
要利用 zip 包提供的注册方法来注册解密函数,可以使用 RegisterDecompressor 方法。首先,需要扩展之前的 ZipCrypto 实现,使其能够解密压缩数据流。然后,可以将这个解密流注册到 zip 包中。

最后给出整体代码:

package mainimport ("archive/zip""bytes""compress/flate""fmt""hash/crc32""io""io/ioutil""os""path/filepath"
)type ZipCrypto struct {password []byteKeys     [3]uint32
}func NewZipCrypto(passphrase []byte) *ZipCrypto {z := &ZipCrypto{}z.password = passphrasez.init()return z
}func (z *ZipCrypto) init() {z.Keys[0] = 0x12345678z.Keys[1] = 0x23456789z.Keys[2] = 0x34567890for i := 0; i < len(z.password); i++ {z.updateKeys(z.password[i])}
}func (z *ZipCrypto) updateKeys(byteValue byte) {z.Keys[0] = crc32update(z.Keys[0], byteValue)z.Keys[1] += z.Keys[0] & 0xffz.Keys[1] = z.Keys[1]*134775813 + 1z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24))
}func (z *ZipCrypto) magicByte() byte {var t uint32 = z.Keys[2] | 2return byte((t * (t ^ 1)) >> 8)
}func (z *ZipCrypto) Encrypt(data []byte) []byte {length := len(data)chiper := make([]byte, length)for i := 0; i < length; i++ {v := data[i]chiper[i] = v ^ z.magicByte()z.updateKeys(v)}return chiper
}func (z *ZipCrypto) Decrypt(chiper []byte) []byte {length := len(chiper)plain := make([]byte, length)for i, c := range chiper {v := c ^ z.magicByte()z.updateKeys(v)plain[i] = v}return plain
}func crc32update(pCrc32 uint32, bval byte) uint32 {return crc32.IEEETable[(pCrc32^uint32(bval))&0xff] ^ (pCrc32 >> 8)
}func ZipCryptoDecryptor(r *io.SectionReader, password []byte) (*io.SectionReader, error) {z := NewZipCrypto(password)b := make([]byte, r.Size())r.Read(b)m := z.Decrypt(b)return io.NewSectionReader(bytes.NewReader(m), 12, int64(len(m))), nil
}type unzip struct {offset int64fp     *os.Filename   string
}func (uz *unzip) init() (err error) {uz.fp, err = os.Open(uz.name)return err
}func (uz *unzip) close() {if uz.fp != nil {uz.fp.Close()}
}func (uz *unzip) Size() int64 {if uz.fp == nil {if err := uz.init(); err != nil {return -1}}fi, err := uz.fp.Stat()if err != nil {return -1}return fi.Size() - uz.offset
}func (uz *unzip) ReadAt(p []byte, off int64) (int, error) {if uz.fp == nil {if err := uz.init(); err != nil {return 0, err}}return uz.fp.ReadAt(p, off+uz.offset)
}// DeCompressZip 解压zip包
func DeCompressZip(zipFile, dest, passwd string, offset int64) error {uz := &unzip{offset: offset, name: zipFile}defer uz.close()zr, err := zip.NewReader(uz, uz.Size())if err != nil {return err}if passwd != "" {// Register a custom Deflate compressor.zr.RegisterDecompressor(zip.Deflate, func(r io.Reader) io.ReadCloser {rs := r.(*io.SectionReader)r, _ = ZipCryptoDecryptor(rs, []byte(passwd))return flate.NewReader(r)})zr.RegisterDecompressor(zip.Store, func(r io.Reader) io.ReadCloser {rs := r.(*io.SectionReader)r, _ = ZipCryptoDecryptor(rs, []byte(passwd))return ioutil.NopCloser(r)})}for _, f := range zr.File {fpath := filepath.Join(dest, f.Name)if f.FileInfo().IsDir() {os.MkdirAll(fpath, os.ModePerm)continue}if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {return err}inFile, err := f.Open()if err != nil {return err}outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())if err != nil {inFile.Close()return err}_, err = io.Copy(outFile, inFile)inFile.Close()outFile.Close()if err != nil {return err}}return nil
}func main() {err := DeCompressZip("Go.zip", "./tmp", "123456", 0)if err != nil {fmt.Println(err)}return
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/382833.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mq基础入门

前言 黑马商城导入了mq依赖 但是没有改service发消息 因为下单业务一直有问题 所以先没改 作业时间不够也没处理 1.异步调用 就是所谓的发短信 可以不用立即恢复 比如下单业务 下了单更新信息 就相当于发个消息通知一下 不用立即更改 但是支付就比较重要 不需要因为故障导…

谷粒商城实战笔记-48~49-商品服务-API-三级分类-查询-树形展示三级分类数据-前端优化

文章目录 一&#xff0c;48-商品服务-API-三级分类-查询-树形展示三级分类数据1&#xff0c;创建商品服务命名空间2&#xff0c;商品服务增加配置3&#xff0c;网关增加商品服务的路由配置4&#xff0c;前端树形展示5&#xff0c;测试 二&#xff0c;49-商品服务-API-三级分类-…

Prometheus配置alertmanager告警

1、拉取镜像并运行 1、配置docker镜像源 [rootlocalhost ~]# vim /etc/docker/daemon.json {"registry-mirrors": ["https://dfaad.mirror.aliyuncs.com"] } [rootlocalhost ~]# systemctl daemon-reload [rootlocalhost ~]# systemctl restart docker2、…

VTK源码分析:Type System

作为一款开源跨平台的数据可视化代码库&#xff0c;VTK以其清晰的流水线工作方式、丰富的后处理算法、异种渲染/交互方式&#xff0c;而被众多CAx软件选作后处理实施方案。而异种渲染/交互方式的实现&#xff0c;主要是倚重于VTK的类型系统&#xff0c;因此&#xff0c;有必要对…

从安装Node到TypeScript到VsCode的配置教程

从安装Node到TypeScript到VsCode的配置教程 1.下载Node安装包&#xff0c; 链接 2.双击安装包&#xff0c;选择安装路径&#xff0c;如下&#xff1a; 3.一直点击下一步&#xff0c;直至安装结束即可&#xff1a; 这个时候&#xff0c;node会默认配置好环境变量&#xff0c;并且…

抖音客户端一面

C | 字节抖音客户端一面 Http握手过程 1. 客户端问候(Client Hello) 客户端向服务器发送一个“问候”消息&#xff0c;其中包含客户端支持的SSL/TLS版本、加密算法、压缩方法以及一个随机数。 version 版本号,https也有版本号哦TLS 1.0、TLS 1.1、TLS 1.2等等 random 随机数…

(11)Python引领金融前沿:投资组合优化实战案例

1. 前言 本篇文章为 Python 对金融的投资组合优化的示例。投资组合优化是从一组可用的投资组合中选择最佳投资组合的过程&#xff0c;目的是最大限度地提高回报和降低风险。 投资组合优化是从一组可用的投资组合中选择最佳投资组合的过程&#xff0c;目的是最大限度地提高回报…

JUnit 单元测试

JUnit 测试是程序员测试&#xff0c;就是白盒测试&#xff0c;可以让程序员知道被测试的软件如何 &#xff08;How&#xff09;完成功能和完成什么样&#xff08;What&#xff09;的功能。 下载junit-4.12和hamcrest-core-1.3依赖包 相关链接 junit-4.12&#xff1a;Central …

【Qt】窗口

文章目录 QMainWindow菜单栏工具栏状态栏浮动窗口对话框自定义对话框Qt内置对话框QMessageBox QMainWindow Qt中的主窗口以QMainWindow表示&#xff0c;其总体结构如下&#xff1a; 菜单栏 菜单栏MenuBar&#xff0c;可包含多个菜单Menu&#xff0c;每个菜单也可以包含多个菜…

Godot游戏制作 03世界构建1.0版

在game场景&#xff0c;删除StaticBody2D节点&#xff0c;添加TileMap节点 添加TileSet图块集 添加TileSet源 拖动图片到图块&#xff0c;自动创建图块 使用橡皮擦擦除。取消橡皮擦后按住Shift创建大型图块。 进入选择模式&#xff0c;TileMap选择绘制&#xff0c;选中图块后在…

Java——————接口(interface) <详解>

1.1 接口的概念 在现实生活中&#xff0c;接口的例子比比皆是&#xff0c;比如&#xff1a;笔记本电脑上的USB接口&#xff0c;电源插座等。 电脑的USB口上&#xff0c;可以插&#xff1a;U盘、鼠标、键盘...所有符合USB协议的设备 电源插座插孔上&#xff0c;可以插&#xff…

若依Vue前后端分离版如何部署(windows)(超详细)

一、项目环境准备 下面是项目所需要准备的环境 Node.js redis 1、Node.js下载 下面进入官网可以下载Node.js — 在任何地方运行 JavaScript (nodejs.org)https://nodejs.org/zh-cn 下载完成安装后&#xff0c;需要配置环境变量&#xff0c;首先复制以下nodejs的安…

springboot系列十一:Thymeleaf

文章目录 官方文档基本介绍Thymeleaf机制说明Thymeleaf语法表达式运算符th属性迭代条件运算使用Thymeleaf th属性需要注意点 Thymeleaf综合案例需求说明思路分析代码实现 作业布置 官方文档 在线文档: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html 离线…

css黑色二级下拉导航菜单

黑色二级下拉导航菜单https://www.bootstrapmb.com/item/14816 body { font-family: Arial, sans-serif; margin: 0; padding: 0; }nav { background-color: #000; /* 导航背景色为黑色 */ }.menu { list-style-type: none; margin: 0; padding: 0; overflow: hidden; }.menu l…

ARP欺骗——华为ensp

首先&#xff0c;搭建好网络拓扑。网络设备包含客户端Client1和服务端Server1&#xff0c;交换机 以及 云。 图中的 Client和Server 配置IP地址&#xff0c;要和 vm8 在相同的网段。故设置客户端ip为192.168.11.10 &#xff0c;服务端ip为&#xff1a;192.168.11.20&#xff0…

uni-app AppStore Connect上传拒绝汇总

1.Guideline 2.3.3 - Performance - Accurate Metadata 问题是图片不对&#xff0c;最好是自己截图&#xff0c;然后用香蕉云编 上传图片合成图片 2.Guideline 5.1.2 - Legal - Privacy - Data Use and Sharing 解决办法&#xff1a;在uniapp manifest.json找到 APP常用其他…

【Ubuntu】Ubuntu系统镜像

清华镜像源 Index of /ubuntu-releases/ | 清华大学开源软件镜像站 | Tsinghua Open Source MirrorIndex of /ubuntu-releases/ | 清华大学开源软件镜像站&#xff0c;致力于为国内和校内用户提供高质量的开源软件镜像、Linux 镜像源服务&#xff0c;帮助用户更方便地获取开源软…

Llama 3.1要来啦?!测试性能战胜GPT-4o

哎呀&#xff0c;Meta声称将于今晚发布的Llama 3.1&#xff0c;数小时前就在Hugging Face上泄露出来了&#xff1f;泄露的人很有可能是Meta员工&#xff1f; 还是先来看泄露出来的llama3.1吧。新的Llama 3.1模型包括8B、70B、405B三个版本。 而经过网友测试&#xff0c;该base…

Langchain核心模块与实战[8]:RAG检索增强生成[loader机制、文本切割方法、长文本信息处理技巧]

Langchain核心模块与实战[8]:RAG(Retrieval Augmented Generation,检索增强生成) RAG(Retrieval-Augmented Generation)技术是一种结合检索和生成功能的自然语言处理(NLP)技术。该技术通过从大型外部数据库中检索与输入问题相关的信息,来辅助生成模型回答问题。其核心…

gitee的怎么上传项目

前提 1.先下载Git Bash (如果没有下载的宝子们下载连接如下: 链接: link ) 项目上传到Gitee步骤 1.在Gitee上建立远程仓库 2.填写相关信息 3.进入本地你想要上传的文件目录下&#xff0c;右键单击空白处&#xff0c;点击Git Bash Here 4.配置你的用户名和邮箱 git con…