golang 解压带密码的zip包
golang 解压带密码的zip包,同时支持指定文件头偏移量加载zip包。下面首先给出完整的代码,后面再对代码实现过程的思考和原理做详细解释。
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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 | package main import ( "archive/zip" "bytes" "compress/flate" "fmt" "hash/crc32" "io" "io/ioutil" "os" "path/filepath" ) type ZipCrypto struct { password []byte Keys [3]uint32 } func NewZipCrypto(passphrase []byte) *ZipCrypto { z := &ZipCrypto{} z.password = passphrase z.init() return z } func (z *ZipCrypto) init() { z.Keys[0] = 0x12345678 z.Keys[1] = 0x23456789 z.Keys[2] = 0x34567890 for 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] & 0xff z.Keys[1] = z.Keys[1]*134775813 + 1 z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24)) } func (z *ZipCrypto) magicByte() byte { var t uint32 = z.Keys[2] | 2 return 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 int64 fp *os.File name 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) } func isInclude(includes []string, fname string) bool { if includes == nil { return true } for i := 0; i < len(includes); i++ { if includes[i] == fname { return true } } return false } //DeCompressZip 解压zip包 func DeCompressZip(zipFile, dest, passwd string, includes []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( "test.zip" , "./tmp" , "password" , nil, 0) if err != nil { fmt.Println(err) } return } |
下面给出思考过程
golang zip包的解压有官方的zip包(archive/zip),但是官方给的zip解压包代码只有解压不带密码的zip包,如果我们要解压带密码的zip就做不了了。这时候我们不要急着去寻找第三方的库去使用,我们先从设计者的角度思考,这是一个官方的代码库,zip带密码解压和压缩是很常见的功能,官方的代码应该是要支持的,如果没有封装好的接口那就看一下是否有预留一些注册接口让我们自己去实现解压的代码。
看一下golang官方的zip代码库,https://golang.google.cn/pkg/archive/zip/#pkg-examples,,我们很容易看到有两个注册接口,一个是压缩和一个是解压的
既然有注册接口,那接下来我们需要做的就是研究怎么使用这个接口,还有怎么写解压算法。
怎么使用注册解压的接口官方没有给出例子,不过使用注册压缩算法,两个接口差不过是可以参考一下:
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 | package main import ( "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. } |
通过官方的例子还有源码解压和压缩的源码我们可以写出这样子的解压代码:
1 2 3 4 5 | zr, err := zip.NewReader(uz, uz.Size()) <br> if err != nil { <br> return err <br>}<br>zr.RegisterDecompressor(zip.Deflate, func (r io.Reader) io.ReadCloser { rs := r.(*io.SectionReader) r, _ = ZipCryptoDecryptor(rs, []byte(passwd)) //这里是解压算法核心代码,对rs数据流的代码进行解密 return flate.NewReader(r) }) |
编写完注册解压代码,现在需要实现解密算法,标准的zip解密算法网上有公开的算法,这种没啥好思考的,按照标准的zip加解密算法来就可以了,这里我参考的是一个开源库的代码:
https://github.com/yeka/zip,关于这个库我这里讲一下:我们可以直接用里的代码,我们需要的接口里面都已经实现好了,里面的整个代码是在官方的zip库的基础上做了一些修改,改的比较多,我们不需要这么多,项目中也不想引入第三方库就只引用了他里面写的zip解密代码。另外还有一点我不是很喜欢的是,这个库里面的代码抛弃了注册的功能,直接把解密代码写在了open文件里,废弃了一个很好用的功能。
加解密的代码如下:
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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | type ZipCrypto struct { password []byte Keys [3]uint32 } func NewZipCrypto(passphrase []byte) *ZipCrypto { z := &ZipCrypto{} z.password = passphrase z.init() return z } func (z *ZipCrypto) init() { z.Keys[0] = 0x12345678 z.Keys[1] = 0x23456789 z.Keys[2] = 0x34567890 for 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] & 0xff z.Keys[1] = z.Keys[1]*134775813 + 1 z.Keys[2] = crc32update(z.Keys[2], (byte)(z.Keys[1]>>24)) } func (z *ZipCrypto) magicByte() byte { var t uint32 = z.Keys[2] | 2 return 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 } |
这样子基本就实现了对有密码的zip做解压。
不过有时候我们可能会在zip包头部加上一些信息,这样子的 zip直接用zip.NewReader打开 是会被识别未 非法zip的,这时候我们读取文件时就需要设置一个头部偏移量。设置头部偏移量这里有一个坑,为我们不能直接用Seek去设置,这样子在zip.NewReader里是没有效果的,我们需要再实现一个ReadAt函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | type unzip struct { offset int64 fp *os.File name string } 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) } |
最后用的地方是:uz里有ReadAt的方法
1 2 3 4 5 6 7 | uz := &unzip{offset: offset, name: zipFile} defer uz.close() zr, err := zip.NewReader(uz, uz.Size()) if err != nil { return err } |
以上均实现后就可以实现一个支持头部偏移和带密码解压zip包的代码
参考资料:
https://golang.google.cn/pkg/archive/zip/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架