mirror of
https://github.com/therootcompany/golib.git
synced 2025-10-07 01:28:19 +00:00
150 lines
3.3 KiB
Go
150 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/pbkdf2"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var version = "v1.0.0"
|
|
var help = "pbkdf2-sha256 [password=random] [salt=random] [iterations=1000] [keySize=16]"
|
|
|
|
func main() {
|
|
if len(os.Args) > 1 {
|
|
switch os.Args[1] {
|
|
case "-V", "version", "-version", "--version":
|
|
fmt.Println(version)
|
|
return
|
|
case "help", "-help", "--help":
|
|
fmt.Println("Usage:", help)
|
|
os.Exit(0)
|
|
return
|
|
}
|
|
}
|
|
|
|
var password string
|
|
var salt []byte
|
|
var iterations int
|
|
var keySize int
|
|
var err error
|
|
|
|
// Default values
|
|
iterations = 1000
|
|
keySize = 16
|
|
|
|
// Parse arguments
|
|
args := os.Args[1:]
|
|
if len(args) > 4 {
|
|
fmt.Fprintf(os.Stderr, "USAGE\n\t%s\n", help)
|
|
return
|
|
}
|
|
|
|
// Password
|
|
if len(args) > 0 && args[0] != "" {
|
|
password = args[0]
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "\nUSAGE\n\t%s\n\n", help)
|
|
rnd := make([]byte, 8)
|
|
_, _ = rand.Read(rnd)
|
|
hexPass := hex.EncodeToString(rnd)
|
|
password = fmt.Sprintf("%s-%s-%s-%s", hexPass[:4], hexPass[4:8], hexPass[8:12], hexPass[12:])
|
|
fmt.Printf("password : %s\n", password)
|
|
}
|
|
|
|
// Salt
|
|
if len(args) > 1 && args[1] != "" {
|
|
saltStr := args[1]
|
|
salt, err = parseHexOrBase64(saltStr)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error decoding salt: %v\n", err)
|
|
return
|
|
}
|
|
} else {
|
|
salt = make([]byte, 16)
|
|
_, _ = rand.Read(salt)
|
|
fmt.Printf("salt : %s\n", base64.RawURLEncoding.EncodeToString(salt))
|
|
}
|
|
|
|
// Iterations
|
|
if len(args) > 2 && args[2] != "0" && args[2] != "" {
|
|
iterations, err = parseInt(args[2])
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing iterations: %v\n", err)
|
|
return
|
|
}
|
|
} else {
|
|
fmt.Printf("iterations : %d\n", iterations)
|
|
}
|
|
|
|
// Key size
|
|
if len(args) > 3 && args[3] != "0" && args[3] != "" {
|
|
keySize, err = parseInt(args[3])
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing key size: %v\n", err)
|
|
return
|
|
}
|
|
} else {
|
|
fmt.Printf("key-size : %d\n", keySize)
|
|
}
|
|
|
|
// Generate PBKDF2 key
|
|
derivedKey, err := pbkdf2.Key(sha256.New, password, salt, iterations, keySize)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error parsing key size: %v\n", err)
|
|
return
|
|
}
|
|
fmt.Printf("derived-key: %s\n\n", hex.EncodeToString(derivedKey))
|
|
}
|
|
|
|
func parseHexOrBase64(data string) ([]byte, error) {
|
|
var b []byte
|
|
|
|
// Check if salt is hex (all uppercase or lowercase, valid hex chars)
|
|
isHex := true
|
|
for _, c := range data {
|
|
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
|
|
isHex = false
|
|
break
|
|
}
|
|
}
|
|
// Check for mixed case
|
|
hasUpper := strings.ContainsAny(data, "ABCDEF")
|
|
hasLower := strings.ContainsAny(data, "abcdef")
|
|
if isHex && !(hasUpper && hasLower) {
|
|
var err error
|
|
b, err = hex.DecodeString(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
// Assume URL-safe base64, convert to RFC base64
|
|
rfcData := strings.ReplaceAll(data, "-", "+")
|
|
rfcData = strings.ReplaceAll(rfcData, "_", "/")
|
|
rfcData = strings.ReplaceAll(rfcData, "=", "")
|
|
var err error
|
|
b, err = base64.RawStdEncoding.DecodeString(rfcData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func parseInt(s string) (int, error) {
|
|
n, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if n < 0 {
|
|
return 0, fmt.Errorf("value must be positive")
|
|
}
|
|
return n, nil
|
|
}
|