238 lines
5.7 KiB
Go
238 lines
5.7 KiB
Go
|
|
package lib
|
||
|
|
|
||
|
|
import (
|
||
|
|
"errors"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"apigo.cc/go/crypto"
|
||
|
|
"apigo.cc/go/crypto-sm"
|
||
|
|
"apigo.cc/go/file"
|
||
|
|
"apigo.cc/go/safe"
|
||
|
|
)
|
||
|
|
|
||
|
|
type KeySet struct {
|
||
|
|
Name string
|
||
|
|
buf *safe.SafeBuf // Holds the 128-byte obfuscated key
|
||
|
|
}
|
||
|
|
|
||
|
|
// Close safely erases the memory buffer.
|
||
|
|
func (k *KeySet) Close() {
|
||
|
|
if k == nil || k.buf == nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
k.buf.Close()
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetRaw extracts the actual 32-byte Key and 12-byte IV from the obfuscated buffer.
|
||
|
|
func (k *KeySet) GetRaw() ([]byte, []byte, error) {
|
||
|
|
if k == nil || k.buf == nil {
|
||
|
|
return nil, nil, errors.New("invalid keyset")
|
||
|
|
}
|
||
|
|
|
||
|
|
plain := k.buf.Open()
|
||
|
|
defer plain.Close()
|
||
|
|
|
||
|
|
key, iv, err := UnpackMemoryKey(plain.Data)
|
||
|
|
if err != nil {
|
||
|
|
return nil, nil, err
|
||
|
|
}
|
||
|
|
return key, iv, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewSymmetric creates a symmetric cipher from the keyset using the specified algorithm.
|
||
|
|
func (k *KeySet) NewSymmetric(algo string) (*crypto.Symmetric, error) {
|
||
|
|
key, iv, err := k.GetRaw()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer safe.ZeroMemory(key)
|
||
|
|
defer safe.ZeroMemory(iv)
|
||
|
|
|
||
|
|
switch strings.ToLower(algo) {
|
||
|
|
case "aes-gcm", "gcm", "":
|
||
|
|
return crypto.NewAESGCMWithoutEraseKey(key, iv[:12])
|
||
|
|
case "aes-cbc", "cbc":
|
||
|
|
cbcIv := iv
|
||
|
|
if len(cbcIv) == 12 {
|
||
|
|
cbcIv = append(cbcIv, 0, 0, 0, 0)
|
||
|
|
}
|
||
|
|
return crypto.NewAESCBCWithoutEraseKey(key, cbcIv)
|
||
|
|
case "sm4-gcm", "sm4":
|
||
|
|
return sm.NewSM4GCMWithoutEraseKey(key, iv[:12])
|
||
|
|
case "sm4-cbc":
|
||
|
|
cbcIv := iv
|
||
|
|
if len(cbcIv) == 12 {
|
||
|
|
cbcIv = append(cbcIv, 0, 0, 0, 0)
|
||
|
|
}
|
||
|
|
return sm.NewSM4CBCWithoutEraseKey(key, cbcIv)
|
||
|
|
default:
|
||
|
|
return nil, errors.New("unsupported algorithm: " + algo)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// CreateKeystore creates a new master key, obfuscates it, encrypts it with the master password, and saves it.
|
||
|
|
func CreateKeystore(name string, masterPassword []byte) (*KeySet, error) {
|
||
|
|
if name == "" {
|
||
|
|
return nil, errors.New("key name cannot be empty")
|
||
|
|
}
|
||
|
|
|
||
|
|
keyPath := GetKeystorePath()
|
||
|
|
if err := EnsureDir(keyPath); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
filePath := filepath.Join(keyPath, name)
|
||
|
|
if file.Exists(filePath) {
|
||
|
|
return nil, errors.New("key exists")
|
||
|
|
}
|
||
|
|
|
||
|
|
key, err := CryptoRandBytes(32)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
iv, err := CryptoRandBytes(12)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pack to 128 bytes
|
||
|
|
memKeyBuf, err := PackMemoryKey(key, iv)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Encrypt the 128 bytes with master password
|
||
|
|
salt, _ := CryptoRandBytes(16)
|
||
|
|
fileIv, _ := CryptoRandBytes(12)
|
||
|
|
|
||
|
|
passCopy := append([]byte(nil), masterPassword...)
|
||
|
|
saltCopy := append([]byte(nil), salt...)
|
||
|
|
sym, err := crypto.NewAESGCMByPassword(passCopy, saltCopy)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
encData, err := sym.EncryptBytes(memKeyBuf)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Pack file header
|
||
|
|
header, cipherPadding, err := PackFileHeader(salt, fileIv)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
// Construct final file content: Header (256) + Padding + Ciphertext
|
||
|
|
paddingBuf, _ := CryptoRandBytes(cipherPadding)
|
||
|
|
|
||
|
|
finalBuf := append([]byte(nil), header...)
|
||
|
|
finalBuf = append(finalBuf, paddingBuf...)
|
||
|
|
finalBuf = append(finalBuf, encData...)
|
||
|
|
|
||
|
|
// Write raw binary
|
||
|
|
if err := file.WriteBytes(filePath, finalBuf); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return &KeySet{
|
||
|
|
Name: name,
|
||
|
|
buf: safe.NewSafeBufAndErase(memKeyBuf),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// LoadKeystore loads and decrypts a master key from disk.
|
||
|
|
func LoadKeystore(name string, masterPassword []byte) (*KeySet, error) {
|
||
|
|
if name == "default" {
|
||
|
|
return GetDefaultKeySet(), nil
|
||
|
|
}
|
||
|
|
keyPath := GetKeystorePath()
|
||
|
|
filePath := filepath.Join(keyPath, name)
|
||
|
|
|
||
|
|
if !file.Exists(filePath) {
|
||
|
|
return nil, errors.New("key not found")
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := file.ReadBytes(filePath)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(data) < FileHeaderSize {
|
||
|
|
return nil, errors.New("invalid key file format (too short)")
|
||
|
|
}
|
||
|
|
|
||
|
|
header := data[:FileHeaderSize]
|
||
|
|
salt, _, cipherPadding, err := UnpackFileHeader(header)
|
||
|
|
if err != nil {
|
||
|
|
return nil, errors.New("invalid key file header")
|
||
|
|
}
|
||
|
|
|
||
|
|
cipherStart := FileHeaderSize + cipherPadding
|
||
|
|
if len(data) <= cipherStart {
|
||
|
|
return nil, errors.New("invalid key file format (no ciphertext)")
|
||
|
|
}
|
||
|
|
|
||
|
|
cipherText := data[cipherStart:]
|
||
|
|
|
||
|
|
passCopy := append([]byte(nil), masterPassword...)
|
||
|
|
sym, err := crypto.NewAESGCMByPassword(passCopy, salt)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
dec, err := sym.DecryptBytes(cipherText)
|
||
|
|
if err != nil {
|
||
|
|
// --- 🛡️ 诱饵模式 (Decoy Mode) 🛡️ ---
|
||
|
|
// 密码错误不报错,而是返回一个随机生成的 KeySet。
|
||
|
|
// 这会让攻击者无法通过报错来判断暴力破解是否成功。
|
||
|
|
// 同时增加 1.5s 延迟,大幅降低自动化破解效率。
|
||
|
|
time.Sleep(1500 * time.Millisecond)
|
||
|
|
garbage, _ := CryptoRandBytes(MemoryKeySize)
|
||
|
|
return &KeySet{
|
||
|
|
Name: name,
|
||
|
|
buf: safe.NewSafeBufAndErase(garbage),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(dec) != MemoryKeySize {
|
||
|
|
return nil, errors.New("corrupted key data (invalid length)")
|
||
|
|
}
|
||
|
|
|
||
|
|
return &KeySet{
|
||
|
|
Name: name,
|
||
|
|
buf: safe.NewSafeBufAndErase(dec),
|
||
|
|
}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// HasMasterPassword checks if there are any keys in the keystore.
|
||
|
|
// If there are none, we might prompt the user to create a master password.
|
||
|
|
func HasMasterPassword() bool {
|
||
|
|
// In the new design, the master password is just a mental concept that decrypts individual files.
|
||
|
|
// If there are any files in the keystore, it implies the system is initialized.
|
||
|
|
keyPath := GetKeystorePath()
|
||
|
|
if !file.Exists(keyPath) {
|
||
|
|
return false
|
||
|
|
}
|
||
|
|
files, _ := file.ReadDir(keyPath)
|
||
|
|
return len(files) > 0
|
||
|
|
}
|
||
|
|
|
||
|
|
const (
|
||
|
|
DefaultKey = "?GQ$0K0GgLdO=f+~L68PLm$uhKr4'=tV"
|
||
|
|
DefaultIV = "VFs7@sK61cj^f?HZ"
|
||
|
|
)
|
||
|
|
|
||
|
|
// GetDefaultKeySet returns a pre-defined keyset that doesn't require a password.
|
||
|
|
func GetDefaultKeySet() *KeySet {
|
||
|
|
key := []byte(DefaultKey)
|
||
|
|
iv := []byte(DefaultIV)
|
||
|
|
memKeyBuf, _ := PackMemoryKey(key, iv)
|
||
|
|
return &KeySet{
|
||
|
|
Name: "default",
|
||
|
|
buf: safe.NewSafeBufAndErase(memKeyBuf),
|
||
|
|
}
|
||
|
|
}
|