为了账号安全,请及时绑定邮箱和手机立即绑定

让我们用Go语言做一个真正能用的密码生成器吧

接下来我们要做一个初学者项目,我们将构建一个密码生成和保存工具,它不仅生成密码,还能加密和保存密码——所以它实际上是有用的。

我们将把代码分成不同的文件,这样我们就不会有一个庞大的"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 来构建一个可执行文件,然后测试程序。

密码生成器示例(点击图片查看大图)

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消