github.com/GGP1/kure@v0.8.4/commands/gen/phrase/phrase.go (about) 1 package phrase 2 3 import ( 4 "fmt" 5 "math" 6 "strings" 7 8 cmdutil "github.com/GGP1/kure/commands" 9 "github.com/GGP1/kure/terminal" 10 11 "github.com/GGP1/atoll" 12 13 "github.com/awnumar/memguard" 14 "github.com/pkg/errors" 15 "github.com/spf13/cobra" 16 ) 17 18 var ( 19 thousand = math.Pow(10, 3) 20 million = math.Pow(10, 6) 21 billion = math.Pow(10, 9) 22 trillion = math.Pow(10, 12) 23 quadrillion = math.Pow(10, 15) 24 quintillion = math.Pow(10, 18) 25 sextillion = math.Pow(10, 21) 26 ) 27 28 const ( 29 minute = 60 30 hour = minute * 60 31 day = hour * 24 32 month = day * 30 33 year = month * 12 34 decade = year * 10 35 century = decade * 10 36 millennium = century * 10 37 ) 38 39 const example = ` 40 * Generate a random passphrase 41 kure gen phrase -l 8 -L WordList -s & 42 43 * Generate and show QR code 44 kure gen phrase -l 5 -q 45 46 * Generate, copy and mute standard output 47 kure gen -l 7 -cm` 48 49 type phraseOptions struct { 50 list, separator string 51 incl, excl []string 52 copy, mute, qr bool 53 length uint64 54 } 55 56 // NewCmd returns a new command. 57 func NewCmd() *cobra.Command { 58 opts := phraseOptions{} 59 cmd := &cobra.Command{ 60 Use: "phrase", 61 Short: "Generate a random passphrase", 62 Long: `Generate a random passphrase. 63 64 Keyspace is the number of possible combinations of the passphrase. 65 Average time taken to crack is based on a brute force attack scenario where the guesses per second is 1 trillion.`, 66 Aliases: []string{"passphrase"}, 67 Example: example, 68 RunE: runPhrase(&opts), 69 PostRun: func(cmd *cobra.Command, args []string) { 70 // Reset variables (session) 71 opts = phraseOptions{ 72 separator: " ", 73 } 74 }, 75 } 76 77 f := cmd.Flags() 78 f.BoolVarP(&opts.copy, "copy", "c", false, "copy the passphrase to the clipboard") 79 f.Uint64VarP(&opts.length, "length", "l", 0, "number of words") 80 f.StringVarP(&opts.separator, "separator", "s", " ", "character that separates each word") 81 f.StringVarP(&opts.list, "list", "L", "WordList", "passphrase list used {NoList|WordList|SyllableList}") 82 f.StringSliceVarP(&opts.incl, "include", "i", nil, "words to include in the passphrase") 83 f.StringSliceVarP(&opts.excl, "exclude", "e", nil, "words to exclude from the passphrase") 84 f.BoolVarP(&opts.qr, "qr", "q", false, "show QR code on terminal") 85 f.BoolVarP(&opts.mute, "mute", "m", false, "mute standard output when the passphrase is copied") 86 87 return cmd 88 } 89 90 func runPhrase(opts *phraseOptions) cmdutil.RunEFunc { 91 return func(cmd *cobra.Command, args []string) error { 92 if opts.length < 1 || opts.length > math.MaxUint64 { 93 return cmdutil.ErrInvalidLength 94 } 95 96 // Use Wordlist list as default 97 l := atoll.WordList 98 if opts.list != "" { 99 opts.list = strings.ReplaceAll(opts.list, " ", "") 100 101 switch strings.ToLower(opts.list) { 102 case "nolist", "no": 103 l = atoll.NoList 104 105 case "wordlist", "word": 106 // Do nothing as it's the default 107 108 case "syllablelist", "syllable": 109 l = atoll.SyllableList 110 111 default: 112 return errors.Errorf("invalid list: %q", opts.list) 113 } 114 } 115 116 p := &atoll.Passphrase{ 117 Length: opts.length, 118 Separator: opts.separator, 119 Include: opts.incl, 120 Exclude: opts.excl, 121 List: l, 122 } 123 124 passphrase, err := atoll.NewSecret(p) 125 if err != nil { 126 return err 127 } 128 129 phraseBuf := memguard.NewBufferFromBytes([]byte(passphrase)) 130 memguard.WipeBytes([]byte(passphrase)) 131 defer phraseBuf.Destroy() 132 133 if opts.qr { 134 if err := terminal.DisplayQRCode(phraseBuf.String()); err != nil { 135 return err 136 } 137 } 138 139 entropy := p.Entropy() 140 keyspace, timeToCrack := FormatSecretSecurity(atoll.Keyspace(p), atoll.SecondsToCrack(p)/2) 141 142 if !opts.mute || !opts.copy { 143 fmt.Printf(`Passphrase: %s 144 145 Entropy: %.2f bits 146 Keyspace: %s 147 Average time taken to crack: %s 148 `, phraseBuf.String(), entropy, keyspace, timeToCrack) 149 } 150 151 if opts.copy { 152 if err := cmdutil.WriteClipboard(cmd, 0, "Passphrase", phraseBuf.String()); err != nil { 153 return err 154 } 155 } 156 return nil 157 } 158 } 159 160 // FormatSecretSecurity makes the data passed easier to read. 161 func FormatSecretSecurity(keyspace, avgTimeToCrack float64) (space, time string) { 162 if math.IsInf(keyspace, 1) { 163 return "+Inf", "+Inf" 164 } 165 166 space = fmt.Sprintf("%.2f", keyspace) 167 time = fmt.Sprintf("%.2f seconds", avgTimeToCrack) 168 169 switch { 170 case keyspace >= sextillion: 171 space = fmt.Sprintf("%.2f sextillion", keyspace/sextillion) 172 173 case keyspace >= quintillion: 174 space = fmt.Sprintf("%.2f quintillion", keyspace/quintillion) 175 176 case keyspace >= quadrillion: 177 space = fmt.Sprintf("%.2f quadrillion", keyspace/quadrillion) 178 179 case keyspace >= trillion: 180 space = fmt.Sprintf("%.2f trillion", keyspace/trillion) 181 182 case keyspace >= billion: 183 space = fmt.Sprintf("%.2f billion", keyspace/billion) 184 185 case keyspace >= million: 186 space = fmt.Sprintf("%.2f million", keyspace/million) 187 188 case keyspace >= thousand: 189 space = fmt.Sprintf("%.2f thousand", keyspace/thousand) 190 } 191 192 switch { 193 case avgTimeToCrack >= millennium: 194 time = fmt.Sprintf("%.2f millenniums", avgTimeToCrack/millennium) 195 196 case avgTimeToCrack >= century: 197 time = fmt.Sprintf("%.2f centuries", avgTimeToCrack/century) 198 199 case avgTimeToCrack >= decade: 200 time = fmt.Sprintf("%.2f decades", avgTimeToCrack/decade) 201 202 case avgTimeToCrack >= year: 203 time = fmt.Sprintf("%.2f years", avgTimeToCrack/year) 204 205 case avgTimeToCrack >= month: 206 time = fmt.Sprintf("%.2f months", avgTimeToCrack/month) 207 208 case avgTimeToCrack >= day: 209 time = fmt.Sprintf("%.2f days", avgTimeToCrack/day) 210 211 case avgTimeToCrack >= hour: 212 time = fmt.Sprintf("%.2f hours", avgTimeToCrack/hour) 213 214 case avgTimeToCrack >= minute: 215 time = fmt.Sprintf("%.2f minutes", avgTimeToCrack/minute) 216 } 217 218 return prettify(space), prettify(time) 219 } 220 221 // prettify adds commas to the number inside the string to make it easier for humans to read. 222 // 223 // Example: 51334.21 -> 51,334.21 224 func prettify(str string) string { 225 idx := strings.IndexByte(str, '.') 226 num := str[:idx] 227 228 sb := strings.Builder{} 229 j := 0 230 for i := len(num) - 1; i >= 0; i-- { 231 sb.WriteByte(num[j]) 232 if i%3 == 0 && i != 0 { 233 sb.WriteByte(',') 234 } 235 j++ 236 } 237 238 // Append remaining characters 239 sb.WriteString(str[idx:]) 240 return sb.String() 241 }