golang解壓帶密碼的zip包的方法詳解
Zip文件詳解
ZIP 文件格式是一種常用的壓縮和歸檔格式,用于將多個文件和目錄打包到一個單獨的文件中,同時對其內(nèi)容進行壓縮以減少文件大小。ZIP 文件格式的設計旨在支持多種壓縮算法、加密和數(shù)據(jù)完整性校驗。以下是 ZIP 文件格式的主要特性和常用算法:
ZIP 文件格式主要特性
文件頭:
每個文件都有一個本地文件頭和一個中央目錄文件頭。文件頭包含文件名、壓縮方法、時間戳、CRC-32 校驗和、壓縮前后的大小等信息。
數(shù)據(jù)描述符:
可選的后綴結(jié)構,包含文件的 CRC-32 校驗和、壓縮大小和未壓縮大小。
中央目錄:
ZIP 文件的末尾包含一個中央目錄記錄,列出了 ZIP 文件中的所有文件和目錄的文件頭信息,用于快速定位和訪問。
結(jié)束記錄:
ZIP 文件末尾的“中央目錄結(jié)束記錄”標識了中央目錄的結(jié)束,包含中央目錄的偏移量和大小等信息。
一個簡單的 ZIP 文件可以包含多個文件的本地文件頭、壓縮數(shù)據(jù)、中央目錄和結(jié)束記錄。解壓工具通過讀取中央目錄找到各個文件的偏移量和大小,然后根據(jù)這些信息讀取和解壓文件數(shù)據(jù)。
以下是一個 ZIP 文件結(jié)構的簡化示例:
[本地文件頭1] [文件數(shù)據(jù)1] [本地文件頭2] [文件數(shù)據(jù)2] ... [中央目錄] [中央目錄結(jié)束記錄]
ZIP 文件格式因其廣泛的支持和高效的壓縮性能,廣泛應用于文件歸檔和傳輸。DEFLATE 算法是其最常用的壓縮算法,提供了良好的平衡點。
常用算法
壓縮算法:
- DEFLATE:這是 ZIP 文件中最常用的壓縮算法,由 Phil Katz 發(fā)明。它結(jié)合了 LZ77 算法和 Huffman 編碼,提供了良好的壓縮比和解壓速度。
- STORE:不進行任何壓縮,僅用于存儲數(shù)據(jù)。適用于已經(jīng)被壓縮過的數(shù)據(jù),如 JPEG 圖像或 MP3 音頻文件。
- 其他壓縮方法:ZIP 規(guī)范還支持其他壓縮方法,如 BZIP2、LZMA 和 PPMd,但這些方法在實際使用中較為少見。
加密算法:
- 傳統(tǒng) ZIP 加密:早期的 ZIP 文件使用一種相對簡單的對稱加密方法,但這種方法的安全性較弱。
- AES 加密:一些現(xiàn)代的 ZIP 工具支持使用高級加密標準 (AES) 進行加密,提供了更強的安全性。
校驗算法:
- CRC-32:用于每個文件的數(shù)據(jù)完整性校驗。每個文件都有一個 CRC-32 校驗和,用于檢測數(shù)據(jù)傳輸或存儲過程中的錯誤。
Zip格式結(jié)構圖總覽

Zip文件結(jié)構詳解
zip格式壓縮包主要由三大部分組成:數(shù)據(jù)區(qū)、中央目錄記錄區(qū)(也有叫核心目錄記錄)、中央目錄記錄尾部區(qū)。
數(shù)據(jù)區(qū)
數(shù)據(jù)區(qū)是由一系列本地文件記錄組成,本地文件記錄主要是記錄了壓縮前后文件的元數(shù)據(jù)以及存放壓縮后的文件,組成部分也分為三大部分:本地文件頭、文件數(shù)據(jù)、文件描述
本地文件頭

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 bytes file name (variable size) extra field (variable size)
本地文件頭主要是記錄了壓縮文件的元數(shù)據(jù):
- loca file header signature:0~3,4個字節(jié),用來存放本地文件頭標識,一般為固定值
0x04034b50,用于解壓時候,讀取判斷文件頭的開始; - version needed to extract:4~5,2個字節(jié),記錄解壓縮文件所需的最低支持的ZIP規(guī)范版本,apk壓縮版本默認是20, 即Deflate壓縮方式。該字段值=解壓所需的最低ZIP規(guī)范版本*10,比如,最低支持的ZIP規(guī)范版本是2.0,那么該字段的值就是20。每個版本定義如下。
當前最低功能版本定義如下:(壓縮包記錄的解壓版本都是需要版本*10,比如:2.0 * 10 = 20) 1.0 – 默認值 1.1 – 文件是卷標 2.0 – 文件是一個文件夾(目錄) 2.0 – 使用 Deflate 壓縮來壓縮文件 2.0 – 使用傳統(tǒng)的 PKWARE 加密對文件進行加密 2.1 – 使用 Deflate64? 壓縮文件 2.5 – 使用 PKWARE DCL Implode 壓縮文件 2.7 – 文件是補丁數(shù)據(jù)集 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 – 中央目錄加密
- genaral purpose bit flag:6~7,2個字節(jié),記錄通用標志位,第0位為
1時(即二進制:00000000 00000001),表示文件被加密,解壓時候需要解密;第3位為1時候(即二進制:00000000 00000100),表示有數(shù)據(jù)描述部分,本地文件頭中的 CRC-32、壓縮大小和未壓縮大小字段都被設置為0(雖然zip規(guī)范是這么定義,但是發(fā)現(xiàn)有些壓縮包即使聲明有數(shù)據(jù)描述部分,但是本地文件頭的CRC-32、壓縮大小和未壓縮大小依然還是設置為真實值) , 正確的值被放在緊跟在壓縮數(shù)據(jù)之后的數(shù)據(jù)描述部分,apk的通用標志位默認傳0即可,也有傳2048、2056,目前第15位是PKWARE保留位。 - compression method:8~9,2個字節(jié),記錄壓縮包所用到的壓縮方式,apk默認Deflate壓縮,傳8即可, 要是傳0 ,則是不壓縮,各種壓縮方式對應數(shù)值如下:
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
- last mod file time:10~11,2個字節(jié),記錄文件最后修改時間,是MS-DOS格式編碼的時間

- last mod date time:12~13,2個字節(jié),記錄文件最后修改日期,是MS-DOS格式編碼的日期
- crc-32:14~17,4個字節(jié),記錄文件未壓縮時的CRC-32校驗碼
- compressed size:18~21,4個字節(jié),記錄文件壓縮后的大小
- uncompressed size:22~25,4個字節(jié),記錄文件未壓縮的大小
- file name length:26~27,2個字節(jié),記錄文件名的長度(假設文件名長度為n)
- extra field length:28~29,2個字節(jié),記錄擴展區(qū)的長度(假設擴展區(qū)長度為m)
- file name:30~30+n,n個字節(jié),記錄文件名
- extral field:30+n~30+n+m,m個字節(jié),記錄擴展數(shù)據(jù)
文件數(shù)據(jù)
文件數(shù)據(jù)緊跟在本地文件頭之后,一般是壓縮后的文件數(shù)據(jù)或壓縮方式選擇不壓縮時候,用來存儲未壓縮文件數(shù)據(jù)。
文件描述
文件描述符僅在通用位標志的第 3 位被設置為1時才存在。 它是字節(jié)對齊的,緊跟在文件數(shù)據(jù)的最后一個字節(jié)之后。當且僅當無法在 .ZIP 文件中查找時才使用此描述符,例如:當輸出 的.ZIP 文件是標準輸出或不可查找設備時使用文件描述,換句話說,正常情況下都不需要使用。

( 數(shù)據(jù)描述符標識不一定有,因為一開始規(guī)范是沒有的,后面才加上去的)
中央目錄記錄區(qū)(核心目錄記錄區(qū) )
中心目錄區(qū)的結(jié)構如下。
[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 bytes file name (variable size) extra field (variable size) file comment (variable size)
中央目錄記錄區(qū)是有一系列中央目錄記錄所組成,一條中央目錄記錄對應數(shù)據(jù)區(qū)中的一個壓縮文件記錄,中央目錄記錄由以下部分構成:(中央目錄區(qū)通常由多個文件頭(file header)組成,每一個被壓縮的文件都有一個對應的file header(注意,這里不是local file header),用于標識和定位該文件在ZIP文件中的位置。這個文件頭和本地文件頭類似,記錄了被壓縮文件的元數(shù)據(jù)信息,包括文件原始大小,壓縮之后的大小,文件注釋等。)

- central file header signature:0~3,4個字節(jié),記錄核心目錄文件頭標識,固定值:
0x02014b50,用于解壓時候,查找判斷是否是中央目錄的開始位置 - version made by:4~5,2個字節(jié),記錄壓縮所用的版本,同數(shù)據(jù)區(qū)本地文件頭的解壓所需版本,apk設置20
- 6~7:2個字節(jié),記錄解壓所需的最小版本,同數(shù)據(jù)區(qū)本地文件頭的解壓所需版本,apk設置20
- 8~9:2個字節(jié),通用位標記,同數(shù)據(jù)區(qū)本地文件頭的通用位標記
- 壓縮方法、文件最后修改時間、文件最后修改日期、CRC-32校驗碼、壓縮后大小、未壓縮大小、文件名長度、擴展區(qū)長度,這幾個字段的含義都等同于數(shù)據(jù)區(qū)本地文件頭對應字段的含義
- file comment length:32~33,2個字節(jié),記錄文件注釋的長度
- disk number start:34~35,2個字節(jié),記錄文件開始位置的磁盤編號,一般傳0即可
- 36~41:內(nèi)部文件屬性、外部文件屬性,一般也是傳0即可
- 42~45:4個字節(jié),記錄數(shù)據(jù)區(qū)本地文件頭相對于壓縮包開始位置的偏移量
數(shù)據(jù)簽名(digital signature):
header signature 4 bytes (0x05054b50) size of data 2 bytes signature data (variable size)
- header signature:數(shù)字簽名起始標識,固定值為0x05054b50。
- size of data:數(shù)字簽名數(shù)據(jù)大小。
- signature data :簽名數(shù)據(jù)
中央目錄記錄尾部區(qū)
中央目錄記錄尾部主要作用是用來定位中央目錄記錄區(qū)的開始位置,同時記錄壓縮包的注釋內(nèi)容:

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)
- end of central dir signature:0~3,4個字節(jié),中央目錄記錄尾部開頭標記,固定值:
0x06054b50,用于解壓時,查找判斷中央目錄尾部的起始位置 - number of this disk:4~5,2個字節(jié),記錄中央目錄記錄尾部區(qū)所在磁盤編號
- number of the disk with the start of the central directory:6~7,2個字節(jié),記錄中央目錄開始位置所在的磁盤編號
- total number of entries in the central directory on this disk:8~9,2個字節(jié),該磁盤上所記錄的核心目錄數(shù)量
- total number of entries in the central directory:10~11,2個字節(jié),zip壓縮包中的文件總數(shù)
- size of the central directory:12~15,4個字節(jié),整個中央目錄的大?。ㄒ宰止?jié)為單位)
- offset of start of central directory with respect to the starting disk number:16~19,4個字節(jié),中央目錄開始位置相對位移
- ZIP file comment length:20~21,2個字節(jié),注釋內(nèi)容的長度(假設長度為n)
- ZIP file comment:22~22+n,n個字節(jié),注釋內(nèi)容
中央目錄結(jié)束標識是ZIP文件解壓的入口。通過讀取中央目錄結(jié)束標識,解壓縮軟件可以快速地找到中央目錄,并據(jù)此解析整個ZIP文件的結(jié)構和內(nèi)容。通過里面的中央核心目錄區(qū)的大小可以找到對應的中央目錄模塊,然后根據(jù)中央目錄文件頭中的本地文件頭偏移(relative offset of local header)可以尋址到對應的文件,并進行解壓。
每個壓縮文件都必須且僅有一個中央目錄結(jié)束標識。如果ZIP文件損壞或結(jié)構不正確,可能會導致中央目錄結(jié)束標識丟失或損壞,從而使得解壓縮軟件無法正確讀取和解析ZIP文件。
壓縮包解壓過程
方式1 通過解析中央目錄區(qū)來解壓
通過ZIP文件的結(jié)構我們發(fā)現(xiàn),ZIP文件的中央目錄區(qū)保存了所有的文件信息。所以,可以通過中央目錄區(qū)拿到所有的文件信息并進行解壓,步驟如下所示。

- 首先在 ZIP 文件末尾通過中央目錄結(jié)束標識 (0x06054b50)找到中央目錄結(jié)束標識數(shù)據(jù)塊。
- 通過中央目錄結(jié)束標識中的中央目錄區(qū)開始位置偏移找到中央目錄區(qū)數(shù)據(jù)塊。
- 根據(jù)中央目錄區(qū)的File Header中的 local file header的偏移量找到對應的local file header。
- 根據(jù) local file header找到對應的file data
- 解密 file data(如果需要);
- 解壓 file data;
方式2 通過讀取本地文件頭來解壓
根據(jù) ZIP 文件格式標準可知,除了 中央目錄區(qū), 本地文件頭中也包含了每個文件的相關信息。因此,可以基于本地文件頭去解壓文件數(shù)據(jù),其解壓流程就可以變?yōu)椋?/p>
- 從頭開始,通過本地文件頭標識搜索對應的 local file header
- 讀取 local file header并找到file data;
- 解密 file data(如果需要);
- 解壓 file data;
兩種解壓方式對比
通過兩種解壓方式可以明顯看出,兩種解壓方式適用的場景不同。
方式1適用場景:
- 適用于在解壓文件已經(jīng)存在于磁盤上,并且需要解壓壓縮包中所有的文件。
方式2適用場景:
- 當文件不在磁盤上,比如從網(wǎng)絡接收的數(shù)據(jù),想邊接收邊解壓;
- 需要順序解壓ZIP文件前面的一小部分文件,可以使用這種方式,因為方式1讀中央目錄區(qū)會帶來額外的耗時;
- ZIP文件中的中央目錄區(qū)遭到損壞;
golang解壓zip包
官方archive/zip
golang zip包的解壓有官方的zip包(archive/zip),但是官方給的zip解壓包代碼只有解壓不帶密碼的zip包。
下面給出解壓操作的封裝:
func Unzip(src, dst string) error {
// zip.NewReader() 適合從stream中讀取字節(jié)序列
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)
// 如果是目錄則創(chuàng)建目錄
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 操作系統(tǒng)(如 Linux)中,文件權限使用三位八進制數(shù)來表示,每一位分別表示所有者(Owner)、組(Group)和其他人(Others)的權限。每一位的權限可以是 1(執(zhí)行權限),2(寫入權限),4(讀取權限)的組合。它們的組合值表示特定的權限設置。
權限位的含義:
- 1:執(zhí)行權限 (Execute)
- 2:寫入權限 (Write)
- 4:讀取權限 (Read)
這些權限可以相加來組合權限。例如:
- 7 (4+2+1):讀取、寫入和執(zhí)行權限 (Read + Write + Execute)
- 6 (4+2):讀取和寫入權限 (Read + Write)
- 5 (4+1):讀取和執(zhí)行權限 (Read + Execute)
- 4:讀取權限 (Read)
- 3 (2+1):寫入和執(zhí)行權限 (Write + Execute)
- 2:寫入權限 (Write)
- 1:執(zhí)行權限 (Execute)
- 0:無權限 (No permissions)
權限設置示例:
權限設置通常以三位八進制數(shù)表示,例如 0755,每一位代表不同用戶類別的權限:
- 第一位(Owner 權限):
7,表示所有者有讀取、寫入和執(zhí)行權限。 - 第二位(Group 權限):
5,表示組用戶有讀取和執(zhí)行權限。 - 第三位(Others 權限):
5,表示其他用戶有讀取和執(zhí)行權限。
rwxr-xr-x
r代表讀取權限w代表寫入權限x代表執(zhí)行權限-代表沒有該權限
示例解釋:
0755:
- 所有者(Owner)權限:
7(rwx) 讀取、寫入和執(zhí)行 - 組(Group)權限:
5(r-x) 讀取和執(zhí)行 - 其他人(Others)權限:
5(r-x) 讀取和執(zhí)行
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文件里,廢棄了一個很好用的功能。


自己實現(xiàn)解壓加密的zip包
golang官方的zip代碼庫,https://golang.google.cn/pkg/archive/zip/#pkg-examples有兩個注冊接口,一個是壓縮和一個是解壓的:


官方注冊壓縮器方法示例:
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.
}
類比這個案例可以寫出:
func main() {
zf, _ := zip.OpenReader("")
zf.RegisterDecompressor(zip.Deflate, func(r io.Reader) io.ReadCloser {
// TODO:解密算法實現(xiàn)
return flate.NewReader(r)
})
}
接下來實現(xiàn)解密算法(官方鏈接:https://support.pkware.com/pkzip/appnote):

zip標準文件:https://pkwaredownloads.blob.core.windows.net/pem/APPNOTE.txt,溫馨提示:不要機器翻譯成中文。

ZIP 文件加密算法通常使用一種簡單的流加密方法,稱為 ZipCrypto。解密過程包括初始化三個 32 位整數(shù) key0, key1, 和 key2,并根據(jù)密碼和加密的字節(jié)數(shù)據(jù)更新這些值。加密和解密使用同樣的邏輯,只是加密是將明文轉(zhuǎn)換為密文,而解密是將密文轉(zhuǎn)換為明文。
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)
}
實現(xiàn)加密解密算法后寫一個小例子來測試下:
func main() {
password := "generalzy"
zc := NewZipCrypto(password)
// 示例數(shù)據(jù)
data := []byte("Hello, ZipCrypto!")
fmt.Printf("Original: %s\n", data)
// 加密數(shù)據(jù)
encrypted := zc.Encrypt(data)
fmt.Printf("Encrypted: %x\n", encrypted)
// 初始化解密器
zcDecrypt := NewZipCrypto(password)
// 解密數(shù)據(jù)
decrypted := zcDecrypt.Decrypt(encrypted)
fmt.Printf("Decrypted: %s\n", decrypted)
}

要利用 zip 包提供的注冊方法來注冊解密函數(shù),可以使用 RegisterDecompressor 方法。首先,需要擴展之前的 ZipCrypto 實現(xiàn),使其能夠解密壓縮數(shù)據(jù)流。然后,可以將這個解密流注冊到 zip 包中。
最后給出整體代碼:
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)
}
// 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
}
以上就是golang解壓帶密碼的zip包的方法詳解的詳細內(nèi)容,更多關于golang解壓zip包的資料請關注腳本之家其它相關文章!
相關文章
golang?gorm的Callbacks事務回滾對象操作示例
這篇文章主要為大家介紹了golang?gorm的Callbacks事務回滾對象操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
Golang自定義開發(fā)Prometheus?exporter詳解
Exporter是基于Prometheus實施的監(jiān)控系統(tǒng)中重要的組成部分,承擔數(shù)據(jù)指標的采集工作,這篇文章主要為大家介紹了如何自定義編寫開發(fā)?Prometheus?exporter,感興趣的可以了解一下2023-06-06

