~seirdy/moac

15bbfcc2c8916775bc4faaa3266e094eef5d92ea — Rohan Kumar a month ago 6e0ad7a
Feat(CLI): allow setting guesses-per-sec

Add a CLI flag -G to set guesses-per-second.

This obviously required updating flag parsing, manpages, and CLi
testscript scenarios.

In addition, GuessesPerSecond had to be checked for validity (i.e. it
couldn't be negative); this added to the checking of CLI error messages.

Also edited error strings that generated said error messages to remove
single quotes since testscript doesn't like them and "cannot" is cleaner
than "can't" with a bunch of escapes.
M cmd/moac-pwgen/main.go => cmd/moac-pwgen/main.go +4 -1
@@ 29,6 29,7 @@ OPTIONS:
  -P <power>	Power available to the computer (W)
  -T <temperature>	Temperature of the system (K)
  -t <time>	Time limit for brute-force attack (s)
  -G <guesses>	Guesses per second in a brute-force attack
  -l <length>	minimum generated password length; can override (increase) -s
  -L <length>	maximum generated password length; can override (decrease) -s
`


@@ 71,6 72,8 @@ func parseOpts(
			givens.Temperature, err = strconv.ParseFloat(opt.Value, 64)
		case 't':
			givens.Time, err = strconv.ParseFloat(opt.Value, 64)
		case 'G':
			givens.GuessesPerSecond, err = strconv.ParseFloat(opt.Value, 64)
		case 'l':
			minLen64, err = strconv.ParseInt(opt.Value, 10, 32)
		case 'L':


@@ 116,7 119,7 @@ func main() {
}

func main1() int {
	opts, optind, err := getopt.Getopts(os.Args, "hvqre:s:m:g:P:T:t:l:L:")
	opts, optind, err := getopt.Getopts(os.Args, "hvqre:s:m:g:P:T:t:G:l:L:")
	if !cli.DisplayErr(err, usage) {
		return 1
	}

M cmd/moac-pwgen/testdata/scripts/fail.txt => cmd/moac-pwgen/testdata/scripts/fail.txt +4 -0
@@ 37,3 37,7 @@ stderr 'moac: bad GenPW param: lengths and entropies cannot be negative: bad val
! moac-pwgen -l 4 -L 4 -s -128
stderr 'moac: bad GenPW param: lengths and entropies cannot be negative: bad value -128 is below 0'
! stdout '.'

! moac-pwgen -s 128 -G -1e18 -t 3.1536e8
stderr 'moac: cannot compute MinEntropy: invalid givens: physical values cannot be negative: bad value -1e\+18 is below 0'
! stdout '.'

M cmd/moac-pwgen/testdata/scripts/success.txt => cmd/moac-pwgen/testdata/scripts/success.txt +6 -0
@@ 72,3 72,9 @@ stdout '^....................$'
moac-pwgen -L 8 ascii latin
! stderr '.'
stdout '^........$'

# actually somewhat realistic scenario: crack in 10 years
# folding@home managed to hit around 2.5 exaflops once; let's try 10 exaflops
moac-pwgen -t 3.1536e8 -G 1e18
! stderr '.'
stdout '^..............$'

M cmd/moac/main.go => cmd/moac/main.go +4 -1
@@ 30,6 30,7 @@ OPTIONS:
  -P <power>	Power available to the computer (W)
  -T <temperature>	Temperature of the system (K)
  -t <time>	Time limit for brute-force attack (s)
  -G <guesses>	Guesses per second in a brute-force attack
  -p <password>	Password to analyze; use "-" for stdin

COMMANDS:


@@ 80,6 81,8 @@ func parseOpts(
			givens.Temperature, err = strconv.ParseFloat(opt.Value, 64)
		case 't':
			givens.Time, err = strconv.ParseFloat(opt.Value, 64)
		case 'G':
			givens.GuessesPerSecond, err = strconv.ParseFloat(opt.Value, 64)
		case 'p':
			givens.Password = opt.Value
		}


@@ 141,7 144,7 @@ func main1() int {
}

func getOutput() (output float64, exitEarly bool, err error) {
	opts, optind, err := getopt.Getopts(os.Args, "hvqre:s:m:g:P:T:t:p:")
	opts, optind, err := getopt.Getopts(os.Args, "hvqre:s:m:g:P:T:t:G:p:")
	if err != nil {
		return output, exitEarly, fmt.Errorf("%w\n%s", err, usage)
	}

M cmd/moac/testdata/scripts/fail.txt => cmd/moac/testdata/scripts/fail.txt +8 -0
@@ 22,6 22,14 @@ stderr 'moac: not enough given values: missing password'
stderr 'moac: bad arguments: unknown command ascii'
! stdout '.'

! moac -q ascii
stderr 'moac: bad arguments: unknown command ascii'
! stdout '.'

! moac -m -8
stderr 'moac.*BruteForceability.*negative'
! stdout '.'

! moac -s 128 -G -1e18 -t 3.1536e8
stderr 'moac: cannot compute BruteForceability: invalid givens: physical values cannot be negative: bad value -1e\+18 is below 0'
! stdout '.'

M cmd/moac/testdata/scripts/success.txt => cmd/moac/testdata/scripts/success.txt +6 -0
@@ 50,3 50,9 @@ stdout '^6\.1e\+29$'

moac -t 1.45e17 -P 3.828e26 -T 1.5e7 -qs 396 strength
stdout '^0\.962$'

# actually somewhat realistic scenario: crack in 10 years
# folding@home managed to hit around 2.5 exaflops once; let's try 10 exaflops
moac -t 3.1536e8 -s 128 -G 1e18
! stderr '.'
stdout '^9\.27e-13$'

M doc/moac-pwgen.1.scd => doc/moac-pwgen.1.scd +5 -0
@@ 30,6 30,7 @@ moac-pwgen [OPTIONS...] [CHARSETS...]
*-m* <mass>
	Mass at attacker's disposal (kg). Used to compute mass-energy. Overrides the
	value of *-e* if the computed mass-energy is lower.
	Mass can also be used to calculate Bremermann's Limit.

*-g* <energy>
	Energy used per guess (J).


@@ 46,6 47,10 @@ moac-pwgen [OPTIONS...] [CHARSETS...]
*-t* <time>
	Time limit for brute-force attack (s).

*-G* <guesses>
	Guesses-per-second in a brute-force attack. Overridden by values computed from
	Bremermann's Limit and the Landauer limit if those are smaller or if unset.

*-l* <length>
	Minimum number of characters in generated password. Overrides value of *-s* if
	doing so would increase password length.

M doc/moac.1.scd => doc/moac.1.scd +8 -1
@@ 34,6 34,7 @@ moac [OPTIONS...] [COMMAND]
*-m* <mass>
	Mass at attacker's disposal (kg). Used to compute mass-energy. Overrides the
	value of *-e* if the computed mass-energy is lower.
	Mass can also be used to calculate Bremermann's Limit.

*-g* <energy>
	Energy used per guess (J).


@@ 50,6 51,10 @@ moac [OPTIONS...] [COMMAND]
*-t* <time>
	Time limit for brute-force attack (s).

*-G* <guesses>
	Guesses-per-second in a brute-force attack. Overridden by values computed from
	Bremermann's Limit and the Landauer limit if those are smaller or if unset.

*-p* <password>
	Password to analyze. Use '-' to read the password from stdin.



@@ 68,7 73,9 @@ values, *moac* uses the following default values:

*energy per guess*: Landauer limit.

*guesses per second*: product of Bremermann's Limit and mass.
*guesses per second*: product of Bremermann's Limit and mass. If *-P* is
specified and the quotient of power and the Landauer limit is smaller, set it to
that instead.

*temperature*: 2.7 K, a low estimate for the temperature of cosmic background
radiation.

M givens.go => givens.go +7 -6
@@ 88,9 88,10 @@ func (givens *Givens) validate() error {
		return fmt.Errorf("invalid temperature: %w", err)
	}

	if err := bounds.NonNegative(
		givens.Energy, givens.Mass, givens.Power, givens.Time); err != nil {
		return fmt.Errorf("physical values can't be negative: %w", err)
	err := bounds.NonNegative(
		givens.Energy, givens.Mass, givens.Power, givens.Time, givens.GuessesPerSecond, givens.EnergyPerGuess)
	if err != nil {
		return fmt.Errorf("physical values cannot be negative: %w", err)
	}

	return nil


@@ 121,7 122,7 @@ func (givens *Givens) Populate() error {
// can be brute-forced with certainty.
func (givens *Givens) BruteForceability() (float64, error) {
	if err := givens.Populate(); err != nil {
		return 0, fmt.Errorf("can't compute BruteForceability: %w", err)
		return 0, fmt.Errorf("cannot compute BruteForceability: %w", err)
	}

	if givens.Entropy+givens.Time == 0 {


@@ 135,7 136,7 @@ func (givens *Givens) BruteForceability() (float64, error) {
// quantum computers that use Grover's Algorithm.
func (givens *Givens) BruteForceabilityQuantum() (float64, error) {
	if err := givens.Populate(); err != nil {
		return 0, fmt.Errorf("can't calculate BruteForceabilityQuantum: %w", err)
		return 0, fmt.Errorf("cannot calculate BruteForceabilityQuantum: %w", err)
	}

	givensQuantum := givens


@@ 165,7 166,7 @@ func computeBruteForceability(givens *Givens) float64 {
// Passwords need an entropy greater than this to have a chance of not being guessed.
func (givens *Givens) MinEntropy() (entropyNeeded float64, err error) {
	if err := givens.Populate(); err != nil {
		return 0, fmt.Errorf("can't compute MinEntropy: %w", err)
		return 0, fmt.Errorf("cannot compute MinEntropy: %w", err)
	}

	energyBound := math.Log2(givens.Energy / givens.EnergyPerGuess)

M givens_test.go => givens_test.go +9 -0
@@ 151,6 151,15 @@ func givensTestCases() []givensTestCase { //nolint:funlen // single statement; l
			expectedErrME: bounds.ErrImpossibleNegative,
		},
		{
			name: "negativeGPS",
			given: moac.Givens{
				Energy:           4.0e52,
				GuessesPerSecond: -1.0e4,
			},
			expectedErrBF: bounds.ErrImpossibleNegative,
			expectedErrME: bounds.ErrImpossibleNegative,
		},
		{
			name:          "Mising energy, mass",
			given:         moac.Givens{},
			expectedBFQ:   0,

M pwgen/indexing.go => pwgen/indexing.go +1 -1
@@ 13,7 13,7 @@ import (
func randInt(max int) int {
	newInt, err := rand.Int(rand.Reader, big.NewInt(int64(max)))
	if err != nil {
		log.Panicf("can't generate passwords: crypto/rand unavailable: %v", err)
		log.Panicf("cannot generate passwords: crypto/rand unavailable: %v", err)
	}

	return int(newInt.Int64())

M pwgen/nocrand_test.go => pwgen/nocrand_test.go +1 -1
@@ 27,7 27,7 @@ func shouldPanic(t *testing.T) {
	defer func() {
		rand.Reader = csprng

		if out := recover(); out != "can't generate passwords: crypto/rand unavailable: EOF" {
		if out := recover(); out != "cannot generate passwords: crypto/rand unavailable: EOF" {
			t.Errorf("panic due to CSPRNG unavailability sent unexpected message: %v", out)
		}
	}()