-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathpassword.go
103 lines (88 loc) · 2.88 KB
/
password.go
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
package jess
import (
"math"
"strings"
)
var (
// ASCII printable characters (character codes 32-127).
passwordCharSets = []string{
"abcdefghijklmnopqrstuvwxyz",
"ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"0123456789",
"- .,_", // more common special characters, especially with passwords using words
"!\"#$%&'()*+/:;<=>?@[\\]^`{|}~",
}
// extended ASCII codes (character code 128-255)
// assume pool size of 32 (a quarter), as not all of them are common / easily accessible on every keyboard.
passwordExtraPoolSize = 32
createPasswordCallback func(signet *Signet, minSecurityLevel int) error
getPasswordCallback func(signet *Signet) error
)
// SetPasswordCallbacks sets callbacks that are used to let the user enter passwords.
func SetPasswordCallbacks(
createPassword func(signet *Signet, minSecurityLevel int) error,
getPassword func(signet *Signet) error,
) {
if createPasswordCallback == nil {
createPasswordCallback = createPassword
}
if getPasswordCallback == nil {
getPasswordCallback = getPassword
}
}
// CalculatePasswordSecurityLevel calculates the security level of the given password and iterations of the pbkdf algorithm.
func CalculatePasswordSecurityLevel(password string, iterations int) int {
// TODO: this calculation is pretty conservative and errs on the safe side
// maybe soften this up a litte, but couldn't find any scientific foundation for that
charactersFound := 0
distinctCharactersFound := 0
characterPoolSize := 0
// loop all character sets
for _, charSet := range passwordCharSets {
foundInCharSet := false
// loop through every character in the character set
for _, char := range charSet {
// count occurrences in password
cnt := countRuneInString(password, char)
// disqualify if a single character is 1/4 of the password
if cnt*4 >= len(password) {
return -1
}
// we found something!
if cnt > 0 {
charactersFound += cnt
distinctCharactersFound++
foundInCharSet = true
}
}
// if we found anything in this char set, add the it's length to the total pool
if foundInCharSet {
characterPoolSize += len(charSet)
}
}
// disqualify if characters are repeated 4 or more times, on average
if distinctCharactersFound*4 <= len(password) {
return -1
}
// check if there are some extra characters
if charactersFound < len(password) {
// add the extra pool size
characterPoolSize += passwordExtraPoolSize
}
possibleCombinationsWithPoolSize := math.Pow(float64(characterPoolSize), float64(len(password)))
entropy := math.Log2(possibleCombinationsWithPoolSize)
avgNumberOfGuesses := math.Pow(2, entropy-1)
avgGuessingOperations := avgNumberOfGuesses * float64(iterations)
securityLevel := math.Log2(avgGuessingOperations)
return int(securityLevel) // always round down
}
func countRuneInString(s string, r rune) (n int) {
for {
i := strings.IndexRune(s, r)
if i < 0 {
return
}
n++
s = s[i+1:]
}
}