隐藏终端上的密码输入

时间:2011-07-28 09:16:16

标签: c linux

我希望在使用*编写密码时屏蔽我的密码。 我使用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()对我来说毫无用处。 有没有人有解决方案?

15 个答案:

答案 0 :(得分:46)

在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;
}

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

答案 1 :(得分:9)

可以使用以下代码模拟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 或其他终端库来处理这些事情。

答案 2 :(得分:9)

如果没有getch依赖并避免过时的getpass,建议的方法是通过termios使用来禁用终端ECHO。在进行了几次搜索以找到一个灵活的密码例程之后,我很惊讶很少有人使用C语言进行单独使用。而不是简单地使用termios getch选项重新编码c_lflag,稍微更通用的方法只需要一些补充。除了替换getch之外,任何例程都应该强制执行指定的最大长度以防止溢出,如果用户尝试输入超出最大值,则截断,并警告是否以某种方式发生截断。

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

希望通过这个问题寻找类似的解决方案对其他人有用:

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

#include <sys/time.h>
#include <termios.h>
#include <errno.h>   /* for errno */
#include <unistd.h>  /* for EINTR */

#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 = 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)

答案 3 :(得分:6)

您可以通过这种方式在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;
}

答案 4 :(得分:3)

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

#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()呢?

答案 5 :(得分:2)

如果没有必要在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;
}

这应该适合你的问题。

希望有所帮助。

答案 6 :(得分:2)

感谢各位的帮助和帮助。支持解决我的问题。 我找到了一个最好的方法来隐藏我最适合我的密码。 要使用getpass()函数。它只需要包含“unistd.h”文件。

getpass函数的syntex:

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;
}

输出:

输入密码:

HEET

答案 7 :(得分:2)

#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;
}

只需复制这些代码段并使用它即可。希望它有所帮助

答案 8 :(得分:0)

不幸的是,在C标准库中没有开箱即用的功能。也许在第三方图书馆。

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

答案 9 :(得分:0)

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

这是我用curses编写的代码。使用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;
}

答案 10 :(得分:0)

在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(更改tty的设置)的等效shell脚本:

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?

答案 11 :(得分:0)

  

获取密码

     

此功能已过时。不要使用它。如果您想阅读输入          在未启用终端回显的情况下,请参见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发行版都不会屏蔽带星号的密码。这样做会泄露密码的长度,这无济于事。键入密码时,简单地使光标不移动是更容易和更好的方法。如果出于任何原因,您要求为每个键入的字符打印一个*,那么您必须在{{ 1}}被击中,这始终是有问题的。

答案 12 :(得分:0)

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';

答案 13 :(得分:0)

这是我的想法,改编自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;
}

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

答案 14 :(得分:-1)

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

当使用它(有或没有回声)来读取密码并打印'*'输入的字符时,不仅仅是在遇到换行/回车之前读取字符,你还有在“字符串构建例程”中处理退格(否则退格会以实际字符串结尾,并且不会导致从中删除字符,例如各种基于字符串的输入函数的情况)。

在使用getch tho的DOS中,C语也会发生同样的情况。也可以愉快地返回0x08退格(或127或你的特定操作系统用作退格的任何东西)

跟踪'不删除-before-字符串的开头',将'字符串的新结尾'替换为0并将当前位置计数器移回1(除非您处于位置0)取决于具有任何这些功能的程序员(甚至是dos C上的getch)。

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