mirror of
https://github.com/Luzifer/password.git
synced 2024-11-09 18:00:03 +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
14
cmdGet.go
14
cmdGet.go
|
@ -1,9 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Luzifer/password/hasher"
|
||||
"github.com/Luzifer/password/lib"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -15,6 +17,7 @@ func getCmdGet() *cobra.Command {
|
|||
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().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)
|
||||
}
|
||||
|
||||
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 {
|
||||
Length int
|
||||
SpecialCharacters bool
|
||||
JSON bool
|
||||
}
|
||||
|
||||
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