Cria sinalizadores para script de senha bash

2

Eu fiz um script bash para criar uma senha aleatória e (se o "flag" estiver correto) armazene-a em um arquivo txt. Eu adicionei um alias para o caminho do script para que eu possa executá-lo como um único comando. Eu chamo de "rand".

O que eu já tenho funciona, mas não é de forma alguma elegante. Primeiro de tudo, eu quero que as bandeiras sejam incluídas em qualquer ordem, não consertadas como estão agora. No momento eu só tenho dois. -p para "senha segura" e -s para "salvar". Eu quero adicionar pelo menos mais um, -u para "nome de usuário", mas desde que eu não estou feliz com o estado atual do código, eu tenho que adiar aquele por enquanto.

Eu quero executar o comando assim:

rand -FLAGS_IN_ANY_ORDER -NUMBER -FILE_NAME

Aqui está a versão atual do código:

#!/bin/bash

num=$#
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

if [[ "$1" = "-p" ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9.-@ \
    | head -c $2 ; echo

elif [[ "$1" = "-ps" ]] && [[ "$3" = * ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9.-@ \
    | head -c $2 > $DIR/$3.txt && echo >> $DIR/$3.txt

    cat $DIR/$3.txt

elif [[ "$1" = "-s" ]] && [[ "$3" = * ]]; then
    cat /dev/urandom \
    | tr -dc A-Za-z0-9 \
    | head -c $2 > $DIR/$3.txt && echo >> $DIR/$3.txt

    cat $DIR/$3.txt

else
    cat /dev/urandom \
    | tr -dc A-Za-z0-9 \
    | head -c $1 ; echo
fi

Idealmente, eu quero o menor número de linhas de código possível, o que não será o caso com todas essas instruções if .

Eu tentei getopts e case s, mas não consegui fazer isso funcionar. Esta é a única versão do código que tenho até agora que não quebra.

Na minha defesa, porém, sou novo em sistemas semelhantes ao UNIX e só comecei o script bash algumas semanas atrás.

Como você resolveria isso?

    
por discipulus 04.03.2018 / 01:35

3 respostas

3

O script a seguir é mais longo que o seu, mas todo o comprimento extra é porque adicionei uma função usage para imprimir uma mensagem de ajuda de uso (ela também serve para imprimir uma mensagem de erro quando uma condição de erro é detectada) e adicionada muitos comentários para explicar o que o código faz e por quê.

O código que analisa as opções e faz o trabalho é muito mais curto e mais simples, e mais fácil de expandir (por exemplo, atualmente leva uma opção -u username , mas não faz nada com isso porque você não mencionou como deve ser usado). Todo o manuseio de casos especiais é eliminado, as "decisões" sobre o que fazer e como fazer são feitas na declaração de caso e em alguns testes if / then imediatamente depois.

Eu também adicionei uma opção -d para que você possa especificar um diretório de saída ... mas isso é redundante como qualquer coisa, exceto um exemplo de como adicionar outra opção, porque eu também adicionei código que verifica se O arquivo de saída contém um caractere / e apenas prefixa $outdir (renomeado de $DIR - é um bom hábito usar nomes de variáveis minúsculas no seu script, reservando maiúsculas para utilitários padrão) caso não seja. ou seja, você pode usar -f /path/to/file ou -d /path/to -f file .

BTW, em vez de criar um alias com o caminho completo para esse script, recomendo apenas colocá-lo em /usr/local/bin ou criar um subdiretório bin/ no diretório home e adicionar ~/bin ao seu $ PATH. Então você pode simplesmente escrever e salvar quantos scripts personalizados desejar.

#!/bin/bash

usage() {
  # this function prints a usage summary, optionally printing an error message
  local ec=0

  if [ $# -ge 2 ] ; then
    # if printing an error message, the first arg is the exit code, and
    # all remaining args are the message.
    ec="$1" ; shift
    printf "%s\n\n" "$*" >&2
  fi

  cat <<EOF
Usage:
       $(basename $0) [-p | -s file | -u <user> | -d <dir> ] <[-l] length>

Generates a random password of specified length, containing only
alpha-numeric characters.

Required arguments:

  -l <length>     Length of password.  Just the length without '-l' also works.

Options:

  -p              Allow some punctuation characters in password.
  -s <file>       Save output to filename.
  -d <dir>        Output directory for the save file
  -u <user>       Username.  At present this does nothing.

  -h              This help message.
EOF

  exit $ec
}

# Initialise variables
length=''
file=''
username=''
pattern='A-Za-z0-9'
outdir=$(dirname "$0")

# -s, -l, -u and -d require an arg. -h and -p don't. The leading ':' in
# the getopts string enables 'silent' error mode (i.e. we handle
# any errors ourselves)
while getopts ':hps:l:u:d:' opt; do
  case "$opt" in 
    p) pattern='A-Za-z0-9.-@' ;;
    s) file="$OPTARG" ;;
    l) length="$OPTARG" ;;
    u) username="$OPTARG" ;;
    d) outdir="$OPTARG" ;;

    h) usage ;;
    :) usage 1 "-$OPTARG requires an argument" ;;
    ?) usage 1 "Unknown option '$opt'" ;;
  esac
done

shift $((OPTIND -1))

# Length can be specified as either '-l nnn' or just 'nnn' on the cmd line
# this code exits with an error message if no length is specified.
#
# It would probably be better to give length a default value instead.
# Just change 'length=''' under the Initialise variables comment above
# to whatever you want as the default, and then delete the '[ -z "$1" ]'
# line below. Remember to update the usage message - documentation should
# always match what the code does.
if [ -z "$length" ] ; then
  [ -z "$1" ] && usage 1 "Length option is required"
  length="$1"
  shift
fi

# if there are any remaining args on the line, we don't know what to do
# with them, so exit with an error message.
[ -n "$*" ] && usage 1 "Unknown arguments: '$*'"

password=$(cat /dev/urandom | tr -dc "$pattern" | head -c "$length")

# I don't know why you want an extra newline in the output, but do it anyway.
printf "$password\n\n"

if [ -n "$file" ] ; then
   # if $file doesn't have a /, pre-pend "$outdir"
   [[ $file =~ '/' ]] || file="$outdir/$file"
   printf "$password\n\n" > "$file" 
fi

BTW, vários programas de geração de senha já existem. por exemplo. pwgen e makepasswd .

Eu usei pwgen para gerar senhas de 16 + dígitos até que eu escrevesse meu próprio passphrase generator que selecionasse aleatoriamente um número de palavras de /usr/share/dict/words e as unisse com números de 1-3 dígitos gerados aleatoriamente e / ou pontuação. /usr/share/dict/words é todo em minúsculas, então meu script capitaliza aleatoriamente algumas das letras. Isso gera senhas muito longas, mas fáceis de lembrar. Os dígitos aleatórios, pontuação e uso de letras maiúsculas aumentam o espaço de pesquisa para o cracking de força bruta e ajudam a garantir que a força da senha não seja comprometida pela previsibilidade do dicionário de palavras.

por exemplo,

$ random-password.sh 
31 surVeying % deRAngement 6 ratiocinations 51 sunDowns
$ printf '%s' '31 surVeying % deRAngement 6 ratiocinations 51 sunDowns' | wc -c
55

O tamanho da senha é muito mais importante que a complexidade - qualquer senha com menos de 10 caracteres pode ser quebrada em pouco tempo com hardware moderno (um segundo ou menos para < = 7 caracteres, horas para 8 caracteres, alguns meses para 10 caracteres). Uma senha de 55 caracteres é efetivamente ininterrupta durante a vida útil do universo pela tecnologia atual (portanto, deve ser segura por pelo menos 10 anos).

O problema é que quanto mais longa for a senha, mais simples será para um ser humano conseguir se lembrar dela. Acho que só preciso lembrar o início da senha e, digitando-a algumas vezes, associo automaticamente (e, portanto, lembro) os próximos dígitos e palavras na frase sem sentido.

    
por 04.03.2018 / 04:56
0

É assim que eu normalmente faço minhas opções, mas acho que isso vai acabar fazendo o seu script mais longo ... ele tem opções em qualquer ordem, no entanto.

while (( $# )); do
    case $1 in
        -n)
            shift
            number=$1
        ;;
        -f)
            shift
            file_name=$1
        ;;
        -p)
            shift
            password=$1
            run=pass
        ;;
        -s)
            run=save
        ;;
        -u)
            shift
            username=$1
        ;;
        *)
            thing=$1
            save=what
        ;;
        shift
    esac
done
case $save in
    save)
        cat /dev/urandom \
            | tr -dc A-Za-z0-9.-@ \
            | head -c "$number" > "${DIR}/${file_name}.txt" && echo >> "${DIR}/${file_name}.txt"
        cat "${DIR}/${file_name}.txt"
    ;;
    pass)
        cat /dev/urandom \
        | tr -dc A-Za-z0-9.-@ \
        | head -c "$number" ; echo
    ;;
    *)
        cat /dev/urandom \
        | tr -dc A-Za-z0-9 \
        | head -c "$thing" ; echo
    ;;
esac

O loop while será executado enquanto ainda houver opções. shift se move para a próxima opção, então ele passará por cada um dos argumentos que você passar e os executará através da instrução case para manipulá-los apropriadamente.

    
por 04.03.2018 / 02:00
0

Esta é uma variação do seu script:

#!/bin/bash

opt_l=8                 # password length is 8 by default
opt_o=/dev/stdout       # write to standard output by default
opt_s=0                 # insecure password by default
opt_u=                  # no default username

while getopts 'l:o:su:' opt; do
    case "$opt" in
        l) opt_l=$OPTARG ;;
        o) opt_o=$OPTARG ;;
        s) opt_s=1       ;;
        u) opt_u=$OPTARG ;;
        *) echo 'Error parsing options' >&2
           exit 1
   esac
done

args=( "$opt_l" 1 )

if (( opt_s )); then
    args=( -s "${args[@]}" )
fi

if [ -n "$opt_u" ]; then
    printf '%s: %s\n' "$opt_u" "$( pwgen "${args[@]}" )" >"$opt_o"
else
    pwgen "${args[@]}" >"$opt_o"
fi

Eu mudei alguns dos nomes dos sinalizadores para que eles estejam mais alinhados com o que as ferramentas padrão usam. Em vez de imprimir a mensagem de erro ao encontrar um sinalizador sem suporte, você poderia enviar algumas informações de uso ao usuário.

O principal é o uso de getopts aqui. É necessária uma especificação como uma string das opções que você está usando. O : na cadeia significa que o caractere de opção precedente recebe um argumento (outras opções são booleanas).

No loop while , $opt será a opção e $OPTARG será o argumento da opção, se o sinalizador aceitar um argumento.

Geralmente, também ocorre shift "$(( OPTIND - 1 ))" após o loop, mas como esse script não aceita operandos adicionais na linha de comando, além das opções e seus argumentos, isso não é necessário. O shift garantiria que operandos adicionais estivessem disponíveis como $1 , $2 etc.

Você usaria o script como

$ ./script.sh -l 4         # generates password of length 4 in the terminal
$ ./script.sh -l 4 -o file # generates password of length 4 and saves to "file"
$ ./script.sh -u bob       # generates password of length 8, prepends with username "bob"
$ ./script.sh -s -u bob    # generates more random password of length 8, prepends with username "bob"

-s -u bob é igual a -ubob -s e -subob e -u alice -s -u bob (as opções posteriores substituem as anteriores).

Seria bastante fácil remover pwgen disso e usar outro gerador de senhas. Para simplificar isso, é possível colocar o gerador de senhas em sua própria função, o que significa que o script principal nunca precisaria ser modificado ao substituir o gerador:

#!/bin/bash

passgen () {
    local args=( "$opt_l" 1 )

    if (( opt_s )); then
        args=( -s "${args[@]}" )
    fi

    pwgen "${args[@]}"
}

opt_l=8                 # password length is 8 by default
opt_o=/dev/stdout       # write to standard output by default
opt_s=0                 # insecure password by default
opt_u=                  # no default username

while getopts 'l:o:su:' opt; do
    case "$opt" in
        l) opt_l=$OPTARG ;;
        o) opt_o=$OPTARG ;;
        s) opt_s=1       ;;
        u) opt_u=$OPTARG ;;
        *) echo 'Error parsing options' >&2
           exit 1
   esac
done

if [ -n "$opt_u" ]; then
    printf '%s: %s\n' "$opt_u" "$( passgen )" >"$opt_o"
else
    passgen >"$opt_o"
fi

Por exemplo, este é mais ou menos seu gerador:

passgen () {
    local source=/dev/urandom
    local chars='A-Za-z0-9'

    if (( opt_s )); then
        chars="$chars"'.-@'
    fi

    tr -dc "$chars" <"$source" | head -c "$opt_l"
}

... e o seguinte gera uma senha usando palavras de /usr/share/dict/words (o número de palavras é retirado da opção -l ). Se a opção -s for usada, ela adicionará um número aleatório ao final, em vez da última palavra.

passgen () {
    local dict=/usr/share/dict/words
    local numwords=$opt_l

    if (( opt_s )); then
        numwords=$(( numwords - 1 ))
    fi

    {
        shuf -n "$numwords" "$dict"

        if (( opt_s )); then
            printf '%d\n' "$RANDOM"
        fi
    } | tr '\n' ' ' | sed 's/ $//'
}

Rodando com esta última função passgen , o comando

./script.sh -s -u bob -l 3

pode gerar algo como

bob: cassis befluster 22625
    
por 05.03.2018 / 09:10