Rumah > Artikel > pembangunan bahagian belakang > Mari bina penjana kata laluan yang sebenarnya boleh kita gunakan
Untuk projek pemula kami yang seterusnya, kami akan membina penjana kata laluan yang bukan sahaja menjana kata laluan tetapi menyulitkan dan menyimpannya - supaya ia benar-benar berfungsi.
Kami akan membahagikan kod kami kepada fail yang berbeza supaya kami tidak mendapat fail "main.go" yang besar.
Mula-mula, kami memulakan projek go dan mencipta fail "profile.go" yang akan mengandungi logik untuk menyulitkan dan menyahsulit kata laluan.
package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "errors" "io" ) // must be 32 characters var key = "askdjasjdbreonfsdfibsdhfgsdfhboo" var ErrMalformedEncryption = errors.New("malformed encryption") // password in small letters so it is not stored type profile struct { Enc, Platform, password 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.password), 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.password = string(password) return nil }
Di sini kami mencipta struktur profil yang mempunyai 3 medan - Enc, Platform dan kata laluan. Enc akan menyimpan kata laluan yang disulitkan, perkhidmatan yang kami hasilkan kata laluan akan disimpan dalam Platform dan kata laluan akan memegang kata laluan yang dijana sebenar. Struktur profil mempunyai 2 kaedah "menyulitkan" dan "menyahsulit". Kami menggunakan AES - algoritma penyulitan kunci simetri untuk menyulitkan dan menyahsulit kata laluan kami.
Seterusnya kami mencipta fail "store.go" yang mengandungi logik untuk menyimpan dan mendapatkan kata laluan.
package main import ( "encoding/gob" "errors" "os" "sync" ) const filename = "profile.bin" var ( ErrInvalidArgs = errors.New("invalid args") ErrNotFound = errors.New("not found") ) type store struct { sync.RWMutex data map[string]*profile } 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() }
Kami memilih fail gob untuk storan kerana ia tidak betul-betul boleh dibaca oleh manusia. Jika fail itu pernah terdedah, kata laluan anda selamat kerana ia akan disulitkan dan sangat sukar dibaca. Struktur stor mengandungi kaedah untuk memuatkan, mencari dan menyimpan ke fail gob. Kami menyimpan kata laluan dalam kamus. Kami juga menggunakan mutex untuk menjadikan kamus serentak selamat. Perkara penting yang perlu diambil perhatian ialah kami tidak akan menyimpan kata laluan yang dijana biasa - sebaliknya kami akan menyimpan nilai yang disulitkan.
Sekarang mari kita tulis beberapa fungsi yang sebenarnya akan menjana kata laluan. Buat fail "password.go" dan taip yang berikut
package main import ( "math" "math/rand" "slices" "strings" ) const ( half = .5 onethird = .3 onefourth = .25 ) var ( randlowers = randFromSeed(lowers()) randuppers = randFromSeed(uppers()) randdigits = randFromSeed(digits()) randsymbols = randFromSeed(symbols()) ) var basicPassword = randlowers func mediumPassword(n int) string { frac := math.Round(float64(n) * half) pwd := basicPassword(n) return pwd[:n-int(frac)] + randuppers(int(frac)) } func hardPassword(n int) string { pwd := mediumPassword(n) frac := math.Round(float64(n) * onethird) return pwd[:n-int(frac)] + randdigits(int(frac)) } func xhardPassword(n int) string { pwd := hardPassword(n) frac := math.Round(float64(n) * onefourth) 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 lowers() string { var b strings.Builder for i := 'a'; i < 'a'+26; i++ { b.WriteRune(i) } return b.String() } func uppers() string { var b strings.Builder for i := 'A'; i < 'A'+26; i++ { b.WriteRune(i) } return b.String() } func symbols() 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 digits() 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, "")), "") }
Di sini kami telah menulis fungsi yang menjana kata laluan pada tahap kesukaran yang berbeza. Fungsi basicPassword menjana rentetan huruf kecil rawak. Fungsi mediumPassword mengambil sebahagian kecil daripada aksara daripada fungsi basicPassword dan menambah huruf besar rawak kepadanya. Fungsi hardPassword melakukan perkara yang sama untuk mediumPassword tetapi menambah digit padanya. xhardPassword melakukan perkara yang sama dan menambah simbol. Fungsi shuffle melakukan apa yang anda jangkakan pada kepingan manakala shuffleStr mengocok rentetan.
Sekarang mari kita susun semuanya. Cipta fail "main.go" dan taip yang berikut
package main import ( "errors" "flag" "fmt" "log" "regexp" "strconv" "strings" ) var usage = ` Usage ----- --get platform=[string] - Gets saved password for a platform --set platform=[string] len=[int] level=(basic|medium|hard|xhard) - Creates and saves a password ` 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, "get password for platform") flag.BoolVar(&c.set, "set", false, "set password for platform") 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("password len cannot be less than 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("could not initialize store: %v", err) } c := createCommands() password, err := c.exec(store) if err != nil { log.Fatalf("could not execute flag commands: %v", err) } fmt.Printf("password: %s\n", password) }
Kami menggunakan bendera untuk menentukan cara kami menjangkakan aplikasi akan berfungsi. "--get" untuk mendapatkan kata laluan dan "--set" untuk menjana dan menyimpan kata laluan. Untuk menetapkan kata laluan, pengguna menyediakan hujah dengan bendera untuk mengarahkan aplikasi tentang jenis kata laluan untuk menjana dan menyimpan. Untuk mendapatkan kata laluan, pengguna turut menyediakan hujah untuk menentukan kata laluan untuk diambil semula.
Anda kini boleh menjalankan "go build" untuk membina binari dan menguji aplikasi.
Atas ialah kandungan terperinci Mari bina penjana kata laluan yang sebenarnya boleh kita gunakan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!