2019-10-10 14:37:27 -06:00
// Copyright 2015 Matthew Holt and The Caddy Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package caddyauth
import (
2020-05-11 15:10:47 -05:00
"bufio"
"bytes"
2019-10-10 14:37:27 -06:00
"fmt"
2020-05-11 15:10:47 -05:00
"os"
2020-05-21 15:09:49 -04:00
"os/signal"
2019-10-10 14:37:27 -06:00
2023-02-24 18:09:12 -05:00
"github.com/spf13/cobra"
2021-03-29 20:39:08 +02:00
"golang.org/x/term"
2023-08-14 23:41:15 +08:00
caddycmd "github.com/caddyserver/caddy/v2/cmd"
"github.com/caddyserver/caddy/v2"
2019-10-10 14:37:27 -06:00
)
func init ( ) {
caddycmd . RegisterCommand ( caddycmd . Command {
Name : "hash-password" ,
2025-10-07 01:27:06 +02:00
Usage : "[--plaintext <password>] [--algorithm <argon2id|bcrypt>] [--bcrypt-cost <difficulty>] [--argon2id-time <iterations>] [--argon2id-memory <KiB>] [--argon2id-threads <n>] [--argon2id-keylen <bytes>]" ,
2019-10-10 14:37:27 -06:00
Short : "Hashes a password and writes base64" ,
Long : `
Convenient way to hash a plaintext password . The resulting
hash is written to stdout as a base64 string .
2025-10-07 01:27:06 +02:00
-- plaintext
The password to hash . If omitted , it will be read from stdin .
If Caddy is attached to a controlling TTY , the input will not be echoed .
2020-05-11 15:10:47 -05:00
2025-10-07 01:27:06 +02:00
-- algorithm
Selects the hashing algorithm . Valid options are :
* ' argon2id ' ( recommended for modern security )
* ' bcrypt ' ( legacy , slower , configurable cost )
2025-08-11 13:26:18 +02:00
2025-10-07 01:27:06 +02:00
bcrypt - specific parameters :
-- bcrypt - cost
Sets the bcrypt hashing difficulty . Higher values increase security by
making the hash computation slower and more CPU - intensive .
Must be within the valid range [ bcrypt . MinCost , bcrypt . MaxCost ] .
If omitted or invalid , the default cost is used .
Argon2id - specific parameters :
-- argon2id - time
Number of iterations to perform . Increasing this makes
hashing slower and more resistant to brute - force attacks .
-- argon2id - memory
Amount of memory to use during hashing .
Larger values increase resistance to GPU / ASIC attacks .
-- argon2id - threads
Number of CPU threads to use . Increase for faster hashing
on multi - core systems .
-- argon2id - keylen
Length of the resulting hash in bytes . Longer keys increase
security but slightly increase storage size .
2019-10-10 14:37:27 -06:00
` ,
2023-02-24 18:09:12 -05:00
CobraFunc : func ( cmd * cobra . Command ) {
cmd . Flags ( ) . StringP ( "plaintext" , "p" , "" , "The plaintext password" )
2025-10-07 01:27:06 +02:00
cmd . Flags ( ) . StringP ( "algorithm" , "a" , bcryptName , "Name of the hash algorithm" )
2025-08-11 13:26:18 +02:00
cmd . Flags ( ) . Int ( "bcrypt-cost" , defaultBcryptCost , "Bcrypt hashing cost (only used with 'bcrypt' algorithm)" )
2025-10-07 01:27:06 +02:00
cmd . Flags ( ) . Uint32 ( "argon2id-time" , defaultArgon2idTime , "Number of iterations for Argon2id hashing. Increasing this makes the hash slower and more resistant to brute-force attacks." )
cmd . Flags ( ) . Uint32 ( "argon2id-memory" , defaultArgon2idMemory , "Memory to use in KiB for Argon2id hashing. Larger values increase resistance to GPU/ASIC attacks." )
cmd . Flags ( ) . Uint8 ( "argon2id-threads" , defaultArgon2idThreads , "Number of CPU threads to use for Argon2id hashing. Increase for faster hashing on multi-core systems." )
cmd . Flags ( ) . Uint32 ( "argon2id-keylen" , defaultArgon2idKeylen , "Length of the resulting Argon2id hash in bytes. Longer hashes increase security but slightly increase storage size." )
2023-02-24 18:09:12 -05:00
cmd . RunE = caddycmd . WrapCommandFuncForCobra ( cmdHashPassword )
} ,
2019-10-10 14:37:27 -06:00
} )
}
func cmdHashPassword ( fs caddycmd . Flags ) ( int , error ) {
2020-05-11 15:10:47 -05:00
var err error
2019-10-10 14:37:27 -06:00
algorithm := fs . String ( "algorithm" )
plaintext := [ ] byte ( fs . String ( "plaintext" ) )
2025-08-11 13:26:18 +02:00
bcryptCost := fs . Int ( "bcrypt-cost" )
2019-10-10 14:37:27 -06:00
2019-10-28 15:08:45 -06:00
if len ( plaintext ) == 0 {
2020-05-21 15:09:49 -04:00
fd := int ( os . Stdin . Fd ( ) )
2021-03-29 20:39:08 +02:00
if term . IsTerminal ( fd ) {
2020-05-21 15:09:49 -04:00
// ensure the terminal state is restored on SIGINT
2021-03-29 20:39:08 +02:00
state , _ := term . GetState ( fd )
2020-12-01 22:27:46 +07:00
c := make ( chan os . Signal , 1 )
2020-05-21 15:09:49 -04:00
signal . Notify ( c , os . Interrupt )
go func ( ) {
<- c
2021-03-29 20:39:08 +02:00
_ = term . Restore ( fd , state )
2020-05-21 15:09:49 -04:00
os . Exit ( caddy . ExitCodeFailedStartup )
} ( )
defer signal . Stop ( c )
fmt . Fprint ( os . Stderr , "Enter password: " )
2021-03-29 20:39:08 +02:00
plaintext , err = term . ReadPassword ( fd )
2020-05-21 15:09:49 -04:00
fmt . Fprintln ( os . Stderr )
2020-05-11 15:10:47 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2020-05-21 15:09:49 -04:00
fmt . Fprint ( os . Stderr , "Confirm password: " )
2021-03-29 20:39:08 +02:00
confirmation , err := term . ReadPassword ( fd )
2020-05-21 15:09:49 -04:00
fmt . Fprintln ( os . Stderr )
2020-05-11 15:10:47 -05:00
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
if ! bytes . Equal ( plaintext , confirmation ) {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "password does not match" )
}
} else {
rd := bufio . NewReader ( os . Stdin )
plaintext , err = rd . ReadBytes ( '\n' )
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
plaintext = plaintext [ : len ( plaintext ) - 1 ] // Trailing newline
}
if len ( plaintext ) == 0 {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "plaintext is required" )
}
2019-10-28 15:08:45 -06:00
}
2019-10-10 14:37:27 -06:00
var hash [ ] byte
2022-09-05 15:32:58 -04:00
var hashString string
2019-10-10 14:37:27 -06:00
switch algorithm {
2025-10-07 01:27:06 +02:00
case bcryptName :
2025-08-11 13:26:18 +02:00
hash , err = BcryptHash { cost : bcryptCost } . Hash ( plaintext )
2025-10-07 01:27:06 +02:00
hashString = string ( hash )
case argon2idName :
time , err := fs . GetUint32 ( "argon2id-time" )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "failed to get argon2id time parameter: %w" , err )
}
memory , err := fs . GetUint32 ( "argon2id-memory" )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "failed to get argon2id memory parameter: %w" , err )
}
threads , err := fs . GetUint8 ( "argon2id-threads" )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "failed to get argon2id threads parameter: %w" , err )
}
keyLen , err := fs . GetUint32 ( "argon2id-keylen" )
if err != nil {
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "failed to get argon2id keylen parameter: %w" , err )
}
hash , _ = Argon2idHash {
time : time ,
memory : memory ,
threads : threads ,
keyLen : keyLen ,
} . Hash ( plaintext )
2022-09-05 15:32:58 -04:00
hashString = string ( hash )
2019-10-10 14:37:27 -06:00
default :
return caddy . ExitCodeFailedStartup , fmt . Errorf ( "unrecognized hash algorithm: %s" , algorithm )
}
if err != nil {
return caddy . ExitCodeFailedStartup , err
}
2022-09-05 15:32:58 -04:00
fmt . Println ( hashString )
2019-10-10 14:37:27 -06:00
return 0 , nil
}