├── .gitignore
├── README.MD
├── go.mod
├── go.sum
├── main.go
└── pic
├── image1.png
├── image2.png
├── image3.png
├── image4.png
├── image5.png
├── image6.png
└── image7.png
/.gitignore:
--------------------------------------------------------------------------------
1 | weread_decrypt.exe
2 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 |
微信读书缓存文件解密
2 |
3 | #### 本项目仅供技术研究使用, 请勿用于商业用途!
4 |
5 | #### 本项目仅供技术研究使用, 请勿用于商业用途!
6 |
7 | #### 本项目仅供技术研究使用, 请勿用于商业用途!
8 |
9 | ## Introduction
10 |
11 | 将微信读书下载到本地的缓存进行解密,文件保存在/data/user/0/com.tencent.weread/databases/{一串数字(vid)}/books/{bookId}中,需要手机的 ROOT 权限
12 |
13 | ## Usage
14 |
15 | 1. Clone this repo
16 | 2. go mod tidy
17 | 3. go build
18 | 4. ./weread_decrypt.exe [vid] [flag] ["Your .res file folder"] 网文 flag 为 0 写出 txt 文件 实体书 flag 为 1 写出 html 文件
19 | 5. Check output folder
20 |
21 | ## Example
22 |
23 | 
24 | 
25 | 
26 | 
27 | 出版书
28 | 
29 | 
30 | 
31 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module weread_decrypt
2 |
3 | go 1.20
4 |
5 | require github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb
6 |
7 | require golang.org/x/crypto v0.11.0 // indirect
8 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb h1:OJYP70YMddlmGq//EPLj8Vw2uJXmrA+cGSPhXTDpn2E=
2 | github.com/yeka/zip v0.0.0-20180914125537-d046722c6feb/go.mod h1:9BnoKCcgJ/+SLhfAXj15352hTOuVmG5Gzo8xNRINfqI=
3 | golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
4 | golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
5 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "crypto/aes"
6 | "crypto/cipher"
7 | "encoding/json"
8 | "fmt"
9 | "io/ioutil"
10 | "log"
11 | "os"
12 | "path"
13 | "strconv"
14 |
15 | "github.com/yeka/zip"
16 | )
17 |
18 | type ChapterInfo struct {
19 | BookID string `json:"bookId"`
20 | Synckey int `json:"synckey"`
21 | Chapters []Chapters `json:"chapters"`
22 | }
23 | type Chapters struct {
24 | ChapterUID int `json:"chapterUid"`
25 | ChapterIdx int `json:"chapterIdx"`
26 | UpdateTime int `json:"updateTime"`
27 | Title string `json:"title"`
28 | WordCount int `json:"wordCount"`
29 | Price int `json:"price"`
30 | IsMPChapter int `json:"isMPChapter"`
31 | Paid int `json:"paid"`
32 | }
33 |
34 | func main() {
35 | vid, err := strconv.Atoi(os.Args[1])
36 | if err != nil {
37 | fmt.Println("vid参数错误")
38 | return
39 | }
40 | flag, err := strconv.Atoi(os.Args[1])
41 | if err != nil {
42 | fmt.Println("flag参数错误")
43 | return
44 | }
45 | dirPath := os.Args[3]
46 |
47 | files, err := ioutil.ReadDir(dirPath)
48 | if err != nil {
49 | fmt.Println(err)
50 | return
51 | }
52 | for _, f := range files {
53 | if f.IsDir() {
54 | continue
55 | }
56 | suffix := path.Ext(f.Name())
57 | if suffix == ".res" {
58 | if flag == 0 {
59 | decryptTxtFile(vid, dirPath+"/"+f.Name())
60 | } else {
61 | decryptHtmlFile(vid, dirPath+"/"+f.Name())
62 | }
63 | }
64 |
65 | }
66 |
67 | }
68 | func getKeyAndIV(vid int) ([]byte, []byte) {
69 |
70 | remapArr := [10]byte{0x2d, 0x50, 0x56, 0xd7, 0x72, 0x53, 0xbf, 0x22, 0xfb, 0x20}
71 | vidLen := len(strconv.Itoa(vid))
72 | vidRemap := make([]byte, vidLen)
73 |
74 | for i := 0; i < vidLen; i++ {
75 | vidRemap[i] = remapArr[strconv.Itoa(vid)[i]-'0']
76 | }
77 | key := make([]byte, 36)
78 | for i := 0; i < 36; i++ {
79 | key[i] = vidRemap[i%vidLen]
80 | }
81 | iv := make([]byte, 16)
82 | for i := 0; i < 16; i++ {
83 | iv[i] = key[i+7]
84 | }
85 | key = key[0:16]
86 | return key, iv
87 | }
88 | func decryptTxtFile(vid int, filePath string) {
89 | f, err := os.Open(filePath)
90 | if err != nil {
91 | fmt.Println(err)
92 | return
93 | }
94 | defer f.Close()
95 | readInt(f)
96 | chapterLen := readInt(f)
97 | chapterUid := make([]int, chapterLen)
98 | for i := 0; i < chapterLen; i++ {
99 | chapterUid[i] = readInt(f)
100 | }
101 | fmt.Println("章节Uid", chapterUid)
102 | readInt(f)
103 | encryptData := make([]byte, 16)
104 | f.Read(encryptData)
105 | key, iv := getKeyAndIV(vid)
106 | block, err := aes.NewCipher(key)
107 | if err != nil {
108 | fmt.Println(err)
109 | return
110 | }
111 | blockMode := cipher.NewCBCDecrypter(block, iv)
112 | decryptedData := make([]byte, 16)
113 | blockMode.CryptBlocks(decryptedData, encryptData)
114 | pwdStr := ""
115 | for i := 0; i < len(decryptedData); i++ {
116 | if decryptedData[i] < 32 || decryptedData[i] > 126 {
117 | continue
118 | }
119 | pwdStr += string(decryptedData[i])
120 | }
121 | f.Read(make([]byte, 8))
122 | zipFile := make([]byte, 0)
123 | for {
124 | b := make([]byte, 1024)
125 | n, err := f.Read(b)
126 | if err != nil {
127 | break
128 | }
129 | zipFile = append(zipFile, b[:n]...)
130 | }
131 | err = os.MkdirAll("./output", 0777)
132 | if err != nil {
133 | fmt.Println(err)
134 | return
135 | }
136 |
137 | zipReader, err := zip.NewReader(bytes.NewReader(zipFile), int64(len(zipFile)))
138 | if err != nil {
139 | fmt.Println(err)
140 | return
141 | }
142 | var chapterInfo ChapterInfo
143 |
144 | for i, f := range zipReader.File {
145 | if f.IsEncrypted() {
146 | f.SetPassword(pwdStr)
147 | }
148 | r, err := f.Open()
149 | if err != nil {
150 | log.Fatal(err)
151 | }
152 | if f.Name == "info.txt" {
153 | b, err := ioutil.ReadAll(r)
154 | if err != nil {
155 | fmt.Println(err)
156 | return
157 | }
158 | json.Unmarshal(b, &chapterInfo)
159 |
160 | continue
161 | }
162 | fileName := fmt.Sprintf("%d%s.txt", chapterInfo.Chapters[i-1].ChapterIdx-1, chapterInfo.Chapters[i-1].Title)
163 | _, err = os.Stat("./output/" + fileName)
164 | if err == nil {
165 | continue
166 | }
167 | file, err := os.Create("./output/" + fileName)
168 | if err != nil {
169 | fmt.Println(err)
170 | return
171 | }
172 | b, err := ioutil.ReadAll(r)
173 | if err != nil {
174 | fmt.Println(err)
175 | return
176 | }
177 | file.Write(b)
178 | file.Close()
179 | }
180 | }
181 | func decryptHtmlFile(vid int, filePath string) {
182 | f, err := os.Open(filePath)
183 | if err != nil {
184 | fmt.Println(err)
185 | return
186 | }
187 | defer f.Close()
188 | readInt(f)
189 | chapterLen := readInt(f)
190 | chapterUid := make([]int, chapterLen)
191 | for i := 0; i < chapterLen; i++ {
192 | chapterUid[i] = readInt(f)
193 | }
194 | fmt.Println("章节Uid", chapterUid)
195 | readInt(f)
196 | encryptData := make([]byte, 16)
197 | f.Read(encryptData)
198 | key, iv := getKeyAndIV(vid)
199 | block, err := aes.NewCipher(key)
200 | if err != nil {
201 | fmt.Println(err)
202 | return
203 | }
204 | blockMode := cipher.NewCBCDecrypter(block, iv)
205 | decryptedData := make([]byte, 16)
206 | blockMode.CryptBlocks(decryptedData, encryptData)
207 | pwdStr := ""
208 | for i := 0; i < len(decryptedData); i++ {
209 | if decryptedData[i] < 32 || decryptedData[i] > 126 {
210 | continue
211 | }
212 | pwdStr += string(decryptedData[i])
213 | }
214 | fmt.Println("密码", pwdStr)
215 | f.Read(make([]byte, 8))
216 | zipFile := make([]byte, 0)
217 | for {
218 | b := make([]byte, 1024)
219 | n, err := f.Read(b)
220 | if err != nil {
221 | break
222 | }
223 | zipFile = append(zipFile, b[:n]...)
224 | }
225 | err = os.MkdirAll("./output", 0777)
226 | if err != nil {
227 | fmt.Println(err)
228 | return
229 | }
230 |
231 | zipReader, err := zip.NewReader(bytes.NewReader(zipFile), int64(len(zipFile)))
232 | if err != nil {
233 | fmt.Println(err)
234 | return
235 | }
236 |
237 | for _, f := range zipReader.File {
238 | if f.IsEncrypted() {
239 | f.SetPassword(pwdStr)
240 | }
241 | r, err := f.Open()
242 | if err != nil {
243 | log.Fatal(err)
244 | }
245 | //解压文件
246 | fileName := "./output/" + f.Name
247 | _, err = os.Stat(fileName)
248 | if err == nil {
249 | continue
250 | }
251 | //创建目录
252 | dir := path.Dir(fileName)
253 | _, err = os.Stat(dir)
254 | if err != nil {
255 | err = os.MkdirAll(dir, 0777)
256 | if err != nil {
257 | fmt.Println(err)
258 | return
259 | }
260 | }
261 |
262 | file, err := os.Create(fileName)
263 | if err != nil {
264 | fmt.Println(err)
265 | return
266 | }
267 | b, err := ioutil.ReadAll(r)
268 | if err != nil {
269 | fmt.Println(err)
270 | return
271 | }
272 | file.Write(b)
273 | file.Close()
274 |
275 | }
276 | }
277 | func readInt(f *os.File) int {
278 | b := make([]byte, 4)
279 | f.Read(b)
280 | return int(b[0])<<24 + int(b[1])<<16 + int(b[2])<<8 + int(b[3])
281 | }
282 |
--------------------------------------------------------------------------------
/pic/image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image1.png
--------------------------------------------------------------------------------
/pic/image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image2.png
--------------------------------------------------------------------------------
/pic/image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image3.png
--------------------------------------------------------------------------------
/pic/image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image4.png
--------------------------------------------------------------------------------
/pic/image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image5.png
--------------------------------------------------------------------------------
/pic/image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image6.png
--------------------------------------------------------------------------------
/pic/image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/blakehan/weread_decrypt/82d8d2325019670b3b3c5a3b601564c3705f4d02/pic/image7.png
--------------------------------------------------------------------------------