mirror of
https://github.com/Luzifer/password.git
synced 2024-12-20 12:51:17 +00:00
Implement JSON output with password hashes
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
9998a6a138
commit
6a76611a12
4 changed files with 199 additions and 1 deletions
16
cmdGet.go
16
cmdGet.go
|
@ -1,9 +1,11 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/Luzifer/password/hasher"
|
||||||
"github.com/Luzifer/password/lib"
|
"github.com/Luzifer/password/lib"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -15,6 +17,7 @@ func getCmdGet() *cobra.Command {
|
||||||
Run: actionCmdGet,
|
Run: actionCmdGet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Flags().BoolVarP(&flags.CLI.JSON, "json", "j", false, "return output in JSON format")
|
||||||
cmd.Flags().IntVarP(&flags.CLI.Length, "length", "l", 20, "length of the generated password")
|
cmd.Flags().IntVarP(&flags.CLI.Length, "length", "l", 20, "length of the generated password")
|
||||||
cmd.Flags().BoolVarP(&flags.CLI.SpecialCharacters, "special", "s", false, "use special characters in your password")
|
cmd.Flags().BoolVarP(&flags.CLI.SpecialCharacters, "special", "s", false, "use special characters in your password")
|
||||||
|
|
||||||
|
@ -33,5 +36,16 @@ func actionCmdGet(cmd *cobra.Command, args []string) {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(password)
|
if !flags.CLI.JSON {
|
||||||
|
fmt.Println(password)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes, err := hasher.GetHashMap(password)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to generate hashes: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
hashes["password"] = password
|
||||||
|
json.NewEncoder(os.Stdout).Encode(hashes)
|
||||||
}
|
}
|
||||||
|
|
1
flags.go
1
flags.go
|
@ -4,6 +4,7 @@ var flags = struct {
|
||||||
CLI struct {
|
CLI struct {
|
||||||
Length int
|
Length int
|
||||||
SpecialCharacters bool
|
SpecialCharacters bool
|
||||||
|
JSON bool
|
||||||
}
|
}
|
||||||
|
|
||||||
Server struct {
|
Server struct {
|
||||||
|
|
94
hasher/hasher.go
Normal file
94
hasher/hasher.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package hasher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
_ "crypto/sha1"
|
||||||
|
_ "crypto/sha256"
|
||||||
|
_ "crypto/sha512"
|
||||||
|
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"github.com/tredoe/osutil/user/crypt"
|
||||||
|
_ "github.com/tredoe/osutil/user/crypt/apr1_crypt"
|
||||||
|
_ "github.com/tredoe/osutil/user/crypt/sha256_crypt"
|
||||||
|
_ "github.com/tredoe/osutil/user/crypt/sha512_crypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hasherFunc func(password string) (string, error)
|
||||||
|
|
||||||
|
var (
|
||||||
|
implementations = map[string]hasherFunc{
|
||||||
|
"htpasswd_apr1": implHTAPR1,
|
||||||
|
"htpasswd_bcrypt": implBcrypt,
|
||||||
|
"htpasswd_sha256": implHTSHA256,
|
||||||
|
"htpasswd_sha512": implHTSHA512,
|
||||||
|
"sha1": implSHA1,
|
||||||
|
"sha256": implSHA256,
|
||||||
|
"sha512": implSHA512,
|
||||||
|
}
|
||||||
|
|
||||||
|
saltSet = []byte(`abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./`)
|
||||||
|
saltSize = 12
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetHashMap(password string) (map[string]string, error) {
|
||||||
|
result := map[string]string{}
|
||||||
|
|
||||||
|
for name, hf := range implementations {
|
||||||
|
h, err := hf(password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result[name] = h
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSalt() ([]byte, error) {
|
||||||
|
salt := make([]byte, saltSize)
|
||||||
|
saltSetLength := big.NewInt(int64(len(saltSet)))
|
||||||
|
|
||||||
|
for i := 0; i < saltSize; i++ {
|
||||||
|
pos, err := rand.Int(rand.Reader, saltSetLength)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
salt[i] = saltSet[pos.Int64()]
|
||||||
|
}
|
||||||
|
|
||||||
|
return salt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func implBcrypt(password string) (string, error) {
|
||||||
|
bc, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||||
|
return string(bc), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func genericHT(password, prefix string) (string, error) {
|
||||||
|
salt, err := getSalt()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypt.NewFromHash(prefix+string(salt)).Generate([]byte(password), append([]byte(prefix), salt...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func implHTAPR1(password string) (string, error) { return genericHT(password, "$apr1$") }
|
||||||
|
func implHTSHA256(password string) (string, error) { return genericHT(password, "$5$") }
|
||||||
|
func implHTSHA512(password string) (string, error) { return genericHT(password, "$6$") }
|
||||||
|
|
||||||
|
func generic(password string, h crypto.Hash) (string, error) {
|
||||||
|
w := h.New()
|
||||||
|
w.Write([]byte(password))
|
||||||
|
return fmt.Sprintf("%x", w.Sum(nil)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func implSHA1(password string) (string, error) { return generic(password, crypto.SHA1) }
|
||||||
|
func implSHA256(password string) (string, error) { return generic(password, crypto.SHA256) }
|
||||||
|
func implSHA512(password string) (string, error) { return generic(password, crypto.SHA512) }
|
89
hasher/hasher_test.go
Normal file
89
hasher/hasher_test.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
package hasher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go_helpers/str"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAvailableHashs(t *testing.T) {
|
||||||
|
hashs, err := GetHashMap("testpass")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Hash map generation failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, impl := range []string{
|
||||||
|
"htpasswd_apr1",
|
||||||
|
"htpasswd_bcrypt",
|
||||||
|
"htpasswd_sha256",
|
||||||
|
"htpasswd_sha512",
|
||||||
|
"sha1",
|
||||||
|
"sha256",
|
||||||
|
"sha512",
|
||||||
|
} {
|
||||||
|
if _, ok := hashs[impl]; !ok {
|
||||||
|
t.Errorf("Hash implementation %q is missing", impl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSalt(t *testing.T) {
|
||||||
|
knownSalts := []string{}
|
||||||
|
|
||||||
|
for i := 0; i < 100; i++ {
|
||||||
|
salt, err := getSalt()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Hash generation failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(salt) != saltSize {
|
||||||
|
t.Errorf("Salt did not have desired size of %d: %d", saltSize, len(salt))
|
||||||
|
}
|
||||||
|
|
||||||
|
ssalt := string(salt)
|
||||||
|
if str.StringInSlice(ssalt, knownSalts) {
|
||||||
|
t.Fatalf("Received collision of hashes: %q", ssalt)
|
||||||
|
}
|
||||||
|
|
||||||
|
knownSalts = append(knownSalts, ssalt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTPasswd(t *testing.T) {
|
||||||
|
hashs, err := GetHashMap("testpass")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Hash map generation failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hashs["htpasswd_sha512"]) != 102 || !strings.HasPrefix(hashs["htpasswd_sha512"], "$6$") {
|
||||||
|
t.Errorf("Invalid htpasswd SHA512 hash: %q", hashs["htpasswd_sha512"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hashs["htpasswd_sha256"]) != 59 || !strings.HasPrefix(hashs["htpasswd_sha256"], "$5$") {
|
||||||
|
t.Errorf("Invalid htpasswd SHA256 hash: %q", hashs["htpasswd_sha256"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hashs["htpasswd_apr1"]) != 37 || !strings.HasPrefix(hashs["htpasswd_apr1"], "$apr1$") {
|
||||||
|
t.Errorf("Invalid htpasswd APR1 hash: %q", hashs["htpasswd_apr1"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStandardHashs(t *testing.T) {
|
||||||
|
hashs, err := GetHashMap("testpass")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Hash map generation failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if hashs["sha1"] != "206c80413b9a96c1312cc346b7d2517b84463edd" {
|
||||||
|
t.Errorf("Invalid SHA1 hash: %q", hashs["sha1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if hashs["sha256"] != "13d249f2cb4127b40cfa757866850278793f814ded3c587fe5889e889a7a9f6c" {
|
||||||
|
t.Errorf("Invalid SHA256 hash: %q", hashs["sha256"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if hashs["sha512"] != "78ddc8555bb1677ff5af75ba5fc02cb30bb592b0610277ae15055e189b77fe3fda496e5027a3d99ec85d54941adee1cc174b50438fdc21d82d0a79f85b58cf44" {
|
||||||
|
t.Errorf("Invalid SHA512 hash: %q", hashs["sha512"])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue