├── .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 | ![pic](pic/image1.png) 24 | ![pic](pic/image2.png) 25 | ![pic](pic/image3.png) 26 | ![pic](pic/image4.png) 27 | 出版书 28 | ![pic](pic/image5.png) 29 | ![pic](pic/image6.png) 30 | ![pic](pic/image7.png) 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 --------------------------------------------------------------------------------