1
0
mirror of https://github.com/Mrs4s/go-cqhttp.git synced 2025-06-19 22:15:04 +08:00

Merge pull request #590 from zkonge/master

增强保存密码安全性
This commit is contained in:
Mrs4s 2021-01-26 03:46:14 +08:00 committed by GitHub
commit 2214570c4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 130 additions and 33 deletions

View File

@ -4,6 +4,7 @@ import (
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"io/ioutil" "io/ioutil"
"math"
"os" "os"
"path" "path"
"path/filepath" "path/filepath"
@ -667,8 +668,11 @@ func (bot *CQBot) CQGetStrangerInfo(userId int64) MSG {
"sex": func() string { "sex": func() string {
if info.Sex == 1 { if info.Sex == 1 {
return "female" return "female"
} else if info.Sex == 0 {
return "male"
} }
return "male" // unknown = 0x2
return "unknown"
}(), }(),
"age": info.Age, "age": info.Age,
"level": info.Level, "level": info.Level,
@ -848,7 +852,14 @@ func (bot *CQBot) CQGetGroupMessageHistory(groupId int64, seq int64) MSG {
if g := bot.Client.FindGroup(groupId); g == nil { if g := bot.Client.FindGroup(groupId); g == nil {
return Failed(100, "GROUP_NOT_FOUND", "群聊不存在") return Failed(100, "GROUP_NOT_FOUND", "群聊不存在")
} }
msg, err := bot.Client.GetGroupMessages(groupId, seq-19, seq) if seq == 0 {
g, err := bot.Client.GetGroupInfo(groupId)
if err != nil {
return Failed(100, "GROUP_INFO_API_ERROR", err.Error())
}
seq = g.LastMsgSeq
}
msg, err := bot.Client.GetGroupMessages(groupId, int64(math.Max(float64(seq-19), 1)), seq)
if err != nil { if err != nil {
log.Warnf("获取群历史消息失败: %v", err) log.Warnf("获取群历史消息失败: %v", err)
return Failed(100, "MESSAGES_API_ERROR", err.Error()) return Failed(100, "MESSAGES_API_ERROR", err.Error())
@ -1008,11 +1019,19 @@ func Failed(code int, msg ...string) MSG {
func convertGroupMemberInfo(groupId int64, m *client.GroupMemberInfo) MSG { func convertGroupMemberInfo(groupId int64, m *client.GroupMemberInfo) MSG {
return MSG{ return MSG{
"group_id": groupId, "group_id": groupId,
"user_id": m.Uin, "user_id": m.Uin,
"nickname": m.Nickname, "nickname": m.Nickname,
"card": m.CardName, "card": m.CardName,
"sex": "unknown", "sex": func() string {
if m.Gender == 1 {
return "female"
} else if m.Gender == 0 {
return "male"
}
// unknown = 0xff
return "unknown"
}(),
"age": 0, "age": 0,
"area": "", "area": "",
"join_time": m.JoinTime, "join_time": m.JoinTime,

View File

@ -422,8 +422,8 @@ func (bot *CQBot) otherClientStatusChangedEvent(c *client.QQClient, e *client.Ot
bot.dispatchEventMessage(MSG{ bot.dispatchEventMessage(MSG{
"post_type": "notice", "post_type": "notice",
"notice_type": "client_status", "notice_type": "client_status",
"online": e.Online,
"client": MSG{ "client": MSG{
"online": e.Online,
"app_id": e.Client.AppId, "app_id": e.Client.AppId,
"device_name": e.Client.DeviceName, "device_name": e.Client.DeviceName,
"device_kind": e.Client.DeviceKind, "device_kind": e.Client.DeviceKind,

View File

@ -850,6 +850,30 @@ JSON数组:
> 不提供起始序号将默认获取最新的消息 > 不提供起始序号将默认获取最新的消息
### 获取当前账号在线客户端列表
终结点:`/get_online_clients`
**参数**
| 字段 | 类型 | 说明 |
| ---------- | ------ | ------------------------- |
| `no_cache` | bool | 是否无视缓存 |
**响应数据**
| 字段 | 类型 | 说明 |
| ---------- | ---------- | ------------ |
| `clients` | []Device | 在线客户端列表 |
**Device**
| 字段 | 类型 | 说明 |
| ---------- | ---------- | ------------ |
| `app_id` | int64 | 客户端ID |
| `device_name` | string | 设备名称 |
| `device_kind` | string | 设备类型 |
### 获取用户VIP信息 ### 获取用户VIP信息
终结点:`/_get_vip_info` 终结点:`/_get_vip_info`
@ -1013,3 +1037,14 @@ JSON数组:
| `name` | string | | 文件名 | | `name` | string | | 文件名 |
| `size` | int64 | | 文件大小 | | `size` | int64 | | 文件大小 |
| `url` | string | | 下载链接 | | `url` | string | | 下载链接 |
### 其他客户端在线状态变更
**上报数据**
| 字段 | 类型 | 可能的值 | 说明 |
| ------------- | ------ | -------------- | -------- |
| `post_type` | string | `notice` | 上报类型 |
| `notice_type` | string | `client_status` | 消息类型 |
| `client` | Device | | 客户端信息 |
| `online` | bool | | 当前是否在线 |

View File

@ -135,6 +135,9 @@ var DefaultConfigWithComments = `
} }
` `
//PasswordHash 存储QQ密码哈希供登录使用
var PasswordHash [16]byte
//JSONConfig Config对应的结构体 //JSONConfig Config对应的结构体
type JSONConfig struct { type JSONConfig struct {
Uin int64 `json:"uin"` Uin int64 `json:"uin"`

3
go.mod
View File

@ -3,7 +3,7 @@ module github.com/Mrs4s/go-cqhttp
go 1.15 go 1.15
require ( require (
github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104 github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a
github.com/dustin/go-humanize v1.0.0 github.com/dustin/go-humanize v1.0.0
github.com/gin-contrib/pprof v1.3.0 github.com/gin-contrib/pprof v1.3.0
github.com/gin-gonic/gin v1.6.3 github.com/gin-gonic/gin v1.6.3
@ -21,6 +21,7 @@ require (
github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816
github.com/tidwall/gjson v1.6.7 github.com/tidwall/gjson v1.6.7
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 golang.org/x/time v0.0.0-20201208040808-7e3f01d25324
) )

5
go.sum
View File

@ -1,7 +1,7 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104 h1:dIKRhxzCrt/jdWRFdJupIU3BM94IYoicgqRzjN+2Bw4= github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a h1:ov5QCDpZpsr+geKLVON7E63UqrpNF0oTiKuZwbKwEmo=
github.com/Mrs4s/MiraiGo v0.0.0-20210124063508-a401ed3ef104/go.mod h1:9V7DdSwpEfCKQNvLZhRnFJFkelTU0tPLfwR5l6UFF1Y= github.com/Mrs4s/MiraiGo v0.0.0-20210124065645-9549a32d954a/go.mod h1:9V7DdSwpEfCKQNvLZhRnFJFkelTU0tPLfwR5l6UFF1Y=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -110,6 +110,7 @@ github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189 h1:4UJw9if55Fu3HOwbfcaQlJ27p3oeJU2JZqoeT3ITJQk=
github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I= github.com/yinghau76/go-ascii-art v0.0.0-20190517192627-e7f465a30189/go.mod h1:rIrm5geMiBhPQkdfUm8gDFi/WiHneOp1i9KjmJqc+9I=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

82
main.go
View File

@ -2,8 +2,11 @@ package main
import ( import (
"bufio" "bufio"
"crypto/aes"
"crypto/md5" "crypto/md5"
"crypto/sha1"
"encoding/base64" "encoding/base64"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -23,6 +26,7 @@ import (
"github.com/guonaihong/gout" "github.com/guonaihong/gout"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
"golang.org/x/term" "golang.org/x/term"
"golang.org/x/crypto/pbkdf2"
"github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/binary"
"github.com/Mrs4s/MiraiGo/client" "github.com/Mrs4s/MiraiGo/client"
@ -233,15 +237,11 @@ func main() {
} }
if conf.EncryptPassword && conf.PasswordEncrypted == "" { if conf.EncryptPassword && conf.PasswordEncrypted == "" {
log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)") log.Infof("密码加密已启用, 请输入Key对密码进行加密: (Enter 提交)")
byteKey, _ := term.ReadPassword(int(os.Stdin.Fd())) byteKey, _ = term.ReadPassword(int(os.Stdin.Fd()))
key := md5.Sum(byteKey) global.PasswordHash = md5.Sum([]byte(conf.Password))
if encrypted := EncryptPwd(conf.Password, key[:]); encrypted != "" { conf.Password = ""
conf.Password = "" conf.PasswordEncrypted = "AES:" + PasswordHashEncrypt(global.PasswordHash[:], byteKey)
conf.PasswordEncrypted = encrypted _ = conf.Save("config.hjson")
_ = conf.Save("config.hjson")
} else {
log.Warnf("加密时出现问题.")
}
} }
if conf.PasswordEncrypted != "" { if conf.PasswordEncrypted != "" {
if len(byteKey) == 0 { if len(byteKey) == 0 {
@ -262,8 +262,23 @@ func main() {
} else { } else {
log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.") log.Infof("密码加密已启用, 使用运行时传递的参数进行解密,按 Ctrl+C 取消.")
} }
key := md5.Sum(byteKey)
conf.Password = DecryptPwd(conf.PasswordEncrypted, key[:]) //升级客户端密码加密方案MD5+TEA 加密密码 -> PBKDF2+AES 加密 MD5
//升级后的 PasswordEncrypted 字符串以"AES:"开始,其后为 Hex 编码的16字节加密 MD5
if !strings.HasPrefix(conf.PasswordEncrypted, "AES:") {
password := OldPasswordDecrypt(conf.PasswordEncrypted, byteKey)
passwordHash := md5.Sum([]byte(password))
newPasswordHash := PasswordHashEncrypt(passwordHash[:], byteKey)
conf.PasswordEncrypted = "AES:" + newPasswordHash
_ = conf.Save("config.hjson")
log.Debug("密码加密方案升级完成")
}
ph, err := PasswordHashDecrypt(conf.PasswordEncrypted[4:], byteKey)
if err != nil {
log.Fatalf("加密存储的密码损坏,请尝试重新配置密码")
}
copy(global.PasswordHash[:], ph)
} }
if !isFastStart { if !isFastStart {
log.Info("Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消.") log.Info("Bot将在5秒后登录并开始信息处理, 按 Ctrl+C 取消.")
@ -283,7 +298,7 @@ func main() {
} }
return "未知" return "未知"
}()) }())
cli := client.NewClient(conf.Uin, conf.Password) cli := client.NewClientMd5(conf.Uin, global.PasswordHash)
cli.OnLog(func(c *client.QQClient, e *client.LogEvent) { cli.OnLog(func(c *client.QQClient, e *client.LogEvent) {
switch e.Type { switch e.Type {
case "INFO": case "INFO":
@ -340,27 +355,50 @@ func main() {
} }
} }
//EncryptPwd 通过给定key加密给定pwd // PasswordHashEncrypt 使用key加密给定passwordHash
func EncryptPwd(pwd string, key []byte) string { func PasswordHashEncrypt(passwordHash []byte, key []byte) string {
tea := binary.NewTeaCipher(key) if len(passwordHash) != 16 {
if tea == nil { panic("密码加密参数错误")
return ""
} }
return base64.StdEncoding.EncodeToString(tea.Encrypt([]byte(pwd)))
key = pbkdf2.Key(key, key, 114514, 32, sha1.New)
cipher, _ := aes.NewCipher(key)
result := make([]byte, 16)
cipher.Encrypt(result, passwordHash)
return hex.EncodeToString(result)
} }
//DecryptPwd 通过给定key解密给定ePwd // PasswordHashDecrypt 使用key解密给定passwordHash
func DecryptPwd(ePwd string, key []byte) string { func PasswordHashDecrypt(encryptedPasswordHash string, key []byte) ([]byte, error) {
ciphertext, err := hex.DecodeString(encryptedPasswordHash)
if err != nil {
return nil, err
}
key = pbkdf2.Key(key, key, 114514, 32, sha1.New)
cipher, _ := aes.NewCipher(key)
result := make([]byte, 16)
cipher.Decrypt(result, ciphertext)
return result, nil
}
// OldPasswordDecrypt 使用key解密老password仅供兼容使用
func OldPasswordDecrypt(encryptedPassword string, key []byte) string {
defer func() { defer func() {
if pan := recover(); pan != nil { if pan := recover(); pan != nil {
log.Fatalf("密码解密失败: %v", pan) log.Fatalf("密码解密失败: %v", pan)
} }
}() }()
encrypted, err := base64.StdEncoding.DecodeString(ePwd) encKey := md5.Sum(key)
encrypted, err := base64.StdEncoding.DecodeString(encryptedPassword)
if err != nil { if err != nil {
panic(err) panic(err)
} }
tea := binary.NewTeaCipher(key) tea := binary.NewTeaCipher(encKey[:])
if tea == nil { if tea == nil {
panic("密钥错误") panic("密钥错误")
} }