Browse Source

change pwd

master
godo 6 months ago
parent
commit
3ca50aab54
  1. 239
      frontend/src/components/builtin/FileList.vue
  2. 41
      frontend/src/components/oa/FilePwd.vue
  3. 3
      frontend/src/components/window/IframeFile.vue
  4. 17
      godo/files/pwd.go
  5. 285
      godo/libs/filecode.go
  6. 8
      godo/libs/filecode_test.go

239
frontend/src/components/builtin/FileList.vue

@ -17,10 +17,7 @@
</div> </div>
</div> </div>
</template> </template>
<div <div draggable="true" class="file-item" :class="{
draggable="true"
class="file-item"
:class="{
chosen: chosenIndexs.includes(index), chosen: chosenIndexs.includes(index),
'no-chosen': !chosenIndexs.includes(index), 'no-chosen': !chosenIndexs.includes(index),
'mode-icon': mode === 'icon', 'mode-icon': mode === 'icon',
@ -29,52 +26,27 @@
'mode-middle': mode === 'middle', 'mode-middle': mode === 'middle',
'mode-detail': mode === 'detail', 'mode-detail': mode === 'detail',
'drag-over': hoverIndex === index, 'drag-over': hoverIndex === index,
}" }" :style="{
:style="{
'--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d', '--theme-color': theme === 'light' ? '#ffffff6b' : '#3bdbff3d',
}" }" v-for="(item, index) in fileList" :key="item.path" @dblclick="handleOnOpen(item)"
v-for="(item, index) in fileList"
:key="item.path"
@dblclick="handleOnOpen(item)"
@touchstart.passive="doubleTouch($event, item)" @touchstart.passive="doubleTouch($event, item)"
@contextmenu.stop.prevent="handleRightClick($event, item, index)" @contextmenu.stop.prevent="handleRightClick($event, item, index)" @drop="hadnleDrop($event, item.path)"
@drop="hadnleDrop($event, item.path)" @dragenter.prevent="handleDragEnter(index)" @dragover.prevent @dragleave="handleDragLeave()"
@dragenter.prevent="handleDragEnter(index)" @dragstart.stop="startDragApp($event, item)" @click="handleClick(index)" @mousedown.stop :ref="(ref: any) => {
@dragover.prevent
@dragleave="handleDragLeave()"
@dragstart.stop="startDragApp($event, item)"
@click="handleClick(index)"
@mousedown.stop
:ref="(ref: any) => {
if (ref) { if (ref) {
appPositions[index] = markRaw(ref as Element); appPositions[index] = markRaw(ref as Element);
} }
} }
" ">
>
<div class="file-item_img"> <div class="file-item_img">
<FileIcon :file="item" /> <FileIcon :file="item" />
</div> </div>
<span <span v-if="editIndex !== index" class="file-item_title">
v-if="editIndex !== index"
class="file-item_title"
>
{{ getName(item) }} {{ getName(item) }}
</span> </span>
<textarea <textarea autofocus draggable="false" @dragover.stop @dragstart.stop @dragenter.stop @mousedown.stop
autofocus @dblclick.stop @click.stop @blur="onEditNameEnd" v-if="editIndex === index"
draggable="false" class="file-item_title file-item_editing" v-model="editName"></textarea>
@dragover.stop
@dragstart.stop
@dragenter.stop
@mousedown.stop
@dblclick.stop
@click.stop
@blur="onEditNameEnd"
v-if="editIndex === index"
class="file-item_title file-item_editing"
v-model="editName"
></textarea>
<template v-if="mode === 'detail'"> <template v-if="mode === 'detail'">
<div class="file-item_type"> <div class="file-item_type">
<span>{{ item.isDirectory ? "-" : dealSize(item.size) }}</span> <span>{{ item.isDirectory ? "-" : dealSize(item.size) }}</span>
@ -92,32 +64,32 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useAppMenu } from "@/hook/useAppMenu"; import { useAppMenu } from "@/hook/useAppMenu";
import { useContextMenu } from "@/hook/useContextMenu.ts"; import { useContextMenu } from "@/hook/useContextMenu.ts";
import { useFileDrag } from "@/hook/useFileDrag"; import { useFileDrag } from "@/hook/useFileDrag";
import { Rect } from "@/hook/useRectChosen"; import { Rect } from "@/hook/useRectChosen";
import { dealSystemName, t } from "@/i18n"; import { dealSystemName, t } from "@/i18n";
import { useChooseStore } from "@/stores/choose"; import { useChooseStore } from "@/stores/choose";
import { getSystemKey } from "@/system/config"; import { getSystemKey } from "@/system/config";
import { emitEvent, mountEvent } from "@/system/event"; import { emitEvent, mountEvent } from "@/system/event";
import { import {
basename, basename,
BrowserWindow, BrowserWindow,
Notify, Notify,
OsFileWithoutContent, OsFileWithoutContent,
useSystem, useSystem,
} from "@/system/index.ts"; } from "@/system/index.ts";
import { Menu } from "@/system/menu/Menu"; import { Menu } from "@/system/menu/Menu";
import { throttle } from "@/util/debounce"; import { throttle } from "@/util/debounce";
import { dealSize } from "@/util/file"; import { dealSize } from "@/util/file";
import { markRaw, onMounted, ref } from "vue"; import { markRaw, onMounted, ref } from "vue";
const { openPropsWindow, copyFile, createLink, deleteFile } = const { openPropsWindow, copyFile, createLink, deleteFile } =
useContextMenu(); useContextMenu();
const sys = useSystem(); const sys = useSystem();
const { startDrag, folderDrop } = useFileDrag(sys); const { startDrag, folderDrop } = useFileDrag(sys);
const choose = useChooseStore(); const choose = useChooseStore();
const props = defineProps({ const props = defineProps({
onChosen: { onChosen: {
type: Function, type: Function,
required: true, required: true,
@ -146,9 +118,9 @@
type: String, type: String,
default: "icon", default: "icon",
}, },
}); });
function getName(item: any) { function getName(item: any) {
const name = dealSystemName(basename(item.path)); const name = dealSystemName(basename(item.path));
// console.log(name) // console.log(name)
// console.log(item.path) // console.log(item.path)
@ -157,8 +129,8 @@
} else { } else {
return name; return name;
} }
} }
function handleOnOpen(item: OsFileWithoutContent) { function handleOnOpen(item: OsFileWithoutContent) {
// props.onOpen(item); // props.onOpen(item);
// emitEvent('desktop.app.open'); // emitEvent('desktop.app.open');
chosenIndexs.value = []; chosenIndexs.value = [];
@ -171,14 +143,14 @@
props.onOpen(item); props.onOpen(item);
emitEvent("desktop.app.open"); emitEvent("desktop.app.open");
} }
} }
function hadnleDrop(mouse: DragEvent, path: string) { function hadnleDrop(mouse: DragEvent, path: string) {
hoverIndex.value = -1; hoverIndex.value = -1;
folderDrop(mouse, path); folderDrop(mouse, path);
chosenIndexs.value = []; chosenIndexs.value = [];
} }
let expired: number | null = null; let expired: number | null = null;
function doubleTouch(e: TouchEvent, item: OsFileWithoutContent) { function doubleTouch(e: TouchEvent, item: OsFileWithoutContent) {
if (e.touches.length === 1) { if (e.touches.length === 1) {
if (!expired) { if (!expired) {
expired = e.timeStamp + 400; expired = e.timeStamp + 400;
@ -193,11 +165,11 @@
expired = e.timeStamp + 400; expired = e.timeStamp + 400;
} }
} }
} }
const editIndex = ref<number>(-1); const editIndex = ref<number>(-1);
const editName = ref<string>(""); const editName = ref<string>("");
async function onEditNameEnd() { async function onEditNameEnd() {
const editEndName = editName.value.trim(); const editEndName = editName.value.trim();
if (editEndName && editIndex.value >= 0) { if (editEndName && editIndex.value >= 0) {
const editpath: any = const editpath: any =
@ -218,19 +190,19 @@
} }
} }
editIndex.value = -1; editIndex.value = -1;
} }
mountEvent("edit.end", () => { mountEvent("edit.end", () => {
onEditNameEnd(); onEditNameEnd();
}); });
const hoverIndex = ref<number>(-1); const hoverIndex = ref<number>(-1);
const appPositions = ref<Array<Element>>([]); const appPositions = ref<Array<Element>>([]);
const chosenIndexs = ref<Array<number>>([]); const chosenIndexs = ref<Array<number>>([]);
function handleClick(index: number) { function handleClick(index: number) {
chosenIndexs.value = [index]; chosenIndexs.value = [index];
} }
onMounted(() => { onMounted(() => {
chosenIndexs.value = []; chosenIndexs.value = [];
props.onChosen( props.onChosen(
throttle((rect: Rect) => { throttle((rect: Rect) => {
@ -253,9 +225,9 @@
chosenIndexs.value = tempChosen; chosenIndexs.value = tempChosen;
}, 100) }, 100)
); );
}); });
function startDragApp(mouse: DragEvent, item: OsFileWithoutContent) { function startDragApp(mouse: DragEvent, item: OsFileWithoutContent) {
if (chosenIndexs.value.length) { if (chosenIndexs.value.length) {
startDrag( startDrag(
mouse, mouse,
@ -271,13 +243,13 @@
chosenIndexs.value = []; chosenIndexs.value = [];
}); });
} }
} }
function handleRightClick( function handleRightClick(
mouse: MouseEvent, mouse: MouseEvent,
item: OsFileWithoutContent, item: OsFileWithoutContent,
index: number index: number
) { ) {
if (chosenIndexs.value.length <= 1) { if (chosenIndexs.value.length <= 1) {
chosenIndexs.value = [ chosenIndexs.value = [
props.fileList.findIndex((app) => app.path === item.path), props.fileList.findIndex((app) => app.path === item.path),
@ -457,22 +429,7 @@
win.show(); win.show();
}, },
}); });
menuArr.push({
label: "文件加密",
click: () => {
const win = new BrowserWindow({
title: "文件加密",
content: "FilePwd",
config: {
path: item.path,
},
width: 400,
height: 200,
center: true,
});
win.show();
},
});
// menuArr.push({ // menuArr.push({
// label: "", // label: "",
// click: () => { // click: () => {
@ -490,6 +447,22 @@
// }, // },
// }); // });
} }
menuArr.push({
label: "文件加密",
click: () => {
const win = new BrowserWindow({
title: "文件加密",
content: "FilePwd",
config: {
path: item.path,
},
width: 400,
height: 200,
center: true,
});
win.show();
},
});
menuArr.push.apply(menuArr, fileMenus); menuArr.push.apply(menuArr, fileMenus);
} }
const sysEndMenu = [ const sysEndMenu = [
@ -510,22 +483,22 @@
//console.log(ext) //console.log(ext)
Menu.buildFromTemplate(menuArr).popup(mouse); Menu.buildFromTemplate(menuArr).popup(mouse);
} }
function handleDragEnter(index: number) { function handleDragEnter(index: number) {
hoverIndex.value = index; hoverIndex.value = index;
} }
function handleDragLeave() { function handleDragLeave() {
hoverIndex.value = -1; hoverIndex.value = -1;
} }
// function dealtName(name: string) { // function dealtName(name: string) {
// return name; // return name;
// } // }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.file-item { .file-item {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -565,13 +538,13 @@
resize: none; resize: none;
border-radius: 0; border-radius: 0;
} }
} }
.file-item:hover { .file-item:hover {
background-color: #b1f1ff4c; background-color: #b1f1ff4c;
} }
.chosen { .chosen {
border: 1px dashed #3bdbff3d; border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b; // background-color: #ffffff6b;
background-color: var(--theme-color); background-color: var(--theme-color);
@ -584,9 +557,9 @@
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
} }
} }
.no-chosen { .no-chosen {
.file-item_title { .file-item_title {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -595,15 +568,15 @@
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
} }
} }
.drag-over { .drag-over {
border: 1px dashed #3bdbff3d; border: 1px dashed #3bdbff3d;
// background-color: #ffffff6b; // background-color: #ffffff6b;
background-color: var(--theme-color); background-color: var(--theme-color);
} }
.mode-icon { .mode-icon {
.file-item_img { .file-item_img {
width: 60%; width: 60%;
height: calc(0.6 * var(--desk-item-size)); height: calc(0.6 * var(--desk-item-size));
@ -620,9 +593,9 @@
word-break: break-all; word-break: break-all;
flex-grow: 0; flex-grow: 0;
} }
} }
.mode-list { .mode-list {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
@ -641,24 +614,24 @@
height: min-content; height: min-content;
word-break: break-all; word-break: break-all;
} }
} }
.mode-icon { .mode-icon {
width: var(--desk-item-size); width: var(--desk-item-size);
height: var(--desk-item-size); height: var(--desk-item-size);
} }
.mode-big { .mode-big {
width: calc(var(--desk-item-size) * 2.5); width: calc(var(--desk-item-size) * 2.5);
height: calc(var(--desk-item-size) * 2.5); height: calc(var(--desk-item-size) * 2.5);
} }
.mode-middle { .mode-middle {
width: calc(var(--desk-item-size) * 1.5); width: calc(var(--desk-item-size) * 1.5);
height: calc(var(--desk-item-size) * 1.5); height: calc(var(--desk-item-size) * 1.5);
} }
.mode-detail { .mode-detail {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-start;
@ -688,10 +661,10 @@
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
} }
.file-bar:hover { .file-bar:hover {
background-color: unset; background-color: unset;
user-select: none; user-select: none;
} }
</style> </style>

41
frontend/src/components/oa/FilePwd.vue

@ -21,12 +21,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { BrowserWindow, useSystem } from "@/system"; import { useSystem } from "@/system";
import { notifyError, notifySuccess } from "@/util/msg"; import { notifyError, notifySuccess } from "@/util/msg";
// import { md5 } from "js-md5"; // import { md5 } from "js-md5";
import { ref } from "vue"; import { ref } from "vue";
import { getSystemConfig, setSystemKey } from "@/system/config"; //import { getSystemConfig, setSystemKey } from "@/system/config";
const window: BrowserWindow | undefined = inject("browserWindow"); const window: any = inject("browserWindow");
const filePwd = ref(""); const filePwd = ref("");
const sys = useSystem(); const sys = useSystem();
async function setFilePwd() { async function setFilePwd() {
@ -47,29 +47,32 @@ async function setFilePwd() {
if (res && res.code == 0) { if (res && res.code == 0) {
notifySuccess("文件密码设置成功"); notifySuccess("文件密码设置成功");
localStorageFilePwd(path, filePwd.value) window.close();
// localStorageFilePwd(path, filePwd.value)
} else { } else {
notifyError("文件密码设置失败"); notifyError("文件密码设置失败");
} }
//console.log("", res, path); //console.log("", res, path);
}else{
notifyError("请输入正确的密码6-10位");
} }
} }
// //
function localStorageFilePwd (path:string, pwd: string) { // function localStorageFilePwd (path:string, pwd: string) {
if (getSystemConfig().file.isPwd && getSystemConfig().userType == 'person') { // if (getSystemConfig().file.isPwd && getSystemConfig().userType == 'person') {
let fileInputPwd = getSystemConfig().fileInputPwd // let fileInputPwd = getSystemConfig().fileInputPwd
const pos = fileInputPwd.findIndex((item: any) => item.path == path) // const pos = fileInputPwd.findIndex((item: any) => item.path == path)
if (pos !== -1) { // if (pos !== -1) {
fileInputPwd[pos].pwd = pwd // fileInputPwd[pos].pwd = pwd
} else { // } else {
fileInputPwd.push({ // fileInputPwd.push({
path: path, // path: path,
pwd: pwd // pwd: pwd
}) // })
} // }
setSystemKey('fileInputPwd', fileInputPwd) // setSystemKey('fileInputPwd', fileInputPwd)
} // }
} // }
</script> </script>
<style scoped> <style scoped>
.btn-group { .btn-group {

3
frontend/src/components/window/IframeFile.vue

@ -148,6 +148,9 @@ const eventHandler = async (e: MessageEvent) => {
// console.log(title); // console.log(title);
title = title.split(SP).pop(); title = title.split(SP).pop();
content = toRaw(content); content = toRaw(content);
if(typeof content == "string"){
content = content.trim()
}
if (content && content !== "") { if (content && content !== "") {
storeRef.value?.contentWindow?.postMessage( storeRef.value?.contentWindow?.postMessage(

17
godo/files/pwd.go

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"godo/libs" "godo/libs"
"io" "io"
"log"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -45,9 +46,9 @@ func HandleReadFile(w http.ResponseWriter, r *http.Request) {
if !isPwd { if !isPwd {
// 未加密文件,直接返回 // 未加密文件,直接返回
text = base64.StdEncoding.EncodeToString(fileData) text = base64.StdEncoding.EncodeToString(fileData)
// if len(text)%8 == 0 { if len(text)%8 == 0 {
// text += " " text += " "
// } }
//log.Printf("fileData: %s", content) //log.Printf("fileData: %s", content)
libs.SuccessMsg(w, text, "文件读取成功") libs.SuccessMsg(w, text, "文件读取成功")
return return
@ -128,21 +129,23 @@ func HandleWriteFile(w http.ResponseWriter, r *http.Request) {
haslink := strings.HasPrefix(string(content), "link::") haslink := strings.HasPrefix(string(content), "link::")
//log.Printf("haslink:%v,fileSecret: %s,isPwdFile:%v,filePwd:%s", haslink, fileSecret, isPwdFile, filePwd) //log.Printf("haslink:%v,fileSecret: %s,isPwdFile:%v,filePwd:%s", haslink, fileSecret, isPwdFile, filePwd)
needPwd := false needPwd := false
if !haslink {
needPwd = true
}
if fileSecret != "" || filePwd != "" { if fileSecret != "" || filePwd != "" {
needPwd = true needPwd = true
} }
if isPwdFile { if isPwdFile {
needPwd = true needPwd = true
} }
if haslink {
needPwd = false
}
log.Printf("needPwd:%v", needPwd)
// 即不是加密用户又不是加密文件 // 即不是加密用户又不是加密文件
if !needPwd { if !needPwd {
// 直接写入新内容 // 直接写入新内容
file.Truncate(0) file.Truncate(0)
file.Seek(0, 0) file.Seek(0, 0)
log.Printf("write file content%v", content)
_, err = file.Write(content) _, err = file.Write(content)
if err != nil { if err != nil {
libs.ErrorMsg(w, "Failed to write file content.") libs.ErrorMsg(w, "Failed to write file content.")

285
godo/libs/filecode.go

@ -1,7 +1,6 @@
package libs package libs
import ( import (
"bytes"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/md5" "crypto/md5"
@ -13,24 +12,9 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"io" "io"
"log"
"strings" "strings"
) )
func GetEncryptedCode(data string) (string, error) {
// 检查是否以 @ 开头
if !strings.HasPrefix(data, "@") {
return "", fmt.Errorf("invalid input format")
}
// 去掉开头的 @
data = data[1:]
// 分割加密的私钥和加密的文本
parts := strings.SplitN(data, "@", 2)
if len(parts) != 2 {
return "", fmt.Errorf("invalid input format")
}
return parts[0], nil
}
func IsEncryptedFile(data string) bool { func IsEncryptedFile(data string) bool {
if len(data) < 2 { if len(data) < 2 {
return false return false
@ -41,13 +25,21 @@ func IsEncryptedFile(data string) bool {
} }
// 去掉开头的 @ // 去掉开头的 @
data = data[1:] data = data[1:]
// 分割加密的私钥和加密的文本 // 分割加密的私钥、二级密码和加密的文本
parts := strings.SplitN(data, "@", 2) parts := strings.SplitN(data, "@", 3)
if len(parts) != 2 { if len(parts) != 3 {
return false return false
} }
// 检查加密私钥和加密文本是否都不为空
hexEncodedPrivateKey := parts[0] hexEncodedPrivateKey := parts[0]
encryptedSecondaryPassword := parts[1]
encryptedText := parts[2]
// 检查各个部分是否都不为空
if hexEncodedPrivateKey == "" || encryptedSecondaryPassword == "" || encryptedText == "" {
return false
}
// 检查十六进制字符串是否有效 // 检查十六进制字符串是否有效
base64Str, err := hex.DecodeString(hexEncodedPrivateKey) base64Str, err := hex.DecodeString(hexEncodedPrivateKey)
if err != nil { if err != nil {
@ -55,25 +47,29 @@ func IsEncryptedFile(data string) bool {
} }
// 尝试将 Base64 字符串解码为字节切片 // 尝试将 Base64 字符串解码为字节切片
_, err = base64.URLEncoding.DecodeString(string(base64Str)) _, err = base64.URLEncoding.DecodeString(string(base64Str))
if err != nil {
return false
}
// 检查二级密码和加密文本是否能被正确解码
_, err = base64.URLEncoding.DecodeString(encryptedSecondaryPassword)
if err != nil {
return false
}
_, err = base64.URLEncoding.DecodeString(encryptedText)
return err == nil return err == nil
} }
// EncodeFile 加密文件 // EncodeFile 加密文件
func EncodeFile(password string, longText string) (string, error) { func EncodeFile(password string, longText string) (string, error) {
privateKey, publicKey, err := GenerateRSAKeyPair(1024) privateKey, publicKey, err := GenerateRSAKeyPair(2048) // 使用2048位密钥更安全
if err != nil { if err != nil {
fmt.Println("生成RSA密钥对失败:", err) fmt.Println("生成RSA密钥对失败:", err)
return "", err return "", err
} }
// 使用公钥加密
encryptedText := ""
if len(longText) > 0 {
encryptedText, err = EncryptLongText(longText, publicKey)
if err != nil {
return "", fmt.Errorf("加密失败:%v", err)
}
}
// 使用密码加密私钥
pwd, err := hashAndMD5(password) pwd, err := hashAndMD5(password)
if err != nil { if err != nil {
return "", fmt.Errorf("加密密码失败:%v", err) return "", fmt.Errorf("加密密码失败:%v", err)
@ -90,33 +86,44 @@ func EncodeFile(password string, longText string) (string, error) {
// 将 Base64 编码后的字符串转换为十六进制字符串 // 将 Base64 编码后的字符串转换为十六进制字符串
hexEncodedPrivateKey := hex.EncodeToString([]byte(base64EncryptedPrivateKey)) hexEncodedPrivateKey := hex.EncodeToString([]byte(base64EncryptedPrivateKey))
return "@" + hexEncodedPrivateKey + "@" + encryptedText, nil // 生成二级密码
secondaryPassword, err := GenerateSecondaryPassword(32) // 32字节的二级密码
if err != nil {
return "", err
}
// 使用公钥加密二级密码
encryptedSecondaryPassword, err := EncryptSecondaryPassword(secondaryPassword, publicKey)
if err != nil {
return "", fmt.Errorf("加密二级密码失败:%v", err)
}
// 使用二级密码加密数据
encryptedText, err := EncryptDataWithCBC(longText, secondaryPassword)
if err != nil {
return "", fmt.Errorf("加密数据失败:%v", err)
}
return "@" + hexEncodedPrivateKey + "@" + encryptedSecondaryPassword + "@" + encryptedText, nil
} }
// DecodeFile 解密文件 // DecodeFile 解密文件
func DecodeFile(password string, encryptedData string) (string, error) { func DecodeFile(password string, encryptedData string) (string, error) {
// 去掉开头的@ // 去掉开头的@
// log.Printf("encryptedData: %s", encryptedData)
if !strings.HasPrefix(encryptedData, "@") { if !strings.HasPrefix(encryptedData, "@") {
return "", fmt.Errorf("无效的加密数据格式") return "", fmt.Errorf("无效的加密数据格式")
} }
encryptedData = encryptedData[1:] encryptedData = encryptedData[1:]
// 分割加密的私钥和加密的文本 // 分割加密的私钥、加密的二级密码和加密的文本
parts := strings.SplitN(encryptedData, "@", 2) parts := strings.SplitN(encryptedData, "@", 3)
log.Printf("parts:%v", parts) if len(parts) != 3 {
if len(parts) == 1 {
return "", nil
}
if len(parts) != 2 {
return "", fmt.Errorf("无效的加密数据格式") return "", fmt.Errorf("无效的加密数据格式")
} }
hexEncodedPrivateKey := parts[0] hexEncodedPrivateKey := parts[0]
encryptedText := parts[1] encryptedSecondaryPassword := parts[1]
if len(encryptedText) == 0 { encryptedText := parts[2]
return "", nil
}
// 将十六进制字符串转换回 Base64 编码字符串 // 将十六进制字符串转换回 Base64 编码字符串
base64DecodedPrivateKey, err := hex.DecodeString(hexEncodedPrivateKey) base64DecodedPrivateKey, err := hex.DecodeString(hexEncodedPrivateKey)
@ -141,8 +148,14 @@ func DecodeFile(password string, encryptedData string) (string, error) {
return "", fmt.Errorf("解密私钥失败:%v", err) return "", fmt.Errorf("解密私钥失败:%v", err)
} }
// 解密文本 // 解密二级密码
decryptedText, err := DecryptLongText(encryptedText, privateKey) secondaryPassword, err := DecryptSecondaryPassword(encryptedSecondaryPassword, privateKey)
if err != nil {
return "", fmt.Errorf("解密二级密码失败:%v", err)
}
// 使用二级密码解密数据
decryptedText, err := DecryptDataWithCBC(encryptedText, secondaryPassword)
if err != nil { if err != nil {
return "", fmt.Errorf("解密文本失败:%v", err) return "", fmt.Errorf("解密文本失败:%v", err)
} }
@ -208,54 +221,6 @@ func hashAndMD5(password string) (string, error) {
return hex.EncodeToString(hasher.Sum(nil)), nil return hex.EncodeToString(hasher.Sum(nil)), nil
} }
// EncryptLongText 使用公钥加密长文本
func EncryptLongText(longText string, publicKey *rsa.PublicKey) (string, error) {
// 分块加密
blockSize := publicKey.N.BitLen()/8 - 2*sha256.Size - 2
chunks := splitIntoChunks([]byte(longText), blockSize)
var encryptedChunks []string
for _, chunk := range chunks {
encryptedChunk, err := EncryptWithPublicKey(chunk, publicKey)
if err != nil {
return "", err
}
encryptedChunks = append(encryptedChunks, encryptedChunk)
}
return strings.Join(encryptedChunks, ":"), nil
}
// DecryptLongText 使用私钥解密长文本
func DecryptLongText(encryptedLongText string, privateKey *rsa.PrivateKey) (string, error) {
// 分块解密
encryptedChunks := strings.Split(encryptedLongText, ":")
var decryptedChunks [][]byte
for _, encryptedChunk := range encryptedChunks {
decryptedChunk, err := DecryptWithPrivateKey(encryptedChunk, privateKey)
if err != nil {
return "", err
}
decryptedChunks = append(decryptedChunks, decryptedChunk)
}
return string(bytes.Join(decryptedChunks, nil)), nil
}
// splitIntoChunks 将数据分割成指定大小的块
func splitIntoChunks(data []byte, chunkSize int) [][]byte {
var chunks [][]byte
for i := 0; i < len(data); i += chunkSize {
end := i + chunkSize
if end > len(data) {
end = len(data)
}
chunks = append(chunks, data[i:end])
}
return chunks
}
// EncryptPrivateKey 使用AES加密RSA私钥 // EncryptPrivateKey 使用AES加密RSA私钥
func EncryptPrivateKey(privateKey *rsa.PrivateKey, password string) (string, error) { func EncryptPrivateKey(privateKey *rsa.PrivateKey, password string) (string, error) {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
@ -317,3 +282,139 @@ func DecryptPrivateKey(encryptedPrivateKey string, password string) (*rsa.Privat
return privateKey, nil return privateKey, nil
} }
// DecryptSecondaryPassword 使用私钥解密二级密码
func DecryptSecondaryPassword(encryptedSecondaryPassword string, privateKey *rsa.PrivateKey) (string, error) {
ciphertext, err := base64.URLEncoding.DecodeString(encryptedSecondaryPassword)
if err != nil {
return "", err
}
plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, privateKey, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}
// DecodeFileWithSecondaryPassword 解密文件
func DecodeFileWithSecondaryPassword(password string, encryptedData string) (string, error) {
// 去掉开头的@
if !strings.HasPrefix(encryptedData, "@") {
return "", fmt.Errorf("无效的加密数据格式")
}
encryptedData = encryptedData[1:]
// 分割加密的二级密码和加密的文本
parts := strings.SplitN(encryptedData, "@", 2)
if len(parts) != 2 {
return "", fmt.Errorf("无效的加密数据格式")
}
encryptedSecondaryPassword := parts[0]
encryptedText := parts[1]
// 使用私钥解密二级密码
privateKey, err := DecryptPrivateKey(password, password)
if err != nil {
return "", fmt.Errorf("解密私钥失败:%v", err)
}
// 使用私钥解密二级密码
secondaryPassword, err := DecryptSecondaryPassword(encryptedSecondaryPassword, privateKey)
if err != nil {
return "", fmt.Errorf("解密二级密码失败:%v", err)
}
// 使用二级密码解密文本
decryptedText, err := DecryptDataWithCBC(encryptedText, secondaryPassword)
if err != nil {
return "", fmt.Errorf("解密文本失败:%v", err)
}
return decryptedText, nil
}
// GenerateSecondaryPassword 生成一个随机的二级密码
func GenerateSecondaryPassword(length int) (string, error) {
b := make([]byte, length)
_, err := rand.Read(b)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// EncryptWithPublicKey 使用公钥加密二级密码
func EncryptSecondaryPassword(secondaryPassword string, publicKey *rsa.PublicKey) (string, error) {
data := []byte(secondaryPassword)
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, publicKey, data, nil)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
// EncryptDataWithCBC 使用二级密码和CBC模式加密数据
func EncryptDataWithCBC(data, secondaryPassword string) (string, error) {
// 确保 secondaryPassword 长度为 32 字节
if len(secondaryPassword) < 32 {
return "", fmt.Errorf("secondary password too short")
}
key := []byte(secondaryPassword[:32])
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
ciphertext := gcm.Seal(nonce, nonce, []byte(data), nil)
return base64.URLEncoding.EncodeToString(ciphertext), nil
}
// DecryptDataWithCBC 使用二级密码和CBC模式解密数据
func DecryptDataWithCBC(encryptedData, secondaryPassword string) (string, error) {
// 确保 secondaryPassword 长度为 32 字节
if len(secondaryPassword) < 32 {
return "", fmt.Errorf("secondary password too short")
}
key := []byte(secondaryPassword[:32])
ciphertext, err := base64.URLEncoding.DecodeString(encryptedData)
if err != nil {
return "", err
}
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return "", fmt.Errorf("ciphertext too short")
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", err
}
return string(plaintext), nil
}

8
godo/libs/filecode_test.go

@ -28,7 +28,7 @@ func TestEncodeFile(t *testing.T) {
} }
func TestDecodeFile(t *testing.T) { func TestDecodeFile(t *testing.T) {
password := "96e79218965eb72c92a549dd5a330112" password := "testpassword"
longText := "This is a test message." longText := "This is a test message."
encryptedData, err := EncodeFile(password, longText) encryptedData, err := EncodeFile(password, longText)
@ -42,13 +42,13 @@ func TestDecodeFile(t *testing.T) {
} }
if decryptedText != longText { if decryptedText != longText {
t.Errorf("Decrypted text does not match original: expected '%s', got '%s'", longText, decryptedText) t.Errorf("Decrypted text does not match original text. Expected: %v, Got: %v", longText, decryptedText)
} }
} }
func TestIsEncryptedFile(t *testing.T) { func TestIsEncryptedFile(t *testing.T) {
password := "testpassword" password := "password"
longText := "This is a test message." longText := "分地方大幅度"
encryptedData, err := EncodeFile(password, longText) encryptedData, err := EncodeFile(password, longText)
if err != nil { if err != nil {

Loading…
Cancel
Save