Skip to content

Commit

Permalink
Add 'salt_encoding' option to plugin
Browse files Browse the repository at this point in the history
Updated HashCompare to use new saltEncoding param, defaults to 'base64' and accepts 'utf-8' as alternative as-is (can easily be extended)
Add automatic key length setting to HashCompare, to accomodate non-standard hash lengths in comparison
Updated HashCompare compare calls across *all* plugins to use salt encoding param, either from supplied 'salt_encoding' option or defaulting to 'base64'
Updated pw util to include flags for salt encoding (default base64) and key length (default 64).
  • Loading branch information
coldfire84 committed Jan 16, 2020
1 parent bbb9ee9 commit 44f274e
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 27 deletions.
9 changes: 8 additions & 1 deletion backends/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type AclRecord struct {
type Files struct {
PasswordPath string
AclPath string
SaltEncoding string
CheckAcls bool
Users map[string]*FileUser //Users keeps a registry of username/FileUser pairs, holding a user's password and Acl records.
AclRecords []AclRecord
Expand All @@ -59,6 +60,12 @@ func NewFiles(authOpts map[string]string, logLevel log.Level) (Files, error) {
return files, errors.New("Files backend error: no password path given.\n")
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
files.SaltEncoding = saltEncoding
} else {
files.SaltEncoding = "base64"
}

if aclPath, ok := authOpts["acl_path"]; ok {
files.AclPath = aclPath
files.CheckAcls = true
Expand Down Expand Up @@ -290,7 +297,7 @@ func (o Files) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, fileUser.Password) {
if common.HashCompare(password, fileUser.Password, o.SaltEncoding) {
return true
}

Expand Down
9 changes: 8 additions & 1 deletion backends/mongo.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Mongo struct {
Port string
Username string
Password string
SaltEncoding string
DBName string
UsersCollection string
AclsCollection string
Expand Down Expand Up @@ -70,6 +71,12 @@ func NewMongo(authOpts map[string]string, logLevel log.Level) (Mongo, error) {
m.Password = mongoPassword
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
m.SaltEncoding = saltEncoding
} else {
m.SaltEncoding = "base64"
}

if mongoDBName, ok := authOpts["mongo_dbname"]; ok {
m.DBName = mongoDBName
}
Expand Down Expand Up @@ -124,7 +131,7 @@ func (o Mongo) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, user.PasswordHash) {
if common.HashCompare(password, user.PasswordHash, o.SaltEncoding) {
return true
}

Expand Down
9 changes: 8 additions & 1 deletion backends/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Mysql struct {
DBName string
User string
Password string
SaltEncoding string
UserQuery string
SuperuserQuery string
AclQuery string
Expand Down Expand Up @@ -93,6 +94,12 @@ func NewMysql(authOpts map[string]string, logLevel log.Level) (Mysql, error) {
missingOptions += " mysql_password"
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
mysql.SaltEncoding = saltEncoding
} else {
mysql.SaltEncoding = "base64"
}

if userQuery, ok := authOpts["mysql_userquery"]; ok {
mysql.UserQuery = userQuery
} else {
Expand Down Expand Up @@ -215,7 +222,7 @@ func (o Mysql) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, pwHash.String) {
if common.HashCompare(password, pwHash.String, o.SaltEncoding) {
return true
}

Expand Down
9 changes: 8 additions & 1 deletion backends/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Postgres struct {
DBName string
User string
Password string
SaltEncoding string
UserQuery string
SuperuserQuery string
AclQuery string
Expand Down Expand Up @@ -77,6 +78,12 @@ func NewPostgres(authOpts map[string]string, logLevel log.Level) (Postgres, erro
missingOptions += " pg_password"
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
postgres.SaltEncoding = saltEncoding
} else {
postgres.SaltEncoding = "base64"
}

if userQuery, ok := authOpts["pg_userquery"]; ok {
postgres.UserQuery = userQuery
} else {
Expand Down Expand Up @@ -161,7 +168,7 @@ func (o Postgres) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, pwHash.String) {
if common.HashCompare(password, pwHash.String, o.SaltEncoding) {
return true
}

Expand Down
19 changes: 13 additions & 6 deletions backends/redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ import (
)

type Redis struct {
Host string
Port string
Password string
DB int32
Conn *goredis.Client
Host string
Port string
Password string
SaltEncoding string
DB int32
Conn *goredis.Client
}

func NewRedis(authOpts map[string]string, logLevel log.Level) (Redis, error) {
Expand All @@ -43,6 +44,12 @@ func NewRedis(authOpts map[string]string, logLevel log.Level) (Redis, error) {
redis.Password = redisPassword
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
redis.SaltEncoding = saltEncoding
} else {
redis.SaltEncoding = "base64"
}

if redisDB, ok := authOpts["redis_db"]; ok {
db, err := strconv.ParseInt(redisDB, 10, 32)
if err == nil {
Expand Down Expand Up @@ -84,7 +91,7 @@ func (o Redis) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, pwHash) {
if common.HashCompare(password, pwHash, o.SaltEncoding) {
return true
}

Expand Down
9 changes: 8 additions & 1 deletion backends/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Sqlite struct {
UserQuery string
SuperuserQuery string
AclQuery string
SaltEncoding string
}

func NewSqlite(authOpts map[string]string, logLevel log.Level) (Sqlite, error) {
Expand Down Expand Up @@ -58,6 +59,12 @@ func NewSqlite(authOpts map[string]string, logLevel log.Level) (Sqlite, error) {
sqlite.AclQuery = aclQuery
}

if saltEncoding, ok := authOpts["salt_encoding"]; ok {
sqlite.SaltEncoding = saltEncoding
} else {
sqlite.SaltEncoding = "base64"
}

//Exit if any mandatory option is missing.
if !sqliteOk {
return sqlite, errors.Errorf("Sqlite backend error: missing options%s.\n", missingOptions)
Expand Down Expand Up @@ -96,7 +103,7 @@ func (o Sqlite) GetUser(username, password string) bool {
return false
}

if common.HashCompare(password, pwHash.String) {
if common.HashCompare(password, pwHash.String, o.SaltEncoding) {
return true
}

Expand Down
44 changes: 29 additions & 15 deletions common/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,30 +80,28 @@ func match(route []string, topic []string) bool {
// making it easy to recreate the hash for password checking, even if we change
// the default criteria here.
// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
func Hash(password string, saltSize int, iterations int, algorithm string) (string, error) {
func Hash(password string, saltSize int, iterations int, algorithm string, saltEncoding string, keylen int) (string, error) {
// Generate a random salt value, 128 bits.
salt := make([]byte, saltSize)
_, err := rand.Read(salt)
if err != nil {
return "", errors.Wrap(err, "read random bytes error")
}

return hashWithSalt(password, salt, iterations, algorithm), nil
return hashWithSalt(password, salt, iterations, algorithm, saltEncoding, keylen), nil
}

// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
func hashWithSalt(password string, salt []byte, iterations int, algorithm string) string {
func hashWithSalt(password string, salt []byte, iterations int, algorithm string, saltEncoding string, keylen int) string {
// Generate the hash. This should be a little painful, adjust ITERATIONS
// if it needs performance tweeking. Greatly depends on the hardware.
// NOTE: We store these details with the returned hash, so changes will not
// affect our ability to do password compares.
shaSize := sha512.Size
shaHash := sha512.New
if algorithm == "sha256" {
shaSize = sha256.Size
shaHash = sha256.New
}
hash := pbkdf2.Key([]byte(password), salt, iterations, shaSize, shaHash)
hash := pbkdf2.Key([]byte(password), salt, iterations, keylen, shaHash)

// Build up the parameters and hash into a single string so we can compare
// other string to the same hash. Note that the hash algorithm is hard-
Expand All @@ -115,25 +113,41 @@ func hashWithSalt(password string, salt []byte, iterations int, algorithm string
buffer.WriteString(fmt.Sprintf("%s$", algorithm))
buffer.WriteString(strconv.Itoa(iterations))
buffer.WriteString("$")
buffer.WriteString(base64.StdEncoding.EncodeToString(salt))
// Re-encode salt, using encoding supplied in saltEncoding param
if saltEncoding == "utf-8" {
buffer.WriteString(string(salt))

} else {
buffer.WriteString(base64.StdEncoding.EncodeToString(salt))
}
buffer.WriteString("$")
buffer.WriteString(base64.StdEncoding.EncodeToString(hash))

//log.Println("Generated: ", buffer.String())
return buffer.String()
}

// HashCompare verifies that passed password hashes to the same value as the
// passed passwordHash.
// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
func HashCompare(password string, passwordHash string) bool {
// SPlit the hash string into its parts.
func HashCompare(password string, passwordHash string, saltEncoding string) bool {
//log.Println("Supplied: ", passwordHash)
// Split the hash string into its parts.
hashSplit := strings.Split(passwordHash, "$")

// Get the iterations and the salt and use them to encode the password
// being compared.cre
// Get the iterations from PBKDF2 string
iterations, _ := strconv.Atoi(hashSplit[2])
salt, _ := base64.StdEncoding.DecodeString(hashSplit[3])
// Convert salt to bytes, using encoding supplied in saltEncoding param
salt := []byte{}
if saltEncoding == "utf-8" {
salt = []byte(hashSplit[3])
} else {
salt, _ = base64.StdEncoding.DecodeString(hashSplit[3])
}
// Work out key length, assumes base64 encoding
hash, _ := base64.StdEncoding.DecodeString(hashSplit[4])
keylen := len(hash)
// Get the algorithm from PBKDF2 string
algorithm := hashSplit[1]
newHash := hashWithSalt(password, salt, iterations, algorithm)
// Generate new PBKDF2 hash to compare against supplied PBKDF2 string
newHash := hashWithSalt(password, salt, iterations, algorithm, saltEncoding, keylen)
return newHash == passwordHash
}
4 changes: 3 additions & 1 deletion pw-gen/pw.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ func main() {
var HashIterations = flag.Int("i", 100000, "hash iterations")
var password = flag.String("p", "", "password")
var saltSize = flag.Int("s", 16, "salt size")
var saltEncoding = flag.String("e", "base64", "salt encoding")
var keylen = flag.Int("l", 64, "key length, reccommend 32 for sha256 and 64 for sha512")

flag.Parse()

pwHash, err := common.Hash(*password, *saltSize, *HashIterations, *algorithm)
pwHash, err := common.Hash(*password, *saltSize, *HashIterations, *algorithm, *saltEncoding, *keylen)
if err != nil {
fmt.Printf("error: %s\n", err)
} else {
Expand Down

0 comments on commit 44f274e

Please sign in to comment.