pkg.re/essentialkaos/ek.10@v12.41.0+incompatible/passwd/passwd.go (about) 1 // Package passwd contains methods for working with passwords 2 package passwd 3 4 // ////////////////////////////////////////////////////////////////////////////////// // 5 // // 6 // Copyright (c) 2022 ESSENTIAL KAOS // 7 // Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0> // 8 // // 9 // ////////////////////////////////////////////////////////////////////////////////// // 10 11 import ( 12 "bytes" 13 "crypto/aes" 14 "crypto/cipher" 15 crand "crypto/rand" 16 "crypto/sha512" 17 "encoding/base64" 18 "errors" 19 "io" 20 "math/rand" 21 "strings" 22 "time" 23 24 "golang.org/x/crypto/bcrypt" 25 ) 26 27 // ////////////////////////////////////////////////////////////////////////////////// // 28 29 type Strength int 30 31 // ////////////////////////////////////////////////////////////////////////////////// // 32 33 const ( 34 STRENGTH_WEAK Strength = iota // Only lowercase English alphabet characters 35 STRENGTH_MEDIUM // Lowercase and uppercase English alphabet characters, digits 36 STRENGTH_STRONG // Lowercase and uppercase English alphabet characters, digits, special symbols 37 ) 38 39 // ////////////////////////////////////////////////////////////////////////////////// // 40 41 const ( 42 _SYMBOLS_WEAK = "abcdefghijklmnopqrstuvwxyz" 43 _SYMBOLS_MEDIUM = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ" 44 _SYMBOLS_STRONG = "!\";%:?*()_+=-~/\\<>,.[]{}" 45 ) 46 47 // ////////////////////////////////////////////////////////////////////////////////// // 48 49 // Encrypt creates hash and encrypts it with salt and pepper 50 // Deprecated: Use Hash method instead 51 func Encrypt(password, pepper string) (string, error) { 52 return Hash(password, pepper) 53 } 54 55 // Hash creates hash and encrypts it with salt and pepper 56 func Hash(password, pepper string) (string, error) { 57 return HashBytes([]byte(password), []byte(pepper)) 58 } 59 60 // HashBytes creates hash and encrypts it with salt and pepper 61 func HashBytes(password, pepper []byte) (string, error) { 62 switch { 63 case len(password) == 0: 64 return "", errors.New("Password can't be empty") 65 case len(pepper) == 0: 66 return "", errors.New("Pepper can't be empty") 67 } 68 69 if !isValidPepper(pepper) { 70 return "", errors.New("Pepper have invalid size") 71 } 72 73 hasher := sha512.New() 74 hasher.Write(password) 75 76 hp, err := bcrypt.GenerateFromPassword(hasher.Sum(nil), 10) 77 78 if err != nil { 79 return "", err 80 } 81 82 block, _ := aes.NewCipher(pepper) 83 hpd := padData(hp) 84 85 ct := make([]byte, aes.BlockSize+len(hpd)) 86 iv := ct[:aes.BlockSize] 87 88 _, err = io.ReadFull(crand.Reader, iv) 89 90 if err != nil { 91 return "", err 92 } 93 94 cfb := cipher.NewCFBEncrypter(block, iv) 95 cfb.XORKeyStream(ct[aes.BlockSize:], hpd) 96 97 return removeBase64Padding(base64.URLEncoding.EncodeToString(ct)), nil 98 } 99 100 // Check compares password with encrypted hash 101 func Check(password, pepper, hash string) bool { 102 return CheckBytes([]byte(password), []byte(pepper), hash) 103 } 104 105 // CheckBytes compares password with encrypted hash 106 func CheckBytes(password, pepper []byte, hash string) bool { 107 if len(password) == 0 || len(hash) == 0 || !isValidPepper(pepper) { 108 return false 109 } 110 111 block, _ := aes.NewCipher(pepper) 112 hpd, err := base64.URLEncoding.DecodeString(addBase64Padding(hash)) 113 114 if err != nil { 115 return false 116 } 117 118 hdpl := len(hpd) 119 120 if hdpl < aes.BlockSize || (hdpl%aes.BlockSize) != 0 { 121 return false 122 } 123 124 iv := hpd[:aes.BlockSize] 125 hp := hpd[aes.BlockSize:] 126 127 if len(hp) == 0 { 128 return false 129 } 130 131 cfb := cipher.NewCFBDecrypter(block, iv) 132 cfb.XORKeyStream(hp, hp) 133 134 h, ok := unpadData(hp) 135 136 if !ok { 137 return false 138 } 139 140 hasher := sha512.New() 141 hasher.Write(password) 142 143 return bcrypt.CompareHashAndPassword(h, hasher.Sum(nil)) == nil 144 } 145 146 // GenPassword generates random password 147 func GenPassword(length int, strength Strength) string { 148 return string(GenPasswordBytes(length, strength)) 149 } 150 151 // GenPassword generates random password 152 func GenPasswordBytes(length int, strength Strength) []byte { 153 return getRandomPasswordBytes(length, getStrength(strength)) 154 } 155 156 // GetPasswordStrength returns password strength 157 func GetPasswordStrength(password string) Strength { 158 return GetPasswordBytesStrength([]byte(password)) 159 } 160 161 // GetPasswordBytesStrength returns password strength 162 func GetPasswordBytesStrength(password []byte) Strength { 163 if len(password) == 0 { 164 return STRENGTH_WEAK 165 } 166 167 var conditions int 168 169 if bytes.ContainsAny(password, "abcdefghijklmnopqrstuvwxyz") && 170 bytes.ContainsAny(password, "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { 171 conditions++ 172 } 173 174 if bytes.ContainsAny(password, "1234567890") { 175 conditions++ 176 } 177 178 if bytes.ContainsAny(password, _SYMBOLS_STRONG) { 179 conditions++ 180 } 181 182 if len(password) < 6 { 183 conditions = 1 184 } else { 185 conditions++ 186 } 187 188 switch conditions { 189 case 4: 190 return STRENGTH_STRONG 191 case 3: 192 return STRENGTH_MEDIUM 193 } 194 195 return STRENGTH_WEAK 196 } 197 198 // GenPasswordVariations generates password variants with possible 199 // typos fixes (case swap for all letters, first leter swap, password 200 // without last symbol) 201 func GenPasswordVariations(password string) []string { 202 if len(password) < 6 { 203 return nil 204 } 205 206 var result []string 207 208 passwordBytes := []byte(password) 209 210 result = append(result, string(genVariantFlipAll(passwordBytes))) 211 result = append(result, string(genVariantFlipFirst(passwordBytes))) 212 result = append(result, string(genVariantTrimLast(passwordBytes))) 213 214 return result 215 } 216 217 // GenPasswordBytesVariations generates password variants with possible 218 // typos fixes (case swap for all letters, first leter swap, password 219 // without last symbol) 220 func GenPasswordBytesVariations(password []byte) [][]byte { 221 if len(password) < 6 { 222 return nil 223 } 224 225 var result [][]byte 226 227 result = append(result, genVariantFlipAll(password)) 228 result = append(result, genVariantFlipFirst(password)) 229 result = append(result, genVariantTrimLast(password)) 230 231 return result 232 } 233 234 // ////////////////////////////////////////////////////////////////////////////////// // 235 236 func getStrength(s Strength) Strength { 237 if s < STRENGTH_WEAK { 238 return STRENGTH_WEAK 239 } 240 241 if s > STRENGTH_STRONG { 242 return STRENGTH_STRONG 243 } 244 245 return s 246 } 247 248 func padData(src []byte) []byte { 249 padding := aes.BlockSize - len(src)%aes.BlockSize 250 padText := bytes.Repeat([]byte{byte(padding)}, padding) 251 252 return append(src, padText...) 253 } 254 255 func unpadData(src []byte) ([]byte, bool) { 256 length := len(src) 257 unpadding := int(src[length-1]) 258 259 if unpadding > length { 260 return nil, false 261 } 262 263 return src[:(length - unpadding)], true 264 } 265 266 func addBase64Padding(src string) string { 267 m := len(src) % 4 268 269 if m != 0 { 270 src += strings.Repeat("=", 4-m) 271 } 272 273 return src 274 } 275 276 func removeBase64Padding(src string) string { 277 return strings.TrimRight(src, "=") 278 } 279 280 func getRandomPasswordBytes(length int, strength Strength) []byte { 281 if length == 0 { 282 return nil 283 } 284 285 if strength == STRENGTH_STRONG && length < 6 { 286 length = 6 287 } 288 289 var symbols = _SYMBOLS_WEAK 290 291 switch strength { 292 case STRENGTH_MEDIUM: 293 symbols += _SYMBOLS_MEDIUM 294 case STRENGTH_STRONG: 295 symbols += _SYMBOLS_MEDIUM + _SYMBOLS_STRONG 296 } 297 298 ls := len(symbols) 299 buf := make([]byte, length) 300 301 for { 302 rand.Seed(time.Now().UTC().UnixNano()) 303 304 for i := 0; i < length; i++ { 305 buf[i] = symbols[rand.Intn(ls)] 306 } 307 308 if GetPasswordBytesStrength(buf) == strength { 309 return buf 310 } 311 } 312 } 313 314 func isValidPepper(pepper []byte) bool { 315 switch len(pepper) { 316 case 16, 24, 32: 317 return true 318 } 319 320 return false 321 } 322 323 func genVariantFlipAll(password []byte) []byte { 324 result := make([]byte, len(password)) 325 326 for i := 0; i < len(password); i++ { 327 result[i] = flipCase(password[i]) 328 } 329 330 return result 331 } 332 333 func genVariantFlipFirst(password []byte) []byte { 334 result := make([]byte, len(password)) 335 336 copy(result, password) 337 338 result[0] = flipCase(password[0]) 339 340 return result 341 } 342 343 func genVariantTrimLast(password []byte) []byte { 344 return append(password[:0:0], password[:len(password)-1]...) 345 } 346 347 func flipCase(b byte) byte { 348 s := string(b) 349 sc := strings.ToLower(s) 350 351 if s != sc { 352 return byte(sc[0]) 353 } 354 355 return byte(strings.ToUpper(s)[0]) 356 }