Ukrywanie danych wejściowych użytkownika na terminalu w skrypcie Linux

121

Mam skrypt basha podobny do następującego:

#!/bin/bash

echo "Please enter your username";
read username;

echo "Please enter your password";
read password;

Chcę, aby kiedy użytkownik wpisze hasło na terminalu, nie powinno ono być wyświetlane (lub powinno być wyświetlane coś takiego jak *******)). Jak to osiągnąć?

jww
źródło
2
Uwaga ogólna, aby uniknąć nieporozumień: ta nazwa użytkownika / hasło nie ma nic wspólnego z nazwą użytkownika / hasłem systemu Linux - szukam tylko sposobu na ukrycie danych wpisywanych przez użytkownika podczas „odczytu hasła”.
Dodałem aktualizację, jeśli chcesz uzyskać fantazyjność, wyświetlając *podczas wpisywania hasła
SiegeX
Dzięki wielkie. Jedno pytanie, jeśli ktoś wie - czy to automatycznie uniemożliwi wejście do .bash_history?
2
Nie sądzę, aby tak było, bash_history przechwytuje tylko twoje polecenie, co dzieje się po uruchomieniu polecenia, którego nie przechwytuje.
Andreas Wong

Odpowiedzi:

272

Po prostu podaj-do twojego wywołania odczytu w następujący sposób:

$ read -s PASSWORD
$ echo $PASSWORD
Andreas Wong
źródło
9
Tak mi się bardziej podoba.
Zagłosowałbym na
4
Aby zapewnić kontekst: nic nie-s wyświetla podczas wpisywania danych wejściowych. ( jest rozszerzeniem innym niż POSIX, więc nie wszystkie powłoki je obsługują, na przykład powłoka dostarczana z BusyBox; użyj podejścia w takich powłokach)-sashssty -echo
mklement0
3
@electron W manrzeczywistości nic na stronie nie sugeruje, że -sustawia kolor tekstu na taki sam, jak kolor tła. Po prostu „odbija się cichym echem”. ss64.com/bash/read.html Silent mode. If input is coming from a terminal, characters are not echoed.
Andreas Wong,
2
@electron przetestowałem na moim pudełku, nie dość, że nie przesuwa kursora, kiedy próbuję podświetlić linię, w której wpisałem hasło, nie mogę go później wkleić. Oba powinny się zdarzyć, jeśli to, co mówi książka, jest prawdą. Domyślam się, że może inny smak powłoki (używałem basha 4.1.2 (1)).
Andreas Wong
1
@DominykasMostauskis tak, ale etykieta jest dla basha, więc rozwiązanie. Prawdopodobnie jest mnóstwo wariantów powłoki, w których to nie działa :)
Andreas Wong,
25

Aktualizacja

Jeśli chcesz uzyskać fantazyjność, wyświetlając *znak dla każdego wpisanego znaku, możesz zrobić coś takiego (używając read -srozwiązania Andreasa ):

unset password;
while IFS= read -r -s -n1 pass; do
  if [[ -z $pass ]]; then
     echo
     break
  else
     echo -n '*'
     password+=$pass
  fi
done

Bez bycia wyszukanym

echo "Please enter your username";
read username;

echo "Please enter your password";
stty -echo
read password;
stty echo
SiegeX
źródło
Powinno być IFS=$'\n'tak, aby można było faktycznie dokończyć wpisywanie hasła. Poza tym miło; bardzo mądry.
sorpigal
2
Nie ma potrzeby ustawiania, IFS=$'\n'ponieważ domyślnym separatorem odczytu jest -d $'\n'. Instrukcja if, która przerywa ciąg znaków o wartości nul, co dzieje się w nowej linii, umożliwia dokończenie hasła.
SiegeX
Podczas testowania na FreeBSD z bash 3.x stwierdzam, że tak nie jest. Testowanie w systemie Linux w wersji 3.1.17 daje wyniki. W tej chwili nie mam pod ręką skrzynki BSD, ale spróbuję to potwierdzić jutro, tylko ze względu na własną ciekawość.
sorpigal
@Sorpigal: interesujące, daj mi znać, czego się dowiedziałeś.
Chodzi
2
Mam to. Moja FreeBSD test oparty na kodzie skopiować z oryginalnego błędnej edycji postu lesmana, który zawiera jedną istotną różnicę: ty był przechodząc . Kiedy spróbowałem później na Linuksie, użyłem repostowanej wersji. Jeśli spróbuję pominąć, to oczywiście działa to identycznie we FreeBSD! Właściwie czuję ulgę, że nie działa tu jakaś tajemnicza magia platformowa. read-d ''-d ''
sorpigal
17

aby znaleźć rozwiązanie, które działa bez basha lub niektórych funkcji read, możesz użyć sttydo wyłączenia echa

stty_orig=$(stty -g)
stty -echo
read password
stty $stty_orig
lesmana
źródło
9

Oto wariacja na temat doskonałego *rozwiązania drukowania @ SiegeX dla bash z dodaną obsługą backspace ; umożliwia to użytkownikowi poprawienie wpisu za pomocą backspaceklucza ( deleteklucz na komputerze Mac) , co jest zwykle obsługiwane przez monity o hasło:

#!/usr/bin/env bash

password=''
while IFS= read -r -s -n1 char; do
  [[ -z $char ]] && { printf '\n'; break; } # ENTER pressed; output \n and break.
  if [[ $char == $'\x7f' ]]; then # backspace was pressed
      # Remove last char from output variable.
      [[ -n $password ]] && password=${password%?}
      # Erase '*' to the left.
      printf '\b \b' 
  else
    # Add typed char to output variable.
    password+=$char
    # Print '*' in its stead.
    printf '*'
  fi
done

Uwaga:

  • Aby wyjaśnić, dlaczego naciśnięcie klawisza Backspace rejestruje kod znaku 0x7f: „W nowoczesnych systemach klawisz Backspace jest często odwzorowywany na znak usuwania (0x7f w ASCII lub Unicode)” https://en.wikipedia.org/wiki/Backspace
  • \b \bjest potrzebny, aby nadać pozór usuwania znaku po lewej stronie; samo użycie \bprzesuwa kursor w lewo, ale pozostawia znak nienaruszony ( nieniszczący backspace). Po wydrukowaniu spacji i ponownym cofnięciu się, znak wydaje się być usunięty (dzięki, znak ucieczki „backspace” „\ b” w C, nieoczekiwane zachowanie? ).

W powłoce obsługującej tylko POSIX (np. W shDebianie i Ubuntu, gdzie shjest dash), użyj tego stty -echopodejścia (które jest nieoptymalne, ponieważ nic nie drukuje ), ponieważ readwbudowana funkcja nie obsługuje opcji -si -n.

mklement0
źródło
Dzięki, @Dylan. Właśnie zdałem sobie sprawę, że kod do usuwania ostatniego znaku wpisanego do tej pory hasła można uprościć - patrz aktualizacja.
mklement0
3

Trochę różni się od odpowiedzi @ lesmana (ale głównie podoba)

stty -echo
read password
stty echo

po prostu: ukryj echo zrób swoje rzeczy pokaż echo

yerlilbilgin
źródło
Czy możesz wyjaśnić, dlaczego jest to lepsze niż odpowiedź @ lesmana ? Widzę, że jest to prostsze z mniejszym narzutem na inne sttywywołanie i jedną mniejszą zmienną na stosie - tylko zabierając echo i tylko przywracając echo. Czy istnieje sposób, w jaki mogłoby to zakłócić inne sttyustawienia? Lesmana zachowuje wszystkie ustawienia.
Jeff Puckett
2

Oto odmiana odpowiedzi @ SiegeX, która działa z tradycyjną powłoką Bourne'a (która nie obsługuje +=przypisań).

password=''
while IFS= read -r -s -n1 pass; do
  if [ -z "$pass" ]; then
     echo
     break
  else
     printf '*'
     password="$password$pass"
  fi
done
tripleee
źródło
Tradycyjna powłoka Bourne'a (lub powłoka zgodna z POSIX) również by nie rozpoznała [[ ... ]], ani też jej readwbudowana powłoka nie miałaby opcji -si -n. Twój kod nie będzie więc działał dashna przykład w. (Będzie działać na platformach, na których shfaktycznie jest bash, na przykład na OSX, gdzie bashpo wywołaniu shtylko modyfikuje kilka domyślnych opcji, jednocześnie nadal obsługując większość bashizmów.)
mklement0
2
Dzięki za informację zwrotną, przeszedłem na singiel, [ale oczywiście niewiele mogę zrobić, jeśli readnie masz tych opcji.
tripleee
2

Zawsze lubię używać znaków ucieczki Ansi:

echo -e "Enter your password: \x1B[8m"
echo -e "\x1B[0m"

8m sprawia, że ​​tekst jest niewidoczny i 0m resetuje tekst do „normalnego”. -E umożliwia Ansi ucieczkę.

Jedynym zastrzeżeniem jest to, że nadal możesz skopiować i wkleić tekst, który tam jest, więc prawdopodobnie nie powinieneś tego używać, jeśli naprawdę chcesz bezpieczeństwa.

Po prostu pozwala ludziom nie patrzeć na Twoje hasła podczas ich wpisywania. Po prostu nie zostawiaj komputera włączonego później. :)


UWAGA:

Powyższe jest niezależne od platformy, o ile obsługuje sekwencje ucieczki Ansi.

Jednak w przypadku innego rozwiązania uniksowego możesz po prostu powiedzieć, readaby nie powtarzać znaków ...

printf "password: "
let pass $(read -s)
printf "\nhey everyone, the password the user just entered is $pass\n"
dylnmc
źródło
1

Uzyskaj nazwę użytkownika i hasło

Spraw, aby było bardziej czytelne, ale umieść je w lepszej pozycji nad ekranem

#!/bin/bash
clear
echo 
echo 
echo
counter=0
unset username
prompt="  Enter Username:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
    if [[ $char == $'\0' ]]; then
        break
    elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then
        prompt=$'\b \b'
        username="${username%?}"
        counter=$((counter-1))
    elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then
        prompt=''
        continue
    else
        counter=$((counter+1))
        prompt="$char"
        username+="$char"
    fi
done
echo
unset password
prompt="  Enter Password:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
    if [[ $char == $'\0' ]]; then
        break
    elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then
        prompt=$'\b \b'
        password="${password%?}"
        counter=$((counter-1))
    elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then
        echo
        prompt="  Enter Password:"
        continue
    else
        counter=$((counter+1))
        prompt='*'
        password+="$char"
    fi
done
MikeW
źródło