Browse Source

带密码读写文件实现

master
刘子旺 7 months ago
parent
commit
cac7c8e803
  1. 1
      godo/cmd/main.go
  2. 106
      godo/files/fs.go
  3. 29
      godo/files/os.go
  4. 102
      godo/files/pwdfile.go
  5. 65
      godo/libs/encode.go

1
godo/cmd/main.go

@ -100,6 +100,7 @@ func OsStart() {
fileRouter.HandleFunc("/zip", files.HandleZip).Methods(http.MethodGet) fileRouter.HandleFunc("/zip", files.HandleZip).Methods(http.MethodGet)
fileRouter.HandleFunc("/unzip", files.HandleUnZip).Methods(http.MethodGet) fileRouter.HandleFunc("/unzip", files.HandleUnZip).Methods(http.MethodGet)
fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet) fileRouter.HandleFunc("/watch", files.WatchHandler).Methods(http.MethodGet)
fileRouter.HandleFunc("/setfilepwd", files.HandleSetFilePwd).Methods(http.MethodGet)
localchatRouter := router.PathPrefix("/localchat").Subrouter() localchatRouter := router.PathPrefix("/localchat").Subrouter()
localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost) localchatRouter.HandleFunc("/message", localchat.HandleMessage).Methods(http.MethodPost)

106
godo/files/fs.go

@ -24,9 +24,6 @@
package files package files
import ( import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"godo/libs" "godo/libs"
@ -164,58 +161,6 @@ func HandleExists(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
} }
// HandleReadFile reads a file's content
func HandleReadFile(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
fpwd := r.Header.Get("fpwd")
haspwd := IsHavePwd(fpwd)
// 校验文件路径
if err := validateFilePath(path); err != nil {
libs.HTTPError(w, http.StatusBadRequest, err.Error())
return
}
// 获取文件路径
basePath, err := libs.GetOsDir()
if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return
}
// 读取内容
fileContent, err := ReadFile(basePath, path)
if err != nil {
libs.HTTPError(w, http.StatusNotFound, err.Error())
return
}
content := string(fileContent)
// 检查文件内容是否以"link::"开头
if !strings.HasPrefix(content, "link::") {
content = base64.StdEncoding.EncodeToString(fileContent)
}
// 初始响应
res := libs.APIResponse{Code: 0, Message: "success"}
switch haspwd {
case true:
// 有密码检验密码
isreal := CheckFilePwd(fpwd)
// 密码正确返回原文,否则返回加密文本
if isreal {
res.Data = content
} else {
data, err := libs.EncryptData(fileContent, libs.EncryptionKey)
if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return
}
res.Data = base64.StdEncoding.EncodeToString(data)
}
case false:
res.Data = content
}
json.NewEncoder(w).Encode(res)
}
// HandleUnlink removes a file // HandleUnlink removes a file
func HandleUnlink(w http.ResponseWriter, r *http.Request) { func HandleUnlink(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path") path := r.URL.Query().Get("path")
@ -372,6 +317,7 @@ func HandleCopyFile(w http.ResponseWriter, r *http.Request) {
// HandleWriteFile writes content to a file // HandleWriteFile writes content to a file
func HandleWriteFile(w http.ResponseWriter, r *http.Request) { func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
// basepath = "/Users/sujia/.godoos/os"
filePath := r.URL.Query().Get("filePath") filePath := r.URL.Query().Get("filePath")
basePath, err := libs.GetOsDir() basePath, err := libs.GetOsDir()
if err != nil { if err != nil {
@ -386,8 +332,13 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
return return
} }
defer fileContent.Close() defer fileContent.Close()
// 输出到控制台进行调试 filedata, err := io.ReadAll(fileContent)
//fmt.Printf("Body content: %v\n", fileContent) if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 创建文件
file, err := os.Create(filepath.Join(basePath, filePath)) file, err := os.Create(filepath.Join(basePath, filePath))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -395,15 +346,25 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
} }
defer file.Close() defer file.Close()
_, err = io.Copy(file, fileContent) // 内容为空直接返回,不为空则加密
if len(filedata) == 0 {
CheckAddDesktop(filePath)
libs.SuccessMsg(w, "", "success")
return
}
// 加密
data, err := libs.EncryptData(filedata, libs.EncryptionKey)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusConflict) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
err = CheckAddDesktop(filePath) _, err = file.Write(data)
if err != nil { if err != nil {
log.Printf("Error adding file to desktop: %s", err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError)
return
} }
// 判断下是否添加到桌面上
CheckAddDesktop(filePath)
res := libs.APIResponse{Message: fmt.Sprintf("File '%s' successfully written.", filePath)} res := libs.APIResponse{Message: fmt.Sprintf("File '%s' successfully written.", filePath)}
json.NewEncoder(w).Encode(res) json.NewEncoder(w).Encode(res)
} }
@ -547,26 +508,3 @@ func HandleDesktop(w http.ResponseWriter, r *http.Request) {
} }
libs.SuccessMsg(w, rootInfo, "success") libs.SuccessMsg(w, rootInfo, "success")
} }
// 设置文件密码
func HandleSetFilePwd(w http.ResponseWriter, r *http.Request) {
fpwd := r.Header.Get("filepwd")
// 密码最长16位
if fpwd == "" || len(fpwd) > 16 {
libs.ErrorMsg(w, "密码长度为空或者过长,最长为16位")
return
}
// 服务端存储
req := libs.ReqBody{
Name: "filepwd",
Value: fpwd,
}
libs.SetConfig(req)
// 客户端加密
mhash := md5.New()
mhash.Write([]byte(fpwd))
v := mhash.Sum(nil)
pwdstr := hex.EncodeToString(v)
res := libs.APIResponse{Message: "success", Data: pwdstr}
json.NewEncoder(w).Encode(res)
}

29
godo/files/os.go

@ -24,12 +24,11 @@
package files package files
import ( import (
"crypto/md5"
"encoding/hex"
"fmt" "fmt"
"godo/libs" "godo/libs"
"io" "io"
"io/fs" "io/fs"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -338,13 +337,13 @@ func CheckDeleteDesktop(filePath string) error {
} }
// 校验文件密码 // 校验文件密码
func CheckFilePwd(fpwd string) bool { func CheckFilePwd(fpwd, salt string) bool {
mhash := md5.New() pwd := libs.HashPassword(fpwd, salt)
mhash.Write([]byte(fpwd)) oldpwd, err := libs.GetConfig("filepwd")
v := mhash.Sum(nil) if !err {
pwdstr := hex.EncodeToString(v) return false
oldpwd, _ := libs.GetConfig("filepwd") }
return oldpwd == pwdstr return oldpwd == pwd
} }
func IsHavePwd(pwd string) bool { func IsHavePwd(pwd string) bool {
@ -354,3 +353,15 @@ func IsHavePwd(pwd string) bool {
return false return false
} }
} }
// salt值优先从server端获取,如果没有则从header获取
func GetSalt(r *http.Request) string {
data, ishas := libs.GetConfig("salt")
salt := data.(string)
if ishas {
return salt
} else {
salt = r.Header.Get("salt")
return salt
}
}

102
godo/files/pwdfile.go

@ -0,0 +1,102 @@
package files
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json"
"godo/libs"
"net/http"
"strings"
)
// 带加密读
func HandleReadFile(w http.ResponseWriter, r *http.Request) {
path := r.URL.Query().Get("path")
fpwd := r.Header.Get("fpwd")
haspwd := IsHavePwd(fpwd)
// 获取salt值
salt := GetSalt(r)
// 校验文件路径
if err := validateFilePath(path); err != nil {
libs.HTTPError(w, http.StatusBadRequest, err.Error())
return
}
// 有密码校验密码
if haspwd {
if !CheckFilePwd(fpwd, salt) {
libs.HTTPError(w, http.StatusBadRequest, "密码错误")
return
}
}
// 获取文件路径
basePath, err := libs.GetOsDir()
if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return
}
// 读取内容
fileContent, err := ReadFile(basePath, path)
if err != nil {
libs.HTTPError(w, http.StatusNotFound, err.Error())
return
}
// 解密
data, err := libs.DecryptData(fileContent, libs.EncryptionKey)
if err != nil {
libs.HTTPError(w, http.StatusInternalServerError, err.Error())
return
}
content := string(data)
// 检查文件内容是否以"link::"开头
if !strings.HasPrefix(content, "link::") {
content = base64.StdEncoding.EncodeToString(data)
}
// 初始响应
res := libs.APIResponse{Code: 0, Message: "success", Data: content}
json.NewEncoder(w).Encode(res)
}
// 设置文件密码
func HandleSetFilePwd(w http.ResponseWriter, r *http.Request) {
fpwd := r.Header.Get("filepwd")
salt := r.Header.Get("salt")
// 密码最长16位
if fpwd == "" || len(fpwd) > 16 {
libs.ErrorMsg(w, "密码长度为空或者过长,最长为16位")
return
}
// md5加密
mhash := md5.New()
mhash.Write([]byte(fpwd))
v := mhash.Sum(nil)
pwdstr := hex.EncodeToString(v)
// 服务端再hash加密
hashpwd := libs.HashPassword(pwdstr, salt)
// 服务端存储
req := libs.ReqBody{
Name: "filepwd",
Value: hashpwd,
}
libs.SetConfig(req)
// salt值存储
reqSalt := libs.ReqBody{
Name: "salt",
Value: salt,
}
libs.SetConfig(reqSalt)
res := libs.APIResponse{Message: "success", Data: pwdstr}
json.NewEncoder(w).Encode(res)
}

65
godo/libs/encode.go

@ -7,6 +7,8 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/base64"
"errors"
"io" "io"
) )
@ -20,6 +22,19 @@ func pkcs7Pad(data []byte, blockSize int) []byte {
return append(data, padtext...) return append(data, padtext...)
} }
// pkcs7Unpad 移除 PKCS#7 填充
func pkcs7Unpad(data []byte) []byte {
length := len(data)
if length == 0 {
return data
}
padding := int(data[length-1]) // 将 padding 转换为 int 类型
if padding > aes.BlockSize || padding < 1 {
return data
}
return data[:length-padding]
}
func EncryptData(data []byte, key []byte) ([]byte, error) { func EncryptData(data []byte, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key) block, err := aes.NewCipher(key)
if err != nil { if err != nil {
@ -50,3 +65,53 @@ func EncryptData(data []byte, key []byte) ([]byte, error) {
return result, nil return result, nil
} }
// DecryptData 使用 AES 解密数据,并验证 HMAC-SHA256 签名
func DecryptData(ciphertext []byte, key []byte) ([]byte, error) {
// 检查 HMAC-SHA256 签名
expectedMacSize := sha256.Size
if len(ciphertext) < expectedMacSize {
return nil, errors.New("ciphertext too short")
}
macSum := ciphertext[len(ciphertext)-expectedMacSize:]
ciphertext = ciphertext[:len(ciphertext)-expectedMacSize]
// 验证 HMAC-SHA256 签名
mac := hmac.New(sha256.New, key)
mac.Write(ciphertext)
calculatedMac := mac.Sum(nil)
if !hmac.Equal(macSum, calculatedMac) {
return nil, errors.New("invalid MAC")
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 检查 IV 的长度
if len(ciphertext) < aes.BlockSize {
return nil, errors.New("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
// 使用 CBC 模式解密数据
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(ciphertext, ciphertext)
// 移除 PKCS#7 填充
unpaddedData := pkcs7Unpad(ciphertext)
return unpaddedData, nil
}
// 哈希加密
func HashPassword(password, salt string) string {
hash := sha256.New()
hash.Write([]byte(password + salt))
return base64.URLEncoding.EncodeToString(hash.Sum(nil))
}

Loading…
Cancel
Save