// // Login() logs in the user, using the stored // bycrypt hash. // // The return values are: // // - bool: if the password matches the hash // - bool: if the hash needs to be changed // in the database (can only true when the // password matches the hash) // - string: what the hash in the database // needs to be changed to (has contents if // and only if the hash needs to be // changed) // func (a *AuthContext) Login(userId uint, password, hashed string) (bool, bool, string) { if a.loggedIn { panic("User already logged in!") } bcryptErr := bcrypt.CompareHashAndPassword([]byte(hashed), []byte(password)) if bcryptErr == bcrypt.ErrMismatchedHashAndPassword { return false, false, "" } else if bcryptErr != nil { panic(bcryptErr) } a.authtoken = generateAuthtoken() a.hasChanged = true a.loggedIn = true a.userId = userId currentCost, costErr := bcrypt.Cost([]byte(hashed)) if costErr != nil { panic(costErr) } if currentCost != a.bcryptCost { newHash, newHashErr := bcrypt.GenerateFromPassword([]byte(password), a.bcryptCost) if newHashErr != nil { panic(newHashErr) } return true, true, string(newHash) } else { return true, false, "" } }
func (c *config) IsCurrent(encoded []byte) (isCurrent bool, err error) { cost, err := bcrypt.Cost(encoded) if err == nil { isCurrent = cost >= c.Cost } return }
func TestSpec(t *testing.T) { Convey("Encrypt Testing", t, func() { Convey("generateSalt()", func() { salt, err := generateSalt() So(err, ShouldBeNil) So(salt, ShouldNotBeBlank) So(len(salt), ShouldEqual, SaltLength) }) Convey("hashPassword()", func() { salt, err := generateSalt() So(err, ShouldBeNil) hash, err := hashPassword(salt, "hershmahgersh") So(err, ShouldBeNil) So(hash, ShouldNotBeNil) cost, err := bcrypt.Cost([]byte(hash)) So(err, ShouldBeNil) So(cost, ShouldEqual, EncryptCost) pw := combine(salt, string(hash)) log.Printf("pw = %+v\n", pw) parsedSalt, parsedHash := getPWPieces(pw) So(salt, ShouldEqual, parsedSalt) So(string(hash), ShouldEqual, parsedHash) }) Convey("->Encrypt()", func() { passString := "mmmPassword1" password, err := Encrypt(passString) So(err, ShouldBeNil) So(password, ShouldNotBeEmpty) salt, _ := getPWPieces(password) So(len(salt), ShouldEqual, SaltLength) }) Convey("->IsMatch()", func() { password := "******" hash, err := Encrypt(password) So(err, ShouldBeNil) match, err := IsMatch(password, hash) So(match, ShouldBeTrue) So(err, ShouldBeNil) match, err = IsMatch("lolfail", hash) So(match, ShouldBeFalse) So(err, ShouldNotBeNil) match, err = IsMatch("Megaman49", hash) So(match, ShouldBeFalse) So(err, ShouldNotBeNil) }) }) }
func (s *scheme) NeedsUpdate(stub string) bool { cost, err := bcrypt.Cost([]byte(stub)) if err != nil { return false } return cost < s.Cost }
// checkCost returns an error if the hash provided does not meet minimum cost requirement func checkCost(hash []byte) error { actual, err := bcrypt.Cost(hash) if err != nil { return fmt.Errorf("parsing bcrypt hash: %v", err) } if actual < bcrypt.DefaultCost { return fmt.Errorf("given hash cost = %d, does not meet minimum cost requirement = %d", actual, bcrypt.DefaultCost) } return nil }
func TestSpec(t *testing.T) { Convey("Authentication Testing", t, func() { Convey("generateSalt()", func() { salt := generateSalt(cDEFAULT_SALT_LENGTH) So(len(salt), ShouldBeGreaterThan, 0) So(len(salt), ShouldEqual, cDEFAULT_SALT_LENGTH) }) Convey("combine()", func() { salt := generateSalt(cDEFAULT_SALT_LENGTH) password := "******" expectedLength := len(salt) + len(password) combo := combine(salt, []byte(password)) So(len(combo), ShouldBeGreaterThan, 0) So(len(combo), ShouldEqual, expectedLength) So(bytes.Equal(combo[len(combo)-len(salt):], salt), ShouldBeTrue) }) Convey("hashPassword()", func() { combo := combine(generateSalt(cDEFAULT_SALT_LENGTH), []byte("hershmahgersh")) hash := hashPassword(combo, CDEFAULT_ENCRYPT_COST) So(len(hash), ShouldBeGreaterThan, 0) cost, err := bcrypt.Cost([]byte(hash)) if err != nil { log.Print(err) } So(cost, ShouldEqual, CDEFAULT_ENCRYPT_COST) }) Convey("CreatePassword()", func() { passString := "mmmPassword1" pwd := CreatePassword(passString, cDEFAULT_SALT_LENGTH, CDEFAULT_ENCRYPT_COST) pass_struct := new(password) So(pwd, ShouldHaveSameTypeAs, pass_struct) So(len(pwd.hash), ShouldBeGreaterThan, 0) So(len(pwd.salt), ShouldBeGreaterThan, 0) So(len(pwd.salt), ShouldEqual, cDEFAULT_SALT_LENGTH) }) Convey("comparePassword", func() { password := "******" passwordMeta := CreatePassword(password, cDEFAULT_SALT_LENGTH, CDEFAULT_ENCRYPT_COST) So(passwordMatch(password, passwordMeta.salt, passwordMeta.hash), ShouldBeTrue) So(passwordMatch("lolfail", passwordMeta.salt, passwordMeta.hash), ShouldBeFalse) So(passwordMatch("Megaman49", passwordMeta.salt, passwordMeta.hash), ShouldBeFalse) }) }) }
func decodeConfiguration(decoder *json.Decoder) (*Configuration, error) { var config Configuration err := decoder.Decode(&config) if err != nil { return nil, err } if config.Version < 1 { return nil, fmt.Errorf("Invalid configuration version (must be > 0): %v", config.Version) } if len(config.Hosts) == 0 { return nil, fmt.Errorf("Invalid configuration: empty hosts") } twoFInc := (2 * int(config.F)) + 1 if twoFInc > len(config.Hosts) { return nil, fmt.Errorf("F given as %v, requires minimum 2F+1=%v hosts but only %v hosts specified.", config.F, twoFInc, len(config.Hosts)) } if config.MaxRMCount == 0 && twoFInc < 128 { config.MaxRMCount = uint8(2 * twoFInc) } else if int(config.MaxRMCount) < twoFInc { return nil, fmt.Errorf("MaxRMCount given as %v but must be at least 2F+1=%v.", config.MaxRMCount, twoFInc) } for idx, hostPort := range config.Hosts { port := common.DefaultPort hostOnly := hostPort if host, portStr, err := net.SplitHostPort(hostPort); err == nil { portInt64, err := strconv.ParseUint(portStr, 0, 16) if err != nil { return nil, err } port = int(portInt64) hostOnly = host } hostPort = net.JoinHostPort(hostOnly, fmt.Sprint(port)) config.Hosts[idx] = hostPort if _, err := net.ResolveTCPAddr("tcp", hostPort); err != nil { return nil, err } } if len(config.Accounts) == 0 { return nil, errors.New("No accounts defined") } else { for un, pw := range config.Accounts { if cost, err := bcrypt.Cost([]byte(pw)); err != nil { return nil, fmt.Errorf("Error in password for account %v: %v", un, err) } else if cost < bcrypt.DefaultCost { return nil, fmt.Errorf("Error in password for account %v: cost too low (%v)", un, cost) } } } return &config, err }
func (p *password) UnmarshalJSON(b []byte) error { var data struct { Email string `json:"email"` Username string `json:"username"` UserID string `json:"userID"` Hash string `json:"hash"` } if err := json.Unmarshal(b, &data); err != nil { return err } *p = password(storage.Password{ Email: data.Email, Username: data.Username, UserID: data.UserID, }) if len(data.Hash) == 0 { return fmt.Errorf("no password hash provided") } // If this value is a valid bcrypt, use it. _, bcryptErr := bcrypt.Cost([]byte(data.Hash)) if bcryptErr == nil { p.Hash = []byte(data.Hash) return nil } // For backwards compatibility try to base64 decode this value. hashBytes, err := base64.StdEncoding.DecodeString(data.Hash) if err != nil { return fmt.Errorf("malformed bcrypt hash: %v", bcryptErr) } if _, err := bcrypt.Cost(hashBytes); err != nil { return fmt.Errorf("malformed bcrypt hash: %v", err) } p.Hash = hashBytes return nil }
// Validate implements FieldValidator interface func (v Password) Validate(value interface{}) (interface{}, error) { s, ok := value.(string) if !ok { if b, ok := value.([]byte); ok { // Maybe it's an already encoded version of the password if _, err := bcrypt.Cost(b); err == nil { return b, nil } } return nil, errors.New("not a string") } l := len(s) if l < v.MinLen { return nil, fmt.Errorf("is shorter than %d", v.MinLen) } if v.MaxLen > 0 && l > v.MaxLen { return nil, fmt.Errorf("is longer than %d", v.MaxLen) } b, err := bcrypt.GenerateFromPassword([]byte(s), v.Cost) if err != nil { return nil, err } return b, nil }
func main() { var opts struct { Rounds int `short:"r" long:"rounds" default:"50000" description:"Number of rounds"` Hashname string `long:"hash" default:"sha256" description:"Hash to use"` Kdname string `long:"kd" description:"Key derivation function"` Cost int `short:"c" long:"cost" default:"14" description:"Cost parameter to key derivation functions"` Hmacenc string `long:"hmacenc" default:"" description:"Base64 encoded password for final hmac encryption step"` } opts.Rounds = 50000 opts.Hashname = "sha256" opts.Kdname = "scrypt" opts.Cost = 14 parser := flags.NewParser(&opts, flags.Default) parser.Usage = "[OPTIONS] <password> [salt]" parser.Usage += "\n\nSupported:\n" parser.Usage += "\tscrpyt bcrypt pbkdf2\n" args, err := parser.Parse() if err != nil { os.Exit(1) } if len(args) == 0 { log.Fatal("Error: ", "Parameter password missing") } if opts.Kdname == "bcrypt" && opts.Hmacenc != "" { log.Fatal("Error: bcrypt hash output can not be encrypted") } if opts.Kdname == "scrypt" { opts.Hashname = "sha256" } var hmacenc_bin []byte if opts.Hmacenc != "" { hmacenc_bin, err = base64.URLEncoding.DecodeString(opts.Hmacenc) if err != nil { log.Fatal("Unable to decode hmac encryption password: "******"Error: ", "Unknown hash given: ", opts.Hashname) } hashlength := h().Size() salt := make([]byte, hashlength) pw := []byte(args[0]) if len(args) == 2 { if opts.Kdname == "bcrypt" { log.Fatal("Error: ", "Salt not supported for bcrypt") } salt, err = base64.URLEncoding.DecodeString(args[1]) if err != nil { log.Fatal("Error: ", "Could not base64 decode salt: ", err) } if len(salt) != hashlength { log.Fatalf("Error: Salt not required size: %d needing %d bytes", len(salt), hashlength) } } else { n, err := rand.Read(salt) if n != len(salt) || err != nil { log.Fatal("Error: ", "Could not generate salt: ", err) } } var dk []byte switch opts.Kdname { case "pbkdf2": dk = pbkdf2.Key(pw, salt, opts.Rounds, hashlength, h) case "scrypt": dk, err = scrypt.Key(pw, salt, 1<<uint(opts.Cost), 8, 1, 32) if err != nil { log.Fatal("Error: ", "in scrypt: ", err) } case "bcrypt": if opts.Cost < bcrypt.MinCost || opts.Cost > bcrypt.MaxCost { log.Fatal("Error: ", "bcrypt: unsupported cost value") } dk, err = bcrypt.GenerateFromPassword(pw, opts.Cost) if err != nil { log.Fatal("Error: ", "in bcrypt: ", err) } // safeguard against bcrypt working with wrong cost value if real_cost, err := bcrypt.Cost(dk); err != nil { panic(err) } else if opts.Cost != real_cost { log.Fatal("Error: ", "bcrypt did not generate hash with user provided cost value") } default: log.Fatal("Error: unknown key derivation") } if opts.Hmacenc != "" { hmac_enc := hmac.New(h, hmacenc_bin) if _, err = hmac_enc.Write(dk); err != nil { log.Fatal("Error: error encrypting hash with hmac: ", err) } dk = hmac_enc.Sum(nil) } salt_b64 := base64.URLEncoding.EncodeToString(salt) pwhash_b64 := base64.URLEncoding.EncodeToString(dk) fmt.Printf("%s$%s\n", salt_b64, pwhash_b64) //fmt.Printf("%x\n", dk) }