【问题标题】:Hide password input on terminal隐藏终端上的密码输入
【发布时间】:2021-01-27 07:11:26
【问题描述】:

我想在使用* 编写密码时隐藏我的密码。 我对这段代码使用 Linux GCC。 我知道一种解决方案是像这样使用getch() 函数

#include <conio.h>   
int main()
{
    char c,password[10];
    int i;
    while( (c=getch())!= '\n');{
        password[i] = c;
        printf("*");
        i++;
    }
    return 1;
}

但问题是GCC 不包含conio.h 文件,所以getch() 对我来说没用。 有人有解决办法吗?

【问题讨论】:

  • 你不能用getc(stdin)代替getch吗?
  • getc(stdin) 仍然会导致输入在终端上回显。 OP 不希望在终端上回显输入。
  • 正确的 susam Pal,getc() 或 getc(stdin) 或 fgetc(stdin) 不会停止回显。
  • conio.h 在 Linux 下不可用,getch() 也不可用。

标签: c linux


【解决方案1】:

这是我的想法,改编自the C++ official site

#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
string getpass(const char *prompt, bool showchar = false, char echochar = '*')
{
    struct termios oi, ni;
    tcgetattr(STDIN_FILENO, &oi);
    ni = oi;
    ni.c_lflag &= ~(ICANON | ECHO);
    const char DELETE = 127;
    const char RETURN = 10;
    string password;
    unsigned char ch = 0;
    cout << prompt;
    tcsetattr(STDIN_FILENO, TCSANOW, &ni);
    while (getchar() != RETURN) {
        if (ch == DELETE) {
            if(password.length != 0){
                if (showchar) cout << "\b \b";
                password.resize(password.length() - 1);
            }
        }else {
            password += getchar();
            if (showchar) cout << echochar;
        }
    }
    tcsetattr(STDIN_FILENO,TCSANOW,&oi)
    cout << endl;
    return password;
}

它会一次读取一个字符并将其添加到字符串中,并支持显示另一个字符。

【讨论】:

  • 请注意,这也适用于 UNIX。 :-)
【解决方案2】:
printf("\nENTER PASSWORD: ");
while (1)
{
    ch=getch();
    if(ch==13)    //ON ENTER PRESS
        break;

    else if(ch==8)    //ON BACKSPACE PRESS REMOVES CHARACTER
    {
        if(i>0)
        {
            i--;
            password[i]='\0';
            printf("\b \b");
        }
    }
    else if (ch==32 || ch==9)    //ON PRESSING TAB OR SPACE KEY
        continue;
    else
    {
        password[i]=ch;
        i++;
        printf("*");
    }         
}
password[i]='\0';

【讨论】:

  • 感谢您提供此代码 sn-p,它可能会提供一些有限的即时帮助。 proper explanation 将通过展示为什么这是解决问题的好方法,并使其对有其他类似问题的未来读者更有用,从而大大提高其长期价值。请edit您的回答添加一些解释,包括您所做的假设。
【解决方案3】:

在没有getch 依赖和避免过时的getpass 的情况下,推荐的方法是通过termios 使用来禁用终端ECHO。经过几次搜索以找到一个固定的灵活密码例程,令我惊讶的是,很少有人可以与 C 一起独立使用。不是简单地使用 termios c_lflag 选项重新编码getch,而是稍微更通用的方法只需要添加一些内容。除了替换 getch 之外,任何例程都应强制指定最大长度以防止溢出,如果用户尝试输入超出最大值则截断,并在以某种方式发生截断时发出警告。

下面,添加的内容将允许从任何FILE * 输入流中读取,将长度限制为指定长度,在接受输入时提供最小的编辑(退格)能力,允许指定或完全禁用字符掩码,最后返回输入密码的长度。当输入的密码被截断到最大或指定长度时添加警告。

希望它对寻找类似解决方案的其他人有用:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>

#define MAXPW 32

/* read a string from fp into pw masking keypress with mask char.
getpasswd will read upto sz - 1 chars into pw, null-terminating
the resulting string. On success, the number of characters in
pw are returned, -1 otherwise.
*/
ssize_t getpasswd (char **pw, size_t sz, int mask, FILE *fp)
{
    if (!pw || !sz || !fp) return -1;       /* validate input   */
#ifdef MAXPW
    if (sz > MAXPW) sz = MAXPW;
#endif

    if (*pw == NULL) {              /* reallocate if no address */
        void *tmp = realloc (*pw, sz * sizeof **pw);
        if (!tmp)
            return -1;
        memset (tmp, 0, sz);    /* initialize memory to 0   */
        *pw =  (char*) tmp;
    }

    size_t idx = 0;         /* index, number of chars in read   */
    int c = 0;

    struct termios old_kbd_mode;    /* orig keyboard settings   */
    struct termios new_kbd_mode;

    if (tcgetattr (0, &old_kbd_mode)) { /* save orig settings   */
        fprintf (stderr, "%s() error: tcgetattr failed.\n", __func__);
        return -1;
    }   /* copy old to new */
    memcpy (&new_kbd_mode, &old_kbd_mode, sizeof(struct termios));

    new_kbd_mode.c_lflag &= ~(ICANON | ECHO);  /* new kbd flags */
    new_kbd_mode.c_cc[VTIME] = 0;
    new_kbd_mode.c_cc[VMIN] = 1;
    if (tcsetattr (0, TCSANOW, &new_kbd_mode)) {
        fprintf (stderr, "%s() error: tcsetattr failed.\n", __func__);
        return -1;
    }

    /* read chars from fp, mask if valid char specified */
    while (((c = fgetc (fp)) != '\n' && c != EOF && idx < sz - 1) ||
            (idx == sz - 1 && c == 127))
    {
        if (c != 127) {
            if (31 < mask && mask < 127)    /* valid ascii char */
                fputc (mask, stdout);
            (*pw)[idx++] = c;
        }
        else if (idx > 0) {         /* handle backspace (del)   */
            if (31 < mask && mask < 127) {
                fputc (0x8, stdout);
                fputc (' ', stdout);
                fputc (0x8, stdout);
            }
            (*pw)[--idx] = 0;
        }
    }
    (*pw)[idx] = 0; /* null-terminate   */

    /* reset original keyboard  */
    if (tcsetattr (0, TCSANOW, &old_kbd_mode)) {
        fprintf (stderr, "%s() error: tcsetattr failed.\n", __func__);
        return -1;
    }

    if (idx == sz - 1 && c != '\n') /* warn if pw truncated */
        fprintf (stderr, " (%s() warning: truncated at %zu chars.)\n",
                __func__, sz - 1);

    return idx; /* number of chars in passwd    */
}

显示使用的简单程序如下。如果使用静态字符数组来保存密码,只需确保将指针传递给函数。

int main (void ) {

    char pw[MAXPW] = {0};
    char *p = pw;
    FILE *fp = stdin;
    ssize_t nchr = 0;

    printf ( "\n Enter password: ");
    nchr = getpasswd (&p, MAXPW, '*', fp);
    printf ("\n you entered   : %s  (%zu chars)\n", p, nchr);

    printf ( "\n Enter password: ");
    nchr = getpasswd (&p, MAXPW, 0, fp);
    printf ("\n you entered   : %s  (%zu chars)\n\n", p, nchr);

    return 0;
}

示例输出

$ ./bin/getpasswd2

 Enter password: ******
 you entered   : 123456  (6 chars)

 Enter password:
 you entered   : abcdef  (6 chars)

【讨论】:

  • 真正从终端读取,不应该使用stdin,而是在main中打开/dev/tty。而在getpasswd中,不要使用文件描述符0,而是使用fileno(fp)来保持一致。
  • 我想知道 new_kbd_mode.c_cc[VTIME] = 0;new_kbd_mode.c_cc[VMIN] = 1; 究竟做了什么。我在手册页termios(3) 中没有明白这一点。你能解释一下吗?
  • 抱歉回复晚了,有一个上诉摘要到期。我从手册页对MIN &gt; 0, TIME == 0 (blocking read) 的理解是read 阻塞直到MIN 字节可用,而不是轮询并返回0(如果没有可用的话)。手册页的noncanonical 部分解释了 4 种组合。我会调查你的/dev/tty 建议——有道理,但如果我记得,你还必须指定哪个 tty。
  • /dev/tty (自动)是当前进程的控制 tty,因此通常是“正确的”。
【解决方案4】:

getpass

此功能已过时。不要使用它。如果你想读取输入 未启用终端回显,请参阅 ECHO 标志的描述 在 termios(3) 中

# include <termios.h>
# include <unistd.h>   /* needed for STDIN_FILENO which is an int file descriptor */

struct termios tp, save;

tcgetattr( STDIN_FILENO, &tp);              /* get existing terminal properties */
save = tp;                                  /* save existing terminal properties */

tp.c_lflag &= ~ECHO;                        /* only cause terminal echo off */

tcsetattr( STDIN_FILENO, TCSAFLUSH, &tp );  /* set terminal settings */

/*
   now input by user in terminal will not be displayed
   and cursor will not move
*/

tcsetattr( STDIN_FILENO, TCSANOW, &save);   /* restore original terminal settings */

如果您注意到,大多数当前的 linux 发行版不会屏蔽带有星号的密码。这样做会泄露密码的长度,这无济于事。在输入密码时简单地使光标不移动更容易更好。如果出于某种原因您需要为输入的每个字符打印*,那么您必须在Enter 之前抓住每个按键命中,这一直是个问题。

【讨论】:

    【解决方案5】:
    #include <termios.h>
    #include <stdio.h>
    
    static struct termios old, new;
    
    void initTermios(int echo) {
        tcgetattr(0, &old);
        new = old;
        new.c_lflag &= ~ICANON;
        new.c_lflag &= echo ? ECHO : ~ECHO;
        tcsetattr(0, TCSANOW, &new);
    }
    
    void resetTermios(void) {
        tcsetattr(0, TCSANOW, &old);
    }
    
    char getch_(int echo) {
        char ch;
        initTermios(echo);
        ch = getchar();
        resetTermios();
        return ch;
    }
    
    char getch(void) {
        return getch_(0);
    }
    
    int main(void) {
        char c;
        printf("(getch example) please type a letter...");
        c = getch();
        printf("\nYou typed: %c\n", c);
        return 0;
    }
    

    只需复制这些 sn-p 并使用它。希望对您有所帮助

    【讨论】:

    • 不要用勺子喂 OP。解释代码是如何工作的。
    【解决方案6】:

    您的方法是正确的,但是您需要在输入密码时关闭终端回显:

    #include <sgtty.h>
    
    void echo_off()
    {
        struct sgttyb state;
        (void)ioctl(0, (int)TIOCGETP, (char *)&state);
        state.sg_flags &= ~ECHO;
        (void)ioctl(0, (int)TIOCSETP, (char *)&state);
    }
    
    void echo_on()
    {
        struct sgttyb state;
        (void)ioctl(0, (int)TIOCGETP, (char *)&state);
        state.sg_flags |= ECHO;
        (void)ioctl(0, (int)TIOCSETP, (char *)&state);
    }
    

    为什么不直接使用getch(),而不是getc()

    【讨论】:

    • 木马,我在我的代码中对他的代码进行了marge,但它给出了这样的错误,请帮助解决它们。 pass.cpp:在函数'void echo_off()'中:pass.cpp:7:17:错误:聚合'sgttyb state'的类型不完整,无法定义pass.cpp:8:23:错误:'TIOCGETP'不是在此范围内声明 pass.cpp:9:22:错误:未在此范围内声明“ECHO” pass.cpp:10:23:错误:未在此范围内声明“TIOCSETP” pass.cpp:在函数“无效”中echo_on()': pass.cpp:15:17: 错误: 聚合 'sgttyb state' 的类型不完整,无法定义 pass.cpp:16:23: 错误: 'TIOCGETP' 未声明
    • 请注意,如果用户在输入密码时按下中断进程(例如按 Ctrl+C),终端回显将处于关闭状态,用户可能无法看到其他 Linux 命令他在 shell 中键入。通常,用户应该能够通过执行(他必须盲目输入)从这种情况中恢复:stty sane
    • Susam:sudo 等程序捕捉信号,以确保恢复 TTY 状态。
    • @Heet Kansagra:看起来你可能还需要#include &lt;asm/ioctls.h&gt;
    • @trojanfoe 添加 #include 后出现错误
    【解决方案7】:

    在 C 语言中,您可以使用 getpasswd() 函数,它的作用与 shell 中的 stty 非常相似,例如:

    #include <stdio.h>
    #include <string.h>
    #include <pwd.h>
    #include <unistd.h>
    
    int main()
    {
        char acct[80], password[80];
    
        printf(“Account: “);
        fgets(acct, 80, stdin);
    
        acct[strlen(acct)-1] = 0; /* remove carriage return */
    
        strncpy(password, getpass(“Password: “), 80);
        printf(“You entered acct %s and pass %s\n”, acct, password);
    
        return 0;
    }
    

    这是使用 stty 的等效 shell 脚本(它会更改您的 tty 的设置):

    save_state=$(stty -g)
    /bin/echo -n “Account: “
    read acct
    /bin/echo -n “Password: “
    stty -echo
    read password # this won’t echo
    stty “$save_state”
    echo “”
    echo account = $acct and password = $password
    

    来源:How can I read a password without echoing it in C?

    【讨论】:

      【解决方案8】:

      在 Linux 世界中,通常不使用星号进行屏蔽,通常只是关闭回显并且终端显示空白。如果您使用su 或登录虚拟终端等。

      有一个库函数来处理获取密码,它不会用星号掩盖密码,但会禁用密码回显到终端。我把它从我有的一本 linux 书里拿出来了。我相信它是 posix 标准的一部分

      #include <unistd.h>
      char *getpass(const char *prompt);
      
      /*Returns pointer to statically allocated input password string
      on success, or NULL on error*/
      

      getpass() 函数首先禁用回显和所有处理 终端特殊字符(例如中断字符,通常 控制-C)。

      然后打印提示指向的字符串,并读取一行 输入,返回以 null 结尾的输入字符串,并带有尾随 换行符被剥离,作为它的函数结果。

      谷歌搜索 getpass() 引用了 GNU 实现(应该在大多数 linux 发行版中)和一些示例代码,如果需要的话,可以实现你自己的

      http://www.gnu.org/s/hello/manual/libc/getpass.html

      他们自己滚动的例子:

      #include <termios.h>
      #include <stdio.h>
      
      ssize_t
      my_getpass (char **lineptr, size_t *n, FILE *stream)
      {
          struct termios old, new;
          int nread;
      
          /* Turn echoing off and fail if we can't. */
          if (tcgetattr (fileno (stream), &old) != 0)
              return -1;
          new = old;
          new.c_lflag &= ~ECHO;
          if (tcsetattr (fileno (stream), TCSAFLUSH, &new) != 0)
              return -1;
      
          /* Read the password. */
          nread = getline (lineptr, n, stream);
      
          /* Restore terminal. */
          (void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
      
          return nread;
      }
      

      如果需要,您可以以此为基础修改它以显示星号。

      【讨论】:

      • 根据 man getpass function is obsolete:“这个函数已经过时了。不要使用它。”
      • 在 Mac OS X 10.7.5 上,getpass() 返回一个无效的指针,如果你尝试使用它会导致程序崩溃。
      • 另外,对于某些接收 tcgetattr 在从命令行运行时在标准输入 (fileno=0) 上返回 ENOTTY!
      • POSIX 1997 支持 getpass(),但它被标记为“旧版”。下一个版本POSIX 2004 不需要getpass()
      【解决方案9】:

      注意 ICANON termios lflag 关闭处理回车/换行,负 ECHO termios 设置关闭 STDIN 的回显。

      当使用它(无论是否打开回显)读取密码并为输入的字符打印'*' 时,在遇到换行符/回车符之前,这不仅仅是读取字符的问题,您还必须处理'字符串构建例程'中的退格(否则退格最终会出现在实际字符串中,并且不会导致字符从其中删除,例如各种基于字符串的输入函数的情况)。

      在 DOS 中的 C 中使用 getch 也会发生同样的情况。这也很乐意为退格返回0x08(或 127 或您的特定操作系统用作退格的任何内容)

      跟踪“不删除字符串开头之前”,用 0 替换“字符串的新结尾”并将当前位置计数器向后移动 1(除非您在位置 0)最多具有任何这些功能的程序员(甚至是 dos C 上的 getch)。

      getpass() 没有按照用户最初的要求做 btw,他想要 * 的(仍然向站在他身后看着他的屏幕的人以及在如果他在使用后没有关闭终端)。但在“非封闭环境”中没有 * 可能是一个更好的主意。

      【讨论】:

        【解决方案10】:

        如果不需要移植到 Windows 上,您可以使用 ncurses.h,但这里有一些更“便携”的版本:

        如果不需要便携,请使用 ncurses 解决方案

        portablegetch.h

        /*portablegetch.h*/
        #ifndef PGETCH
        #define PGETCH
        #ifdef __unix__
        #include <termios.h>
        #include <unistd.h>
        
        static struct termios n_term;
        static struct termios o_term;
        
        static int
        cbreak(int fd) 
        {
           if((tcgetattr(fd, &o_term)) == -1)
              return -1;
           n_term = o_term;
           n_term.c_lflag = n_term.c_lflag & ~(ECHO|ICANON);
           n_term.c_cc[VMIN] = 1;
           n_term.c_cc[VTIME]= 0;
           if((tcsetattr(fd, TCSAFLUSH, &n_term)) == -1)
              return -1;
           return 1;
        }
        
        int 
        getch() 
        {
           int cinput;
        
           if(cbreak(STDIN_FILENO) == -1) {
              fprintf(stderr, "cbreak failure, exiting \n");
              exit(EXIT_FAILURE);
           }
           cinput = getchar();
           tcsetattr(STDIN_FILENO, TCSANOW, &o_term);
        
           return cinput;
        }
        
        #elif _MSC_VER  || __WIN32__ || __MS_DOS__
          #include <conio.h>
        #endif
        #endif
        

        还有 c 文件

        whatever.c

        #include <stdio.h>
        #include <stdlib.h>
        #include "portablegetch.h"
        
        int 
        main(int argc, char **argv) 
        {
          int input;
        
          printf("Please Enter your Password:\t");
        
          while(( input=getch() ) != '\n')
                printf("*");
          printf("\n");
        
          return EXIT_SUCCESS;
        }
        

        这应该适合你的问题。

        希望对您有所帮助。

        【讨论】:

        • 再回答一次,同样的问题。 ICANON 必须在您打印提示之前关闭,并且您必须在关闭它之后和打印提示之前清除所有缓冲的输入。否则用户可能会过早输入密码并且密码会被看到。
        • 嘿,谢谢,效果很好。将 portablegetch.h 的第三行更改为 #if defined(__unix__) || defined(__APPLE__) 将使其也适用于 macOS。
        【解决方案11】:

        感谢所有帮助和支持解决我的问题的人。 我找到了一种在最适合我的 linux 中隐藏密码的最佳方法。 使用 getpass() 函数。它只需要包含“unistd.h”文件。

        getpass 函数语法:

        char * getpass (const char *prompt)

        参数: 提示:输入密码时要打印的字符串指针

        返回值: 密码字符串指针

        例子:

        #include <stdio.h>
        #include <unistd.h>   
        int main()
        {
            char *password; // password string pointer
            password = getpass("Enter Password: "); // get a password
            printf("%s\n",password); // this is just for conformation
                                     // that password stored successfully
            return 1;
        }
        

        输出:

        输入密码:

        【讨论】:

        • 在 Mac OS X 10.7.5 上,getpass() 返回一个无效的指针,如果你尝试使用它会导致程序崩溃。
        【解决方案12】:

        通过扫描字符,您可以将其放入缓冲区。如果按退格键,还需要编写代码,并适当更正插入的密码。

        这是我曾经用诅咒编写的代码。用gcc file.c -o pass_prog -lcurses编译

        #include <stdio.h>
        #include <stdlib.h>
        #include <curses.h>
        
        #define ENOUGH_SIZE 256
        
        #define ECHO_ON 1
        #define ECHO_OFF 0
        
        #define BACK_SPACE 127
        
        char *my_getpass (int echo_state);
        
        int main (void)
        {
          char *pass;
        
          initscr ();
        
          printw ("Enter Password: ");
          pass = my_getpass (ECHO_ON);
        
          printw ("\nEntered Password: %s", pass);
          refresh ();
          getch ();
          endwin ();
          return 0;
        }
        
        
        char *my_getpass (int echo_state)
        {
          char *pass, c;
          int i=0;
        
          pass = malloc (sizeof (char) * ENOUGH_SIZE);
          if (pass == NULL)
          {
            perror ("Exit");
            exit (1);
          }
        
          cbreak ();
          noecho ();
        
          while ((c=getch()) != '\n')
          {
            if (c == BACK_SPACE)
            {
              /* Do not let the buffer underflow */
              if (i > 0)
              { 
                i--;
                if (echo_state == ECHO_ON)
                       printw ("\b \b");
              }
            }
            else if (c == '\t')
              ; /* Ignore tabs */
            else
            {
              pass[i] = c;
              i = (i >= ENOUGH_SIZE) ? ENOUGH_SIZE - 1 : i+1;
              if (echo_state == ECHO_ON)
                printw ("*");
            }
          }
          echo ();
          nocbreak ();
          /* Terminate the password string with NUL */
          pass[i] = '\0';
          endwin ();
          return pass;
        }
        

        【讨论】:

          【解决方案13】:

          您可以通过这种方式在 Linux 上创建自己的 getch() 函数。

          int getch() {
              struct termios oldtc, newtc;
              int ch;
              tcgetattr(STDIN_FILENO, &oldtc);
              newtc = oldtc;
              newtc.c_lflag &= ~(ICANON | ECHO);
              tcsetattr(STDIN_FILENO, TCSANOW, &newtc);
              ch=getchar();
              tcsetattr(STDIN_FILENO, TCSANOW, &oldtc);
              return ch;
          }
          

          演示代码:

          int main(int argc, char **argv) {
              int ch;
              printf("Press x to exit.\n\n");
              for (;;) {
                  ch = getch();
                  printf("ch = %c (%d)\n", ch, ch);
                  if(ch == 'x')
                        break;
              }
              return 0;
          }
          

          【讨论】:

          • 一个相同的答案,一个相同的问题!您必须关闭 ICANON,丢弃 stdin 上缓冲的任何内容,询问用户密码,读取密码,然后才打开 ICANON。
          【解决方案14】:

          不幸的是,在 C 标准库中没有这样的功能。也许在第三方库中。

          一个选项是使用 ANSI 转义序列在控制台中将背景颜色设置为前景色以隐藏密码。试试this link

          【讨论】:

          • 那很不安全;密码字符串仍然可以在 X11 中使用鼠标或安装了鼠标守护程序的控制台选择。
          • 实际上 getc()、getch() 或 gets() 这所有函数都是不安全的并给出警告警告:`gets' 函数是危险的,不应使用。
          • getc 没有什么不安全的地方。
          【解决方案15】:

          getch 的功能(这是一个非标准的 Windows 函数)可以用以下代码模拟:

          #include <termios.h>
          #include <unistd.h>
          int getch() {
              struct termios oldt, newt;
              int ch;
              tcgetattr(STDIN_FILENO, &oldt);
              newt = oldt;
              newt.c_lflag &= ~(ICANON | ECHO);
              tcsetattr(STDIN_FILENO, TCSANOW, &newt);
              ch = getchar();
              tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
              return ch;
          }
          

          请注意,您的方法并不完美 - 最好使用 ncurses 之类的东西或其他终端库来处理这些事情。

          【讨论】:

          • 嘿,Delan Azabani,您的代码有效,但您对 ncurses 库的了解。请解释一下。
          • ncurses 是一个终端库,可以优雅地处理许多常见任务。它非常流行,几乎所有基于 Linux 的操作系统都包含它。
          • tcsetattr() 的使用是正确的,但请记住,终端会回显字符,并且它在键入时会回显,而不是在检索到时。所以你必须关闭 ICANON,从标准输入读取所有待处理的数据(这样用户就不能在密码仍然可见的地方提前输入密码),向用户询问密码,读取它,然后再重新打开 ICANON。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-08-19
          • 2011-12-06
          • 2012-03-01
          相关资源
          最近更新 更多