x
This commit is contained in:
138
internal/wechat/decrypt/common/common.go
Normal file
138
internal/wechat/decrypt/common/common.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
KeySize = 32
|
||||
SaltSize = 16
|
||||
AESBlockSize = 16
|
||||
SQLiteHeader = "SQLite format 3\x00"
|
||||
IVSize = 16
|
||||
)
|
||||
|
||||
type DBFile struct {
|
||||
Path string
|
||||
Salt []byte
|
||||
TotalPages int64
|
||||
FirstPage []byte
|
||||
}
|
||||
|
||||
func OpenDBFile(dbPath string, pageSize int) (*DBFile, error) {
|
||||
fp, err := os.Open(dbPath)
|
||||
if err != nil {
|
||||
return nil, errors.DecryptOpenFileFailed(dbPath, err)
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
fileInfo, err := fp.Stat()
|
||||
if err != nil {
|
||||
return nil, errors.WeChatDecryptFailed(err)
|
||||
}
|
||||
|
||||
fileSize := fileInfo.Size()
|
||||
totalPages := fileSize / int64(pageSize)
|
||||
if fileSize%int64(pageSize) > 0 {
|
||||
totalPages++
|
||||
}
|
||||
|
||||
buffer := make([]byte, pageSize)
|
||||
n, err := io.ReadFull(fp, buffer)
|
||||
if err != nil {
|
||||
return nil, errors.DecryptReadFileFailed(dbPath, err)
|
||||
}
|
||||
if n != pageSize {
|
||||
return nil, errors.DecryptIncompleteRead(fmt.Errorf("read %d bytes, expected %d", n, pageSize))
|
||||
}
|
||||
|
||||
if bytes.Equal(buffer[:len(SQLiteHeader)-1], []byte(SQLiteHeader[:len(SQLiteHeader)-1])) {
|
||||
return nil, errors.ErrAlreadyDecrypted
|
||||
}
|
||||
|
||||
return &DBFile{
|
||||
Path: dbPath,
|
||||
Salt: buffer[:SaltSize],
|
||||
FirstPage: buffer,
|
||||
TotalPages: totalPages,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func XorBytes(a []byte, b byte) []byte {
|
||||
result := make([]byte, len(a))
|
||||
for i := range a {
|
||||
result[i] = a[i] ^ b
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ValidateKey(page1 []byte, key []byte, salt []byte, hashFunc func() hash.Hash, hmacSize int, reserve int, pageSize int, deriveKeys func([]byte, []byte) ([]byte, []byte)) bool {
|
||||
if len(key) != KeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
_, macKey := deriveKeys(key, salt)
|
||||
|
||||
mac := hmac.New(hashFunc, macKey)
|
||||
dataEnd := pageSize - reserve + IVSize
|
||||
mac.Write(page1[SaltSize:dataEnd])
|
||||
|
||||
pageNoBytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(pageNoBytes, 1)
|
||||
mac.Write(pageNoBytes)
|
||||
|
||||
calculatedMAC := mac.Sum(nil)
|
||||
storedMAC := page1[dataEnd : dataEnd+hmacSize]
|
||||
|
||||
return hmac.Equal(calculatedMAC, storedMAC)
|
||||
}
|
||||
|
||||
func DecryptPage(pageBuf []byte, encKey []byte, macKey []byte, pageNum int64, hashFunc func() hash.Hash, hmacSize int, reserve int, pageSize int) ([]byte, error) {
|
||||
offset := 0
|
||||
if pageNum == 0 {
|
||||
offset = SaltSize
|
||||
}
|
||||
|
||||
mac := hmac.New(hashFunc, macKey)
|
||||
mac.Write(pageBuf[offset : pageSize-reserve+IVSize])
|
||||
|
||||
pageNoBytes := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(pageNoBytes, uint32(pageNum+1))
|
||||
mac.Write(pageNoBytes)
|
||||
|
||||
hashMac := mac.Sum(nil)
|
||||
|
||||
hashMacStartOffset := pageSize - reserve + IVSize
|
||||
hashMacEndOffset := hashMacStartOffset + hmacSize
|
||||
|
||||
if !bytes.Equal(hashMac, pageBuf[hashMacStartOffset:hashMacEndOffset]) {
|
||||
return nil, errors.ErrDecryptHashVerificationFailed
|
||||
}
|
||||
|
||||
iv := pageBuf[pageSize-reserve : pageSize-reserve+IVSize]
|
||||
block, err := aes.NewCipher(encKey)
|
||||
if err != nil {
|
||||
return nil, errors.DecryptCreateCipherFailed(err)
|
||||
}
|
||||
|
||||
mode := cipher.NewCBCDecrypter(block, iv)
|
||||
|
||||
encrypted := make([]byte, pageSize-reserve-offset)
|
||||
copy(encrypted, pageBuf[offset:pageSize-reserve])
|
||||
|
||||
mode.CryptBlocks(encrypted, encrypted)
|
||||
|
||||
decryptedPage := append(encrypted, pageBuf[pageSize-reserve:pageSize]...)
|
||||
|
||||
return decryptedPage, nil
|
||||
}
|
||||
184
internal/wechat/decrypt/darwin/v3.go
Normal file
184
internal/wechat/decrypt/darwin/v3.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/common"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// 常量定义
|
||||
const (
|
||||
V3PageSize = 1024
|
||||
HmacSHA1Size = 20
|
||||
)
|
||||
|
||||
// V3Decryptor 实现 macOS V3 版本的解密器
|
||||
type V3Decryptor struct {
|
||||
// macOS V3 特定参数
|
||||
hmacSize int
|
||||
hashFunc func() hash.Hash
|
||||
reserve int
|
||||
pageSize int
|
||||
version string
|
||||
}
|
||||
|
||||
// NewV3Decryptor 创建 macOS V3 解密器
|
||||
func NewV3Decryptor() *V3Decryptor {
|
||||
hashFunc := sha1.New
|
||||
hmacSize := HmacSHA1Size
|
||||
reserve := common.IVSize + hmacSize
|
||||
if reserve%common.AESBlockSize != 0 {
|
||||
reserve = ((reserve / common.AESBlockSize) + 1) * common.AESBlockSize
|
||||
}
|
||||
|
||||
return &V3Decryptor{
|
||||
hmacSize: hmacSize,
|
||||
hashFunc: hashFunc,
|
||||
reserve: reserve,
|
||||
pageSize: V3PageSize,
|
||||
version: "macOS v3",
|
||||
}
|
||||
}
|
||||
|
||||
// deriveKeys 派生 MAC 密钥
|
||||
// 注意:macOS V3 版本直接使用提供的密钥作为加密密钥,不进行 PBKDF2 派生
|
||||
func (d *V3Decryptor) deriveKeys(key []byte, salt []byte) ([]byte, []byte) {
|
||||
// 对于 macOS V3,直接使用密钥作为加密密钥
|
||||
encKey := key
|
||||
|
||||
// 生成 MAC 密钥
|
||||
macSalt := common.XorBytes(salt, 0x3a)
|
||||
macKey := pbkdf2.Key(encKey, macSalt, 2, common.KeySize, d.hashFunc)
|
||||
|
||||
return encKey, macKey
|
||||
}
|
||||
|
||||
// Validate 验证密钥是否有效
|
||||
func (d *V3Decryptor) Validate(page1 []byte, key []byte) bool {
|
||||
if len(page1) < d.pageSize || len(key) != common.KeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
salt := page1[:common.SaltSize]
|
||||
return common.ValidateKey(page1, key, salt, d.hashFunc, d.hmacSize, d.reserve, d.pageSize, d.deriveKeys)
|
||||
}
|
||||
|
||||
// Decrypt 解密数据库
|
||||
func (d *V3Decryptor) Decrypt(ctx context.Context, dbfile string, hexKey string, output io.Writer) error {
|
||||
// 解码密钥
|
||||
key, err := hex.DecodeString(hexKey)
|
||||
if err != nil {
|
||||
return errors.DecryptDecodeKeyFailed(err)
|
||||
}
|
||||
|
||||
// 打开数据库文件并读取基本信息
|
||||
dbInfo, err := common.OpenDBFile(dbfile, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证密钥
|
||||
if !d.Validate(dbInfo.FirstPage, key) {
|
||||
return errors.ErrDecryptIncorrectKey
|
||||
}
|
||||
|
||||
// 计算密钥
|
||||
encKey, macKey := d.deriveKeys(key, dbInfo.Salt)
|
||||
|
||||
// 打开数据库文件
|
||||
dbFile, err := os.Open(dbfile)
|
||||
if err != nil {
|
||||
return errors.DecryptOpenFileFailed(dbfile, err)
|
||||
}
|
||||
defer dbFile.Close()
|
||||
|
||||
// 写入 SQLite 头
|
||||
_, err = output.Write([]byte(common.SQLiteHeader))
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
|
||||
// 处理每一页
|
||||
pageBuf := make([]byte, d.pageSize)
|
||||
|
||||
for curPage := int64(0); curPage < dbInfo.TotalPages; curPage++ {
|
||||
// 检查是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.DecryptOperationCanceled()
|
||||
default:
|
||||
// 继续处理
|
||||
}
|
||||
|
||||
// 读取一页
|
||||
n, err := io.ReadFull(dbFile, pageBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
// 处理最后一部分页面
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.DecryptReadFileFailed(dbfile, err)
|
||||
}
|
||||
|
||||
// 检查页面是否全为零
|
||||
allZeros := true
|
||||
for _, b := range pageBuf {
|
||||
if b != 0 {
|
||||
allZeros = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allZeros {
|
||||
// 写入零页面
|
||||
_, err = output.Write(pageBuf)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 解密页面
|
||||
decryptedData, err := common.DecryptPage(pageBuf, encKey, macKey, curPage, d.hashFunc, d.hmacSize, d.reserve, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入解密后的页面
|
||||
_, err = output.Write(decryptedData)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageSize 返回页面大小
|
||||
func (d *V3Decryptor) GetPageSize() int {
|
||||
return d.pageSize
|
||||
}
|
||||
|
||||
// GetReserve 返回保留字节数
|
||||
func (d *V3Decryptor) GetReserve() int {
|
||||
return d.reserve
|
||||
}
|
||||
|
||||
// GetHMACSize 返回HMAC大小
|
||||
func (d *V3Decryptor) GetHMACSize() int {
|
||||
return d.hmacSize
|
||||
}
|
||||
|
||||
// GetVersion 返回解密器版本
|
||||
func (d *V3Decryptor) GetVersion() string {
|
||||
return d.version
|
||||
}
|
||||
194
internal/wechat/decrypt/darwin/v4.go
Normal file
194
internal/wechat/decrypt/darwin/v4.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package darwin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/common"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// Darwin Version 4 same as WIndows Version 4
|
||||
|
||||
// V4 版本特定常量
|
||||
const (
|
||||
V4PageSize = 4096
|
||||
V4IterCount = 256000
|
||||
HmacSHA512Size = 64
|
||||
)
|
||||
|
||||
// V4Decryptor 实现Windows V4版本的解密器
|
||||
type V4Decryptor struct {
|
||||
// V4 特定参数
|
||||
iterCount int
|
||||
hmacSize int
|
||||
hashFunc func() hash.Hash
|
||||
reserve int
|
||||
pageSize int
|
||||
version string
|
||||
}
|
||||
|
||||
// NewV4Decryptor 创建Windows V4解密器
|
||||
func NewV4Decryptor() *V4Decryptor {
|
||||
hashFunc := sha512.New
|
||||
hmacSize := HmacSHA512Size
|
||||
reserve := common.IVSize + hmacSize
|
||||
if reserve%common.AESBlockSize != 0 {
|
||||
reserve = ((reserve / common.AESBlockSize) + 1) * common.AESBlockSize
|
||||
}
|
||||
|
||||
return &V4Decryptor{
|
||||
iterCount: V4IterCount,
|
||||
hmacSize: hmacSize,
|
||||
hashFunc: hashFunc,
|
||||
reserve: reserve,
|
||||
pageSize: V4PageSize,
|
||||
version: "macOS v4",
|
||||
}
|
||||
}
|
||||
|
||||
// deriveKeys 派生加密密钥和MAC密钥
|
||||
func (d *V4Decryptor) deriveKeys(key []byte, salt []byte) ([]byte, []byte) {
|
||||
// 生成加密密钥
|
||||
encKey := pbkdf2.Key(key, salt, d.iterCount, common.KeySize, d.hashFunc)
|
||||
|
||||
// 生成MAC密钥
|
||||
macSalt := common.XorBytes(salt, 0x3a)
|
||||
macKey := pbkdf2.Key(encKey, macSalt, 2, common.KeySize, d.hashFunc)
|
||||
|
||||
return encKey, macKey
|
||||
}
|
||||
|
||||
// Validate 验证密钥是否有效
|
||||
func (d *V4Decryptor) Validate(page1 []byte, key []byte) bool {
|
||||
if len(page1) < d.pageSize || len(key) != common.KeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
salt := page1[:common.SaltSize]
|
||||
return common.ValidateKey(page1, key, salt, d.hashFunc, d.hmacSize, d.reserve, d.pageSize, d.deriveKeys)
|
||||
}
|
||||
|
||||
// Decrypt 解密数据库
|
||||
func (d *V4Decryptor) Decrypt(ctx context.Context, dbfile string, hexKey string, output io.Writer) error {
|
||||
// 解码密钥
|
||||
key, err := hex.DecodeString(hexKey)
|
||||
if err != nil {
|
||||
return errors.DecryptDecodeKeyFailed(err)
|
||||
}
|
||||
|
||||
// 打开数据库文件并读取基本信息
|
||||
dbInfo, err := common.OpenDBFile(dbfile, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证密钥
|
||||
if !d.Validate(dbInfo.FirstPage, key) {
|
||||
return errors.ErrDecryptIncorrectKey
|
||||
}
|
||||
|
||||
// 计算密钥
|
||||
encKey, macKey := d.deriveKeys(key, dbInfo.Salt)
|
||||
|
||||
// 打开数据库文件
|
||||
dbFile, err := os.Open(dbfile)
|
||||
if err != nil {
|
||||
return errors.DecryptOpenFileFailed(dbfile, err)
|
||||
}
|
||||
defer dbFile.Close()
|
||||
|
||||
// 写入SQLite头
|
||||
_, err = output.Write([]byte(common.SQLiteHeader))
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
|
||||
// 处理每一页
|
||||
pageBuf := make([]byte, d.pageSize)
|
||||
|
||||
for curPage := int64(0); curPage < dbInfo.TotalPages; curPage++ {
|
||||
// 检查是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.DecryptOperationCanceled()
|
||||
default:
|
||||
// 继续处理
|
||||
}
|
||||
|
||||
// 读取一页
|
||||
n, err := io.ReadFull(dbFile, pageBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
// 处理最后一部分页面
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.DecryptReadFileFailed(dbfile, err)
|
||||
}
|
||||
|
||||
// 检查页面是否全为零
|
||||
allZeros := true
|
||||
for _, b := range pageBuf {
|
||||
if b != 0 {
|
||||
allZeros = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allZeros {
|
||||
// 写入零页面
|
||||
_, err = output.Write(pageBuf)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 解密页面
|
||||
decryptedData, err := common.DecryptPage(pageBuf, encKey, macKey, curPage, d.hashFunc, d.hmacSize, d.reserve, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入解密后的页面
|
||||
_, err = output.Write(decryptedData)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageSize 返回页面大小
|
||||
func (d *V4Decryptor) GetPageSize() int {
|
||||
return d.pageSize
|
||||
}
|
||||
|
||||
// GetReserve 返回保留字节数
|
||||
func (d *V4Decryptor) GetReserve() int {
|
||||
return d.reserve
|
||||
}
|
||||
|
||||
// GetHMACSize 返回HMAC大小
|
||||
func (d *V4Decryptor) GetHMACSize() int {
|
||||
return d.hmacSize
|
||||
}
|
||||
|
||||
// GetVersion 返回解密器版本
|
||||
func (d *V4Decryptor) GetVersion() string {
|
||||
return d.version
|
||||
}
|
||||
|
||||
// GetIterCount 返回迭代次数(Windows特有)
|
||||
func (d *V4Decryptor) GetIterCount() int {
|
||||
return d.iterCount
|
||||
}
|
||||
55
internal/wechat/decrypt/decryptor.go
Normal file
55
internal/wechat/decrypt/decryptor.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package decrypt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/darwin"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/windows"
|
||||
)
|
||||
|
||||
// 错误定义
|
||||
var (
|
||||
ErrInvalidVersion = fmt.Errorf("invalid version, must be 3 or 4")
|
||||
ErrUnsupportedPlatform = fmt.Errorf("unsupported platform")
|
||||
)
|
||||
|
||||
// Decryptor 定义数据库解密的接口
|
||||
type Decryptor interface {
|
||||
// Decrypt 解密数据库
|
||||
Decrypt(ctx context.Context, dbfile string, key string, output io.Writer) error
|
||||
|
||||
// Validate 验证密钥是否有效
|
||||
Validate(page1 []byte, key []byte) bool
|
||||
|
||||
// GetPageSize 返回页面大小
|
||||
GetPageSize() int
|
||||
|
||||
// GetReserve 返回保留字节数
|
||||
GetReserve() int
|
||||
|
||||
// GetHMACSize 返回HMAC大小
|
||||
GetHMACSize() int
|
||||
|
||||
// GetVersion 返回解密器版本
|
||||
GetVersion() string
|
||||
}
|
||||
|
||||
// NewDecryptor 创建一个新的解密器
|
||||
func NewDecryptor(platform string, version int) (Decryptor, error) {
|
||||
// 根据平台返回对应的实现
|
||||
switch {
|
||||
case platform == "windows" && version == 3:
|
||||
return windows.NewV3Decryptor(), nil
|
||||
case platform == "windows" && version == 4:
|
||||
return windows.NewV4Decryptor(), nil
|
||||
case platform == "darwin" && version == 3:
|
||||
return darwin.NewV3Decryptor(), nil
|
||||
case platform == "darwin" && version == 4:
|
||||
return darwin.NewV4Decryptor(), nil
|
||||
default:
|
||||
return nil, errors.PlatformUnsupported(platform, version)
|
||||
}
|
||||
}
|
||||
56
internal/wechat/decrypt/validator.go
Normal file
56
internal/wechat/decrypt/validator.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package decrypt
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/common"
|
||||
)
|
||||
|
||||
type Validator struct {
|
||||
platform string
|
||||
version int
|
||||
dbPath string
|
||||
decryptor Decryptor
|
||||
dbFile *common.DBFile
|
||||
}
|
||||
|
||||
// NewValidator 创建一个仅用于验证的验证器
|
||||
func NewValidator(dataDir string, platform string, version int) (*Validator, error) {
|
||||
decryptor, err := NewDecryptor(platform, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dbFile := GetSimpleDBFile(platform, version)
|
||||
dbPath := filepath.Join(dataDir + "/" + dbFile)
|
||||
d, err := common.OpenDBFile(dbPath, decryptor.GetPageSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Validator{
|
||||
platform: platform,
|
||||
version: version,
|
||||
dbPath: dbPath,
|
||||
decryptor: decryptor,
|
||||
dbFile: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(key []byte) bool {
|
||||
return v.decryptor.Validate(v.dbFile.FirstPage, key)
|
||||
}
|
||||
|
||||
func GetSimpleDBFile(platform string, version int) string {
|
||||
switch {
|
||||
case platform == "windows" && version == 3:
|
||||
return "Msg\\Misc.db"
|
||||
case platform == "windows" && version == 4:
|
||||
return "db_storage\\message\\message_0.db"
|
||||
case platform == "darwin" && version == 3:
|
||||
return "Message/msg_0.db"
|
||||
case platform == "darwin" && version == 4:
|
||||
return "db_storage/message/message_0.db"
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
192
internal/wechat/decrypt/windows/v3.go
Normal file
192
internal/wechat/decrypt/windows/v3.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/common"
|
||||
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// V3 版本特定常量
|
||||
const (
|
||||
PageSize = 4096
|
||||
V3IterCount = 64000
|
||||
HmacSHA1Size = 20
|
||||
)
|
||||
|
||||
// V3Decryptor 实现Windows V3版本的解密器
|
||||
type V3Decryptor struct {
|
||||
// V3 特定参数
|
||||
iterCount int
|
||||
hmacSize int
|
||||
hashFunc func() hash.Hash
|
||||
reserve int
|
||||
pageSize int
|
||||
version string
|
||||
}
|
||||
|
||||
// NewV3Decryptor 创建Windows V3解密器
|
||||
func NewV3Decryptor() *V3Decryptor {
|
||||
hashFunc := sha1.New
|
||||
hmacSize := HmacSHA1Size
|
||||
reserve := common.IVSize + hmacSize
|
||||
if reserve%common.AESBlockSize != 0 {
|
||||
reserve = ((reserve / common.AESBlockSize) + 1) * common.AESBlockSize
|
||||
}
|
||||
|
||||
return &V3Decryptor{
|
||||
iterCount: V3IterCount,
|
||||
hmacSize: hmacSize,
|
||||
hashFunc: hashFunc,
|
||||
reserve: reserve,
|
||||
pageSize: PageSize,
|
||||
version: "Windows v3",
|
||||
}
|
||||
}
|
||||
|
||||
// deriveKeys 派生加密密钥和MAC密钥
|
||||
func (d *V3Decryptor) deriveKeys(key []byte, salt []byte) ([]byte, []byte) {
|
||||
// 生成加密密钥
|
||||
encKey := pbkdf2.Key(key, salt, d.iterCount, common.KeySize, d.hashFunc)
|
||||
|
||||
// 生成MAC密钥
|
||||
macSalt := common.XorBytes(salt, 0x3a)
|
||||
macKey := pbkdf2.Key(encKey, macSalt, 2, common.KeySize, d.hashFunc)
|
||||
|
||||
return encKey, macKey
|
||||
}
|
||||
|
||||
// Validate 验证密钥是否有效
|
||||
func (d *V3Decryptor) Validate(page1 []byte, key []byte) bool {
|
||||
if len(page1) < d.pageSize || len(key) != common.KeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
salt := page1[:common.SaltSize]
|
||||
return common.ValidateKey(page1, key, salt, d.hashFunc, d.hmacSize, d.reserve, d.pageSize, d.deriveKeys)
|
||||
}
|
||||
|
||||
// Decrypt 解密数据库
|
||||
func (d *V3Decryptor) Decrypt(ctx context.Context, dbfile string, hexKey string, output io.Writer) error {
|
||||
// 解码密钥
|
||||
key, err := hex.DecodeString(hexKey)
|
||||
if err != nil {
|
||||
return errors.DecryptDecodeKeyFailed(err)
|
||||
}
|
||||
|
||||
// 打开数据库文件并读取基本信息
|
||||
dbInfo, err := common.OpenDBFile(dbfile, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证密钥
|
||||
if !d.Validate(dbInfo.FirstPage, key) {
|
||||
return errors.ErrDecryptIncorrectKey
|
||||
}
|
||||
|
||||
// 计算密钥
|
||||
encKey, macKey := d.deriveKeys(key, dbInfo.Salt)
|
||||
|
||||
// 打开数据库文件
|
||||
dbFile, err := os.Open(dbfile)
|
||||
if err != nil {
|
||||
return errors.DecryptOpenFileFailed(dbfile, err)
|
||||
}
|
||||
defer dbFile.Close()
|
||||
|
||||
// 写入SQLite头
|
||||
_, err = output.Write([]byte(common.SQLiteHeader))
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
|
||||
// 处理每一页
|
||||
pageBuf := make([]byte, d.pageSize)
|
||||
|
||||
for curPage := int64(0); curPage < dbInfo.TotalPages; curPage++ {
|
||||
// 检查是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.DecryptOperationCanceled()
|
||||
default:
|
||||
// 继续处理
|
||||
}
|
||||
|
||||
// 读取一页
|
||||
n, err := io.ReadFull(dbFile, pageBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
// 处理最后一部分页面
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.DecryptReadFileFailed(dbfile, err)
|
||||
}
|
||||
|
||||
// 检查页面是否全为零
|
||||
allZeros := true
|
||||
for _, b := range pageBuf {
|
||||
if b != 0 {
|
||||
allZeros = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allZeros {
|
||||
// 写入零页面
|
||||
_, err = output.Write(pageBuf)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 解密页面
|
||||
decryptedData, err := common.DecryptPage(pageBuf, encKey, macKey, curPage, d.hashFunc, d.hmacSize, d.reserve, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入解密后的页面
|
||||
_, err = output.Write(decryptedData)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageSize 返回页面大小
|
||||
func (d *V3Decryptor) GetPageSize() int {
|
||||
return d.pageSize
|
||||
}
|
||||
|
||||
// GetReserve 返回保留字节数
|
||||
func (d *V3Decryptor) GetReserve() int {
|
||||
return d.reserve
|
||||
}
|
||||
|
||||
// GetHMACSize 返回HMAC大小
|
||||
func (d *V3Decryptor) GetHMACSize() int {
|
||||
return d.hmacSize
|
||||
}
|
||||
|
||||
// GetVersion 返回解密器版本
|
||||
func (d *V3Decryptor) GetVersion() string {
|
||||
return d.version
|
||||
}
|
||||
|
||||
// GetIterCount 返回迭代次数(Windows特有)
|
||||
func (d *V3Decryptor) GetIterCount() int {
|
||||
return d.iterCount
|
||||
}
|
||||
190
internal/wechat/decrypt/windows/v4.go
Normal file
190
internal/wechat/decrypt/windows/v4.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package windows
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"hash"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sjzar/chatlog/internal/errors"
|
||||
"github.com/sjzar/chatlog/internal/wechat/decrypt/common"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
)
|
||||
|
||||
// V4 版本特定常量
|
||||
const (
|
||||
V4IterCount = 256000
|
||||
HmacSHA512Size = 64
|
||||
)
|
||||
|
||||
// V4Decryptor 实现Windows V4版本的解密器
|
||||
type V4Decryptor struct {
|
||||
// V4 特定参数
|
||||
iterCount int
|
||||
hmacSize int
|
||||
hashFunc func() hash.Hash
|
||||
reserve int
|
||||
pageSize int
|
||||
version string
|
||||
}
|
||||
|
||||
// NewV4Decryptor 创建Windows V4解密器
|
||||
func NewV4Decryptor() *V4Decryptor {
|
||||
hashFunc := sha512.New
|
||||
hmacSize := HmacSHA512Size
|
||||
reserve := common.IVSize + hmacSize
|
||||
if reserve%common.AESBlockSize != 0 {
|
||||
reserve = ((reserve / common.AESBlockSize) + 1) * common.AESBlockSize
|
||||
}
|
||||
|
||||
return &V4Decryptor{
|
||||
iterCount: V4IterCount,
|
||||
hmacSize: hmacSize,
|
||||
hashFunc: hashFunc,
|
||||
reserve: reserve,
|
||||
pageSize: PageSize,
|
||||
version: "Windows v4",
|
||||
}
|
||||
}
|
||||
|
||||
// deriveKeys 派生加密密钥和MAC密钥
|
||||
func (d *V4Decryptor) deriveKeys(key []byte, salt []byte) ([]byte, []byte) {
|
||||
// 生成加密密钥
|
||||
encKey := pbkdf2.Key(key, salt, d.iterCount, common.KeySize, d.hashFunc)
|
||||
|
||||
// 生成MAC密钥
|
||||
macSalt := common.XorBytes(salt, 0x3a)
|
||||
macKey := pbkdf2.Key(encKey, macSalt, 2, common.KeySize, d.hashFunc)
|
||||
|
||||
return encKey, macKey
|
||||
}
|
||||
|
||||
// Validate 验证密钥是否有效
|
||||
func (d *V4Decryptor) Validate(page1 []byte, key []byte) bool {
|
||||
if len(page1) < d.pageSize || len(key) != common.KeySize {
|
||||
return false
|
||||
}
|
||||
|
||||
salt := page1[:common.SaltSize]
|
||||
return common.ValidateKey(page1, key, salt, d.hashFunc, d.hmacSize, d.reserve, d.pageSize, d.deriveKeys)
|
||||
}
|
||||
|
||||
// Decrypt 解密数据库
|
||||
func (d *V4Decryptor) Decrypt(ctx context.Context, dbfile string, hexKey string, output io.Writer) error {
|
||||
// 解码密钥
|
||||
key, err := hex.DecodeString(hexKey)
|
||||
if err != nil {
|
||||
return errors.DecryptDecodeKeyFailed(err)
|
||||
}
|
||||
|
||||
// 打开数据库文件并读取基本信息
|
||||
dbInfo, err := common.OpenDBFile(dbfile, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证密钥
|
||||
if !d.Validate(dbInfo.FirstPage, key) {
|
||||
return errors.ErrDecryptIncorrectKey
|
||||
}
|
||||
|
||||
// 计算密钥
|
||||
encKey, macKey := d.deriveKeys(key, dbInfo.Salt)
|
||||
|
||||
// 打开数据库文件
|
||||
dbFile, err := os.Open(dbfile)
|
||||
if err != nil {
|
||||
return errors.DecryptOpenFileFailed(dbfile, err)
|
||||
}
|
||||
defer dbFile.Close()
|
||||
|
||||
// 写入SQLite头
|
||||
_, err = output.Write([]byte(common.SQLiteHeader))
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
|
||||
// 处理每一页
|
||||
pageBuf := make([]byte, d.pageSize)
|
||||
|
||||
for curPage := int64(0); curPage < dbInfo.TotalPages; curPage++ {
|
||||
// 检查是否取消
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.DecryptOperationCanceled()
|
||||
default:
|
||||
// 继续处理
|
||||
}
|
||||
|
||||
// 读取一页
|
||||
n, err := io.ReadFull(dbFile, pageBuf)
|
||||
if err != nil {
|
||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
||||
// 处理最后一部分页面
|
||||
if n > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return errors.DecryptReadFileFailed(dbfile, err)
|
||||
}
|
||||
|
||||
// 检查页面是否全为零
|
||||
allZeros := true
|
||||
for _, b := range pageBuf {
|
||||
if b != 0 {
|
||||
allZeros = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if allZeros {
|
||||
// 写入零页面
|
||||
_, err = output.Write(pageBuf)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 解密页面
|
||||
decryptedData, err := common.DecryptPage(pageBuf, encKey, macKey, curPage, d.hashFunc, d.hmacSize, d.reserve, d.pageSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入解密后的页面
|
||||
_, err = output.Write(decryptedData)
|
||||
if err != nil {
|
||||
return errors.DecryptWriteOutputFailed(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPageSize 返回页面大小
|
||||
func (d *V4Decryptor) GetPageSize() int {
|
||||
return d.pageSize
|
||||
}
|
||||
|
||||
// GetReserve 返回保留字节数
|
||||
func (d *V4Decryptor) GetReserve() int {
|
||||
return d.reserve
|
||||
}
|
||||
|
||||
// GetHMACSize 返回HMAC大小
|
||||
func (d *V4Decryptor) GetHMACSize() int {
|
||||
return d.hmacSize
|
||||
}
|
||||
|
||||
// GetVersion 返回解密器版本
|
||||
func (d *V4Decryptor) GetVersion() string {
|
||||
return d.version
|
||||
}
|
||||
|
||||
// GetIterCount 返回迭代次数(Windows特有)
|
||||
func (d *V4Decryptor) GetIterCount() int {
|
||||
return d.iterCount
|
||||
}
|
||||
Reference in New Issue
Block a user