~seirdy/moac

ref: v0.2.0 moac/givens.go -rw-r--r-- 5.7 KiB
86be58a3Rohan Kumar Chore: add Makefile 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package moac

import (
	"errors"
	"fmt"
	"math"

	"git.sr.ht/~seirdy/moac/entropy"
)

// Givens holds the values used to compute password strength.
// These values are all physical quantities, measured using standard SI units.
type Givens struct {
	Password         string
	Entropy          float64
	Energy           float64
	Mass             float64 // mass used to build a computer or convert to energy
	Time             float64 // Duration of the attack, in seconds.
	EnergyPerGuess   float64
	Power            float64
	GuessesPerSecond float64
}

const (
	C         = 299792458      // speed of light in a vacuum, m/s
	G         = 6.67408e-11    // gravitation constant, m^3/kg/s^2
	Hubble    = 2.2e-18        // Hubble's Constant, hertz
	Temp      = 2.7            // cosmic radiation temperature (low estimate), kelvin
	Boltzmann = 1.3806503e-23  // Boltzmann's constant, J/K
	Planck    = 6.62607015e-35 // Planck's Constant, J*s

	UMass      = C * C * C / (2 * G * Hubble) // mass of the observable universe.
	Bremermann = C * C / Planck               // Bremermann's limit
	Landauer   = Boltzmann * Temp * math.Ln2  // Landauer limit

	defaultEntropy = 256
)

// populateDefaults fills in default values for entropy calculation if not provided.
func populateDefaults(givens *Givens) {
	if givens.EnergyPerGuess == 0 {
		// maybe put something more elaborate here given different constraints
		givens.EnergyPerGuess = Landauer
	}

	if givens.Energy+givens.Mass == 0 {
		// mass of the observable universe
		givens.Mass = UMass
	}

	if givens.Entropy == 0 {
		if givens.Mass+givens.EnergyPerGuess == 0 {
			givens.Entropy = defaultEntropy
		}
	}
}

func calculatePower(givens *Givens) {
	var (
		powerFromComputationSpeed = givens.GuessesPerSecond * givens.EnergyPerGuess
		powerFromEnergy           = givens.Energy / givens.Time
		// loop over an array for this since its length will grow in the future
		computedPowers = [2]float64{powerFromComputationSpeed, powerFromEnergy}
	)

	for _, power := range computedPowers {
		if givens.Power == 0 || (power > 0 && power < givens.Power) {
			givens.Power = power
		}
	}
}

func calculateEnergy(givens *Givens) {
	var (
		massEnergy       = givens.Mass * C * C
		energyFromPower  = givens.Power * givens.Time
		computedEnergies = [2]float64{massEnergy, energyFromPower}
	)

	for _, energy := range computedEnergies {
		if givens.Energy == 0 || (energy > 0 && energy < givens.Energy) {
			givens.Energy = energy
		}
	}
}

var (
	errMissingEMT = errors.New("missing energy, mass, and/or time")
	errMissingPE  = errors.New("missing password and/or entropy")
)

// populate will solve for entropy, guesses per second, and energy if they aren't given.
// If they are given, it updates them if the computed value is a greater bottleneck than the given value.
func (givens *Givens) populate() error {
	populateDefaults(givens)

	if givens.Password != "" {
		computedEntropy, err := entropy.Entropy(givens.Password)
		if err != nil {
			return fmt.Errorf("error measuring generated password: %w", err)
		}

		if givens.Entropy == 0 || givens.Entropy > computedEntropy {
			givens.Entropy = computedEntropy
		}
	}

	var bremermannGPS float64

	if givens.GuessesPerSecond == 0 && givens.Mass != 0 {
		bremermannGPS = Bremermann * givens.Mass
	}

	calculatePower(givens)

	powerGPS := givens.Power / givens.EnergyPerGuess
	for _, gps := range [2]float64{bremermannGPS, powerGPS} {
		if givens.GuessesPerSecond == 0 || (gps > 0 && gps < givens.GuessesPerSecond) {
			givens.GuessesPerSecond = gps
		}
	}

	calculateEnergy(givens)

	if givens.Energy == 0 && givens.Time == 0 {
		return fmt.Errorf("populating givens: %w", errMissingEMT)
	}

	return nil
}

// BruteForceability computes the liklihood that a password will be
// brute-forced given the contstraints in givens.
// if 0 < BruteForceability <= 1, it represents the probability that the
// password can be brute-forced.
// if BruteForceability > 1, it represents the number of times a password
// can be brute-forced with certainty.
func BruteForceability(givens *Givens, quantum bool) (float64, error) {
	if err := givens.populate(); err != nil {
		return 0, err
	}

	if givens.Entropy+givens.Time == 0 {
		return 0, fmt.Errorf("BruteForceability: cannot compute entropy: %w", errMissingPE)
	}

	var (
		computedBruteForceability = bruteForceability(givens, quantum)
		err                       error
	)

	switch {
	case computedBruteForceability == 0:
		err = fmt.Errorf("BruteForceability: %w", errMissingPE)
	case math.IsNaN(computedBruteForceability):
		err = fmt.Errorf("BruteForceability: %w", errMissingPE)
	}

	return computedBruteForceability, err
}

func bruteForceability(givens *Givens, quantum bool) float64 {
	// with Grover's algorithm, quantum computers get an exponential speedup
	var effectiveEntropy float64

	if quantum {
		effectiveEntropy = givens.Entropy / 2
	} else {
		effectiveEntropy = givens.Entropy
	}

	var (
		guessesRequired = math.Exp2(effectiveEntropy)
		energyBound     = givens.Energy / (guessesRequired * givens.EnergyPerGuess)
	)

	if givens.Time > 0 {
		timeBound := givens.Time * givens.GuessesPerSecond / guessesRequired

		return math.Min(energyBound, timeBound)
	}

	return energyBound
}

// MinEntropy calculates the maximum password entropy that the MOAC can certainly brute-force.
// Passwords need an entropy greater than this to have a chance of not being guessed.
func MinEntropy(givens *Givens, quantum bool) (entropy float64, err error) {
	if err := givens.populate(); err != nil {
		return 0, err
	}

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

	if givens.Time > 0 {
		timeBound := math.Log2(givens.Time * givens.GuessesPerSecond)
		entropy = math.Min(energyBound, timeBound)
	} else {
		entropy = energyBound
	}

	if quantum {
		return entropy * 2, nil
	}

	return entropy, nil
}