次の初心者プロジェクトでは、パスワードを生成するだけでなく、パスワードを暗号化して保存する、実際に機能するパスワード ジェネレーターを構築します。
最終的に大きな「main.go」ファイルができてしまわないように、コードを複数のファイルに分割します。
まず、go プロジェクトを初期化し、パスワードの暗号化と復号化のロジックを含む「profile.go」ファイルを作成します。
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 }
ここでは、Enc、Platform、および Password の 3 つのフィールドを持つプロファイル構造体を作成します。 Enc は暗号化されたパスワードを保持し、パスワードを生成しているサービスはプラットフォームに保存され、password は実際に生成されたパスワードを保持します。プロファイル構造体には「encrypt」と「decrypt」の 2 つのメソッドがあります。当社では、パスワードの暗号化と復号化に対称キー暗号化アルゴリズムである AES を使用しています。
次に、パスワードを保存および取得するためのロジックを含む「store.go」ファイルを作成します。
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() }
gob ファイルは人間が正確に判読できるものではないため、保存用に gob ファイルを選択しました。ファイルが公開された場合でも、パスワードは暗号化され、読み取りが非常に困難になるため、安全です。ストア構造体には、gob ファイルをロード、検索、保存するためのメソッドが含まれています。パスワードを辞書に保存します。また、辞書を同時安全にするためにミューテックスも使用します。注意すべき重要な点は、生成されたプレーンなパスワードを保存するのではなく、代わりに暗号化された値を保存することです。
次に、実際にパスワードを生成する関数をいくつか書いてみましょう。 「password.go」ファイルを作成し、次のように入力します
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, "")), "") }
ここでは、さまざまな難易度のパスワードを生成する関数を作成しました。 BasicPassword 関数は、ランダムな小文字の文字列を生成します。 mediaPassword 関数は、basicPassword 関数から文字の一部を取得し、それにランダムな大文字を追加します。 hardPassword 関数は、mediumPassword に対しても同じことを行いますが、数字が追加されます。 xhardPassword も同じことを行い、シンボルを追加します。 shuffleStr が文字列をシャッフルする間、shuffle 関数はスライスに対して期待どおりの処理を実行します。
それでは、すべてをまとめてみましょう。 「main.go」ファイルを作成し、次のように入力します
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) }
フラグを使用して、アプリケーションがどのように動作するかを指定しました。 「--get」でパスワードを取得し、「--set」でパスワードを生成して保存します。パスワードを設定するには、ユーザーはフラグを含む引数を指定して、生成および保存するパスワードの種類をアプリケーションに指示します。パスワードを取得するには、ユーザーは取得するパスワードを指定する引数も提供します。
「go build」を実行してバイナリをビルドし、アプリケーションをテストできるようになりました。
以上が実際に使用できるパスワードジェネレーターを構築しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。