引子
对文件进行加密和解密。
对文件进行压缩处理。
要求和技术选型
界面化操作,独立文件无依赖包,尽可能小。
界面,选择window环境下的最小桌面化,http://github.com/lxn/walk。
加解密算法:选择golang的crypto包。
压缩算法:选择golang的archive/zip。
文件大小:golang编译后文件较大,选用upx进行文件压缩。
实现
界面实现
拖拽文件到程序进行加密和解密。

界面代码
package main
import (
"filepacker/encrypt"
"os"
"path/filepath"
"github.com/lxn/walk"
. "github.com/lxn/walk/declarative"
)
type FileInfo struct {
Name string
Size int64
}
type FileInfoModel struct {
walk.SortedReflectTableModelBase
dirPath string
items []*FileInfo
}
func (m *FileInfoModel) Items() interface{} {
return m.items
}
func NewFileInfoModel() *FileInfoModel {
return new(FileInfoModel)
}
func main() {
var mw *walk.MainWindow
var pwdLE *walk.LineEdit
var checkBoxZip *walk.CheckBox
var encryptPB, decryptPB *walk.PushButton
var tableView *walk.TableView
tableModel := NewFileInfoModel()
MainWindow{
AssignTo: &mw,
Title: "Filepacker 文件加解密工具",
MinSize: Size{320, 240},
Size: Size{480, 320},
Layout: VBox{},
OnDropFiles: func(files []string) {
for _, f := range files {
info, _ := os.Stat(f)
if info.IsDir() {
continue
}
item := &FileInfo{
Name: f,
Size: info.Size(),
}
tableModel.items = append(tableModel.items, item)
}
tableModel.PublishRowsReset()
},
Children: []Widget{
TableView{
AssignTo: &tableView,
StretchFactor: 2,
Columns: []TableViewColumn{
TableViewColumn{
DataMember: "Name",
Title: "文件名",
Width: 192,
},
TableViewColumn{
DataMember: "Size",
Title: "大小",
Format: "%d",
Alignment: AlignFar,
Width: 64,
},
},
Model: tableModel,
OnCurrentIndexChanged: func() {
},
},
Composite{
Layout: HBox{},
Children: []Widget{
Label{
Text: "密码:",
},
LineEdit{
AssignTo: &pwdLE,
Text: "1234567890abcdef",
},
LinkLabel{
MaxSize: Size{100, 0},
Text: `<a id="this" href="#">随机密码</a>`,
OnLinkActivated: func(link *walk.LinkLabelLink) {
uuid := encrypt.NewUUID()
pwdLE.SetText(uuid.String())
},
},
},
},
Composite{
Layout: HBox{},
Children: []Widget{
CheckBox{
AssignTo: &checkBoxZip,
Name: "doZip",
Text: "压缩",
Checked: true,
},
PushButton{
AssignTo: &encryptPB,
Text: "加密",
OnClicked: func() {
pwd := pwdLE.Text()
if pwd == "" || len(pwd) < 8 {
walk.MsgBox(
mw,
"Error",
"请输入足够长度的密码",
walk.MsgBoxOK|walk.MsgBoxIconError)
return
}
for _, f := range tableModel.items {
src := f.Name
dir, fname := filepath.Split(src)
des := filepath.Join(dir, "aes"+fname)
err := encrypt.EncryptFile(src, des, pwd)
if checkBoxZip.Checked() {
err := encrypt.Zip(des, des+".zip")
if err == nil {
os.RemoveAll(des)
}
}
if err == nil {
walk.MsgBox(
mw,
"信息",
"加密成功:"+des,
walk.MsgBoxOK|walk.MsgBoxIconInformation)
} else {
walk.MsgBox(
mw,
"Error",
err.Error(),
walk.MsgBoxOK|walk.MsgBoxIconError)
}
}
tableModel.items = tableModel.items[0:0]
tableModel.PublishRowsReset()
},
},
PushButton{
AssignTo: &decryptPB,
Text: "解密",
OnClicked: func() {
pwd := pwdLE.Text()
if pwd == "" || len(pwd) < 8 {
walk.MsgBox(
mw,
"Error",
"请输入足够长度的密码",
walk.MsgBoxOK|walk.MsgBoxIconError)
return
}
for _, f := range tableModel.items {
src := f.Name
dir, fname := filepath.Split(src)
des := filepath.Join(dir, "des"+fname)
err := encrypt.DecryptFile(src, des, pwd)
if err == nil {
walk.MsgBox(
mw,
"信息",
"解密成功:"+des,
walk.MsgBoxOK|walk.MsgBoxIconInformation)
} else {
walk.MsgBox(
mw,
"Error",
err.Error(),
walk.MsgBoxOK|walk.MsgBoxIconError)
}
}
tableModel.items = tableModel.items[0:0]
tableModel.PublishRowsReset()
},
},
},
},
},
}.Run()
}
加解密代码
package encrypt
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/binary"
"errors"
"io"
"os"
"strings"
)
const (
FrameSize = 2048
FPFileVersion1 uint16 = 0xfe
FPFileVersion2 uint16 = 0xef
FPFileVersion3 uint8 = 1
FDInfo string = "蒹葭苍苍,白露为霜。所谓伊人,在水一方。"
)
//只能16、24、32共三种情况
func pwdPadding(pwd []byte) []byte {
maxSize := 32
pwdSize := len(pwd)
if pwdSize == 32 {
return pwd
} else if pwdSize > 32 {
return pwd[0:32]
} else {
padding := maxSize - pwdSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(pwd, padtext...)
}
// if pwdSize%minSize == 0 {
// return pwd
// } else if pwdSize <= minSize {
// padding := minSize - pwdSize
// padtext := bytes.Repeat([]byte{byte(padding)}, padding)
// return append(pwd, padtext...)
// } else {
// padding := minSize - pwdSize%minSize
// padtext := bytes.Repeat([]byte{byte(padding)}, padding)
// return append(pwd, padtext...)
// }
}
func EncryptFile(src, dest, pwd string) error {
correctPwd := pwdPadding([]byte(pwd))
srcfile, err := os.Open(src)
if err != nil {
return err
}
defer srcfile.Close()
destfile, err := os.Create(dest)
if err != nil {
return err
}
defer destfile.Close()
//写文件头
var encryptedInfo = AesEncryptCBC([]byte(FDInfo), correctPwd)
var fdHeader []byte = make([]byte, 6)
binary.BigEndian.PutUint16(fdHeader[0:], FPFileVersion1)
binary.BigEndian.PutUint16(fdHeader[2:], FPFileVersion2)
binary.BigEndian.PutUint16(fdHeader[4:], uint16(len(encryptedInfo)))
destfile.Write(fdHeader)
destfile.Write(encryptedInfo)
//加密处理
frameBuf := make([]byte, FrameSize) //一次读取多少个字节
sizeBuf := make([]byte, 2)
for {
n, err := srcfile.Read(frameBuf)
if err != nil { //遇到任何错误立即返回,并忽略 EOF 错误信息
if err == io.EOF {
break
}
return err
}
if n <= 0 {
break
}
encryptedFrame := AesEncryptCBC(frameBuf[:n], correctPwd)
binary.BigEndian.PutUint16(sizeBuf[:], uint16(len(encryptedFrame)))
_, err = destfile.Write(sizeBuf)
if err != nil {
return err
}
_, err = destfile.Write(encryptedFrame)
if err != nil {
return err
}
}
return nil
}
func DecryptFile(src, dest, pwd string) error {
correctPwd := pwdPadding([]byte(pwd))
srcfile, err := os.Open(src)
if err != nil {
return err
}
defer srcfile.Close()
//读文件头
fdHeader := make([]byte, 6)
srcfile.Read(fdHeader)
v1 := binary.BigEndian.Uint16(fdHeader[0:])
v2 := binary.BigEndian.Uint16(fdHeader[2:])
encryptedInfoSize := binary.BigEndian.Uint16(fdHeader[4:])
if v1 != FPFileVersion1 && v2 != FPFileVersion2 {
return errors.New("Not a FP file")
}
encryptedInfo := make([]byte, encryptedInfoSize)
srcfile.Read(encryptedInfo)
descryptedFDInfo := AesEncryptCBC([]byte(FDInfo), correctPwd)
if strings.Compare(string(descryptedFDInfo), string(encryptedInfo)) != 0 {
return errors.New("password wrong")
}
sizeBuf := make([]byte, 2)
destfile, err := os.Create(dest)
if err != nil {
return err
}
defer destfile.Close()
for {
n, err := srcfile.Read(sizeBuf)
if err != nil { //遇到任何错误立即返回,并忽略 EOF 错误信息
if err == io.EOF {
break
}
return err
}
if n <= 0 {
break
}
encryptedFrameSize := binary.BigEndian.Uint16(sizeBuf)
buf := make([]byte, encryptedFrameSize)
n, err = srcfile.Read(buf)
if err != nil { //遇到任何错误立即返回,并忽略 EOF 错误信息
if err == io.EOF {
break
}
return err
}
if n <= 0 {
break
}
d := AesDecryptCBC(buf[:n], correctPwd)
destfile.Write(d)
}
return nil
}
// =================== CBC ======================
func AesEncryptCBC(origData []byte, key []byte) (encrypted []byte) {
// 分组秘钥
// NewCipher该函数限制了输入k的长度必须为16, 24或者32
block, _ := aes.NewCipher(key)
blockSize := block.BlockSize() // 获取秘钥块的长度
origData = pkcs5Padding(origData, blockSize) // 补全码
blockMode := cipher.NewCBCEncrypter(block, key[:blockSize]) // 加密模式
encrypted = make([]byte, len(origData)) // 创建数组
blockMode.CryptBlocks(encrypted, origData) // 加密
return encrypted
}
func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte) {
block, _ := aes.NewCipher(key) // 分组秘钥
blockSize := block.BlockSize() // 获取秘钥块的长度
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
decrypted = make([]byte, len(encrypted)) // 创建数组
blockMode.CryptBlocks(decrypted, encrypted) // 解密
decrypted = pkcs5UnPadding(decrypted) // 去除补全码
return decrypted
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// =================== ECB ======================
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
newCipher, _ := aes.NewCipher(generateKey(key))
length := (len(origData) + aes.BlockSize) / aes.BlockSize
plain := make([]byte, length*aes.BlockSize)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, newCipher.BlockSize(); bs <= len(origData); bs, be = bs+newCipher.BlockSize(), be+newCipher.BlockSize() {
newCipher.Encrypt(encrypted[bs:be], plain[bs:be])
}
return encrypted
}
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
newCipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
for bs, be := 0, newCipher.BlockSize(); bs < len(encrypted); bs, be = bs+newCipher.BlockSize(), be+newCipher.BlockSize() {
newCipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}
trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}
return decrypted[:trim]
}
func generateKey(key []byte) (genKey []byte) {
genKey = make([]byte, 16)
copy(genKey, key)
for i := 16; i < len(key); {
for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
genKey[j] ^= key[i]
}
}
return genKey
}
gowalk准备syso文件
准备manifest文件
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" processorArchitecture="*" name="SomeFunkyNameHere" type="win32"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>
执行命令:
go get github.com/akavel/rsrc
rsrc -manifest filepacker.exe.manifest -o rsrc.syso
编译文件大小处理
通过指定go的编译参数,缩减编译后文件大小。
go build -ldflags="-s -w -H windowsgui"
通过upx进行文件压缩。
upx filepacker.exe
环境清单
GoLang1.17.6 windows/amd64 The Go Programming Language
制成文件
filepacker.exe
1.7M
· 百度网盘
遗留问题
golang编译出的文件目前最小压缩到1.7m,目前没有找到更好的解决方式进一步减小文件尺寸,网上有说可通过go的编译过程中控制引用的包控制尺寸,感觉比较麻烦,没有实际验证。
大神提出增加自解压功能方便使用,考虑到golang编译后的文件较大,准备选用vc++进行实现。