接下来我们要做一个初学者项目,我们将构建一个密码生成和保存工具,它不仅生成密码,还能加密和保存密码——所以它实际上是有用的。
我们将把代码分成不同的文件,这样我们就不会有一个庞大的"main.go"文件。
首先,我们创建了一个名为"profile.go"的文件,用于处理密码的加密和解密。
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"errors"
"io"
)
// 必须是32个字符
var key = "askdjasjdbreonfsdfibsdhfgsdfhboo"
var ErrMalformedEncryption = errors.New("错误的加密格式")
// 密码仅用小写字母表示,以防止明文存储
type profile struct {
Enc string
Platform string
密码 string
}
func (p *profile) encrypt() error {
block, err := aes.NewCipher([]byte(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
}
enc := gcm.Seal(nonce, nonce, []byte(p.密码), nil)
p.Enc = hex.EncodeToString(enc)
return nil
}
func (p *profile) decrypt() error {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return err
}
nsize := gcm.NonceSize()
if len(p.Enc) < nsize {
return ErrMalformedEncryption
}
enc, err := hex.DecodeString(p.Enc)
if err != nil {
return err
}
password, err := gcm.Open(nil, enc[:nsize], enc[nsize:], nil)
if err != nil {
return err
}
p.密码 = string(password)
return nil
}
切换到全屏 退出全屏
在这里我们创建了一个包含3个字段的配置结构体——Enc、Platform和password。Enc将保存加密密码,我们将服务的名称存储在Platform中,password则保存实际生成的密码。此配置结构体包含两个方法"encrypt"和"decrypt"。我们使用AES这种对称密钥加密算法来对密码进行加密和解密。
接下来我们将创建一个"store.go"文件,该文件包含用于存储和检索密码的功能。
package main
import (
"encoding/gob"
"errors"
"os"
"sync"
)
const filename = "profile.bin"
var (
ErrInvalidArgs = errors.New("参数无效")
ErrNotFound = errors.New("未找到记录")
)
type store struct {
sync.RWMutex
data map[string]*profile
}
// 创建一个新的store实例并加载数据
func newStore() (*store, error) {
s := &store{
data: make(map[string]*profile),
}
if err := s.load(); err != nil {
return nil, err
}
return s, nil
}
// 加载数据到内存
func (s *store) load() error {
flags := os.O_CREATE | os.O_RDONLY
f, err := os.OpenFile(filename, flags, 0644)
if err != nil {
return err
}
defer f.Close()
info, err := f.Stat()
if err != nil {
return err
}
if info.Size() == 0 {
return nil
}
return gob.NewDecoder(f).Decode(&s.data)
}
// 保存数据到文件
func (s *store) save() error {
f, err := os.OpenFile(filename, os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
return gob.NewEncoder(f).Encode(s.data)
}
// 根据平台查找密码
func (s *store) find(platform string) (string, error) {
s.RLock()
defer s.RUnlock()
p, ok := s.data[platform]
if !ok {
return "", ErrNotFound
}
if err := p.decrypt(); err != nil {
return "", err
}
return p.password, nil
}
// 增加一个新的平台及其密码
func (s *store) add(platform, password string) error {
if platform == "" {
return ErrInvalidArgs
}
p := &profile{
Platform: platform,
password: password,
}
if err := p.encrypt(); err != nil {
return err
}
s.Lock()
defer s.Unlock()
s.data[platform] = p
return s.save()
}
全屏,退出
我们选择使用 gob 文件来存储数据,因为它们不容易被人类直接阅读。如果文件被暴露,你的密码仍然安全,因为它们会被加密,并且很难被理解和读取。store
结构体包含加载、查找和保存到 gob 文件的方法。我们把密码存储在一个字典里。我们还使用互斥锁(mutex)来确保字典在多线程环境下使用时的安全性。需要注意的是,我们不会存储未加密的密码——我们存储的是它的加密版本。
我们现在来写两个函数生成密码吧。创建一个 "password.go" 文件并输入以下内容到文件中:
package main
import (
"math"
"math/rand"
"slices"
"strings"
)
const (
半 = .5
三分之一 = .3
四分之一 = .25
)
var (
randlowers = randFromSeed(lowers())
randuppers = randFromSeed(uppers())
randdigits = randFromSeed(digits())
randsymbols = randFromSeed(symbols())
)
var basicPassword = randlowers
func 中等强度密码(n int) string {
frac := math.Round(float64(n) * 半)
pwd := basicPassword(n)
return pwd[:n-int(frac)] + randuppers(int(frac))
}
func 强密码(n int) string {
pwd := 中等强度密码(n)
frac := math.Round(float64(n) * 三分之一)
return pwd[:n-int(frac)] + randdigits(int(frac))
}
func 极强密码(n int) string {
pwd := 强密码(n)
frac := math.Round(float64(n) * 四分之一)
return pwd[:n-int(frac)] + randsymbols(int(frac))
}
func randFromSeed(seed string) func(int) string {
return func(n int) string {
var b strings.Builder
for range n {
b.WriteByte(seed[rand.Intn(len(seed))])
}
return b.String()
}
}
func 小写字母() string {
var b strings.Builder
for i := 'a'; i < 'a'+26; i++ {
b.WriteRune(i)
}
return b.String()
}
func 大写字母() string {
var b strings.Builder
for i := 'A'; i < 'A'+26; i++ {
b.WriteRune(i)
}
return b.String()
}
func 符号() string {
var b strings.Builder
for i := '!'; i < '!'+14; i++ {
b.WriteRune(i)
}
for i := ':'; i < ':'+6; i++ {
b.WriteRune(i)
}
for i := '['; i < '['+5; i++ {
b.WriteRune(i)
}
for i := '{'; i < '{'+3; i++ {
b.WriteRune(i)
}
return b.String()
}
func 数字() string {
var b strings.Builder
for i := '0'; i < '0'+9; i++ {
b.WriteRune(i)
}
return b.String()
}
func shuffle[T any](ts []T) []T {
cloned := slices.Clone(ts)
rand.Shuffle(len(cloned), func(i, j int) {
cloned[i], cloned[j] = cloned[j], cloned[i]
})
return cloned
}
func shuffleStr(s string) string {
return strings.Join(shuffle(strings.Split(s, "")), "")
}
全屏模式, 退出全屏
这里我们编写了一些不同难度级别的密码生成函数。basicPassword
函数生成随机的小写字母。mediumPassword
函数从 basicPassword
函数中获取一部分字符,并添加一些随机的大写字母。hardPassword
函数在 mediumPassword
的基础上添加一些数字。xhardPassword
函数也在 hardPassword
的基础上添加一些符号。shuffle
函数会像你期望的那样打乱切片,而 shuffleStr
函数则是打乱字符串。
现在让我们把所有的东西整合到一起。创建一个名为 "main.go" 的文件,并在其中输入以下内容:
package main
import (
"errors"
"flag"
"fmt"
"log"
"regexp"
"strconv"
"strings"
)
var usage = `
用法
-----
--get platform=[字符串值] - 获取平台的保存密码
--set platform=[字符串值] len=[整数值] level=(basic|medium|hard|xhard) - 创建并保存密码
`
var ErrUsage = errors.New(usage)
var pattern = regexp.MustCompile(`\S+=\S+`)
type level int
const (
_ level = iota
level_basic
level_medium
level_hard
level_xhard
)
var level_key = map[string]level{
"basic": level_basic,
"medium": level_medium,
"hard": level_hard,
"xhard": level_xhard,
}
type commands struct {
get, set bool
}
func createCommands() (c commands) {
flag.BoolVar(&c.get, "get", false, "获取平台的密码")
flag.BoolVar(&c.set, "set", false, "设置平台的密码")
flag.Parse()
return
}
func (c commands) exec(store *store) (string, error) {
switch {
case c.get:
return c.getPassword(store)
case c.set:
return c.setPassword(store)
default:
return "", ErrUsage
}
}
func (c commands) getPassword(store *store) (string, error) {
params, err := c.parse()
if err != nil {
return "", err
}
return store.find(params["platform"])
}
func (c commands) setPassword(store *store) (string, error) {
params, err := c.parse()
if err != nil {
return "", err
}
var password string
n, err := strconv.Atoi(params["len"])
if err != nil {
return "", err
}
if n < 8 {
return "", fmt.Errorf("密码长度不能少于8个字符")
}
switch level_key[params["level"]] {
case level_basic:
password = basicPassword(n)
case level_medium:
password = mediumPassword(n)
case level_hard:
password = hardPassword(n)
case level_xhard:
password = xhardPassword(n)
default:
return "", ErrUsage
}
password = shuffleStr(password)
if err := store.add(params["platform"], password); err != nil {
return "", err
}
return password, nil
}
func (c commands) parse() (map[string]string, error) {
args := flag.Args()
if len(args) == 0 {
return nil, ErrUsage
}
params := make(map[string]string)
for i := range args {
if !pattern.MatchString(args[i]) {
return nil, ErrUsage
}
parts := strings.Split(args[i], "=")
params[parts[0]] = parts[1]
}
return params, nil
}
func main() {
store, err := newStore()
if err != nil {
log.Fatalf("无法初始化数据库: %v", err)
}
c := createCommands()
password, err := c.exec(store)
if err != nil {
log.Fatalf("操作失败: %v", err)
}
fmt.Printf("密码为: %s\n", password)
}
全屏。退出全屏。
我们用标志来指定应用程序的行为。“--get”用来获取密码,“--set”用来生成并保存密码。要设置密码,用户需提供标志参数来指定要生成和保存的密码类型。要获取密码,用户需提供参数来指定要检索的密码。
你可以运行 go build
来构建一个可执行文件,然后测试程序。
共同学习,写下你的评论
评论加载中...
作者其他优质文章