mirror of
				https://github.com/therootcompany/golib.git
				synced 2025-10-31 13:12:51 +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
 | |
| }
 |