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/

https://github.com/yeka/zip

 


posted @   cs_wu  阅读(4358)  评论(0编辑  收藏  举报
编辑推荐:
· 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 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示