本篇目录
- 引子
- 在Windows 上实现
- 在Linux上实现
- 回显星号
- 代码解读
- 运行
- 完全隐藏
- 运行
引子
在Windows系统上,当我们使用命令行和MySQL进行交互时,第一步就是要输入密码:
-p
后面的参数紧跟着的就是相应用户的密码。然而这种方式并不安全,因为密码是以明文的方式直接显示在命令行的,如果有人通过特定方式,刻意查看命令行的日志记录,就会查看到这条命令,后面的密码也会随之暴露。所以以这种方式登录到数据库,会弹出一条警告:mysql: [Warning] Using a password on the command line interface can be insecure.
较为安全的方式为:
这里我们会看到,密码在输入的时候是用星号代替的,将密码隐藏起来。这是为了提高安全性。
在Windows 上实现
下面给出C语言代码,来实现隐藏密码的功能:
#include <stdio.h>
#include <conio.h>
#define MAX_PASSWORD_LENGTH 20 //密码最大长度
int main()
{
char password[MAX_PASSWORD_LENGTH];
int i = 0;
char ch;
printf("Enter your password: ");
while (1)
{
ch = getch(); //接收一个字符
if (ch == 13) // If Enter key is pressed 回车键的ASCII值为13
break;
else if (ch == 8) // If Backspace key is pressed
{
if (i > 0)
{
printf("\b \b");
i--;
}
}
else if (ch != ' ' && i < MAX_PASSWORD_LENGTH - 1)
{
printf("*");
password[i] = ch;
i++;
}
}
password[i] = '\0'; // Null-terminate the password string
printf("\nPassword entered: %s\n", password);
return 0;
}
使用DevCpp编译器:
在命令行下执行:
然而这种以星号代替密码的方式,安全性并不是很高。因为一个星号代替一位密码,通过星号的个数可以判断密码的位数,从而缩小破解密码的范围(指定的密码长度暴力破解)。
上面的代码将printf("*");
这一行删掉,将不会显示星号。
下面再给出一个更加安全的代码片,在用户进行输入的时候,连星号都不会显示:
#include <stdio.h>
#include <windows.h>
#define MAX_PASSWORD_LENGTH 20
void hidePasswordInput()
{
DWORD mode;
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
// 获取输入模式
GetConsoleMode(hstdin, &mode);
SetConsoleMode(hstdin, mode & (~ENABLE_ECHO_INPUT)); // 禁用回显输入模式
}
void showPasswordInput()
{
DWORD mode;
HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
// 获取输入模式
GetConsoleMode(hstdin, &mode);
SetConsoleMode(hstdin, mode | ENABLE_ECHO_INPUT); // 启用回显输入模式
}
int main()
{
char password[MAX_PASSWORD_LENGTH];
int i = 0;
printf("Enter your password: ");
hidePasswordInput(); // 隐藏密码输入
while (1)
{
char ch = getchar();
if (ch == '\n') // 如果输入回车键,则结束循环
break;
else if (ch != ' ' && i < MAX_PASSWORD_LENGTH - 1)
{
password[i] = ch;
i++;
}
}
password[i] = '\0'; // 在密码字符串末尾添加空字符
showPasswordInput(); // 显示密码输入
printf("\nPassword entered: %s\n", password);
return 0;
}
使用DevCpp编译器:
这里会看到完全隐藏密码。下面是命令行下运行:
在Linux上实现
环境:Ubuntu,gcc 或 g++ 编译器,如果没有这两个编译器,可以使用下面的命令进行安装。
sudo apt-get install gcc
sudo apt-get install g++
gcc和g++编译:
gcc -o test test.c
或 g++ -o test test.cpp
带有math.h的源文件: gcc test.c test -lm
或 g++ test.c test -lm
执行:./test
,./
的意思是当前目录,test
是编译好的可执行文件。
回显星号
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#define MAX_PASSWORD_LENGTH 20
// 从终端获取密码(隐藏输入)
void getPassword(char* password) {
struct termios oldAttr, newAttr;
// 禁止终端回显
tcgetattr(fileno(stdin), &oldAttr);
newAttr = oldAttr;
newAttr.c_lflag &= ~(ICANON | ECHO);
tcsetattr(fileno(stdin), TCSAFLUSH, &newAttr);
// 读取密码
int i = 0;
char c;
while ((c = getchar()) != '\n' && i < MAX_PASSWORD_LENGTH - 1) {
password[i++] = c;
putchar('*'); // 回显星号
}
password[i] = '\0';
// 恢复终端设置
tcsetattr(fileno(stdin), TCSANOW, &oldAttr);
}
int main() {
char password[MAX_PASSWORD_LENGTH];
printf("请输入密码:");
getPassword(password);
printf("\n你输入的密码是:%s\n", password);
return 0;
}
代码解读
struct termios oldAttr, newAttr;
这行语句定义了两个struct termios
类型的结构体变量,分别命名为oldAttr
和newAttr
。
struct termios
是在头文件termios.h
中定义的结构体。它用于保存终端设备的属性配置信息,包括输入输出模式、控制字符、特殊控制字符等。
在这行语句中,通过声明变量oldAttr
和newAttr
为struct termios
类型,我们创建了两个变量来保存终端设备的属性。通常情况下,我们将使用oldAttr
变量来保存当前终端设备的属性配置,而newAttr
变量则用于修改和存储新的属性配置。
接下来,在代码的后续部分,我们可以通过对这两个变量的操作,比如修改特定属性标志位,实现对终端设备属性的更改与恢复。
在使用termios库控制终端设备属性时,通常需要保存当前终端设备的属性配置,并对其进行修改后再恢复回原来的状态。这种处理方式可以实现修改终端属性而不破坏原有状态的目的。
因此,我们需要至少两个struct termios
结构体变量:
- 一个用于保存当前终端设备的属性配置,即原有的属性配置。这样,当我们修改终端属性后,可以将其恢复为原先的状态,确保程序执行完毕后终端属性还原为用户所期望的状态。这个结构体变量通常被命名为
oldAttr
,表示原有的属性配置。 - 另一个用于修改和存储新的属性配置。当我们想要改变终端设备的属性时,我们将使用该结构体变量保存需要修改的属性配置,然后通过相应的操作将新的属性配置应用到终端设备上。这个结构体变量通常被命名为
newAttr
,表示新的属性配置。
通过定义两个不同的struct termios
结构体变量,我们能够分别保存原有的属性配置和新的属性配置,便于在操作终端属性时进行对比、修改和恢复。这样能够更灵活地控制终端设备的属性,同时确保安全地对属性进行修改和恢复操作。
这行代码使用了tcgetattr()
函数来获取标准输入(stdin)的终端属性,并将其保存到oldAttr
变量所代表的结构体中。
具体解释如下:
tcgetattr()
是termios库中的一个函数,用于获取终端设备的属性配置。它接受两个参数:文件描述符和一个指向struct termios
结构体的指针。
在这行代码中,fileno(stdin)
返回标准输入流(stdin)的文件描述符,作为tcgetattr()
函数的第一个参数。&oldAttr
表示对oldAttr
结构体的地址进行传递,作为tcgetattr()
函数的第二个参数。
因此,该行代码的作用是获取标准输入(stdin)的终端属性,并将这些属性保存在oldAttr
结构体中,以便后续可能的属性修改或恢复操作。
这行代码用于修改newAttr
结构体中的终端属性配置,具体是修改了c_lflag
成员的值。
解释如下:
newAttr
是一个struct termios
类型的结构体,其中的c_lflag
成员表示终端的本地模式标志。通过使用位运算符和按位取反操作符(~),该行代码将ICANON
和ECHO
标志位从c_lflag
中移除。
ICANON
是一个终端模式标志,表示是否启用规范模式。在规范模式下,输入会以行为单位缓冲,即需要用户按下回车键才会传输到程序中处理。通过将ICANON
标志位置为0,这行代码禁用了规范模式,可以实现字符无需等待回车键响应即可输入的效果。ECHO
是一个终端模式标志,表示是否在屏幕上显示输入的字符。通过将ECHO
标志位置为0,这行代码禁止终端将用户输入的字符回显到屏幕上,使得输入的字符不可见。
因此,该行代码的作用是在newAttr
结构体中关闭规范模式和字符回显,对应的终端属性被修改为禁用这两个特性。
当单独解释这行代码中的每个符号时,可以得到以下详细解释:
&=
是一个复合赋值运算符,用于对变量进行按位与操作后再赋值。它将左边的操作数与右边的操作数进行按位与运算,并将结果赋值给左边的操作数。|
是按位或运算符,用于将两个操作数的每个对应位进行逻辑或运算,得到的结果作为新的值。如果两个操作数的任意一位为1,结果的对应位也将是1。
在代码中,newAttr.c_lflag &= ~(ICANON | ECHO);
将结构体 newAttr
中成员 c_lflag
的值与 (ICANON | ECHO)
进行按位与运算后再赋值给 c_lflag
,具体步骤如下:
ICANON
和ECHO
是两个常量,代表各自指定的位标志。ICANON | ECHO
使用按位或运算符|
将ICANON
和ECHO
的位标志进行合并,并得到一个新的值,该值的位组合了这两个标志的状态。~(ICANON | ECHO)
使用按位取反操作符~
对上一步得到的值进行取反运算,即将所有位取反(1 变为 0,0 变为 1),得到一个新的值。newAttr.c_lflag &= ~(ICANON | ECHO)
将newAttr.c_lflag
的当前值与上一步得到的取反值进行按位与运算,然后将结果赋值给newAttr.c_lflag
。这个操作会将newAttr.c_lflag
中对应于(ICANON | ECHO)
标志位为 1 的位清零,而保留其余位的值不变。
综上所述,这行代码的作用是通过按位与运算和按位取反操作,将结构体 newAttr
中成员 c_lflag
的指定标志位清零,即禁用了规范模式和字符回显。
这行代码用于将修改后的终端属性应用到标准输入(stdin)。
以下是该行代码的详细解释:
tcsetattr(fileno(stdin), TCSAFLUSH, &newAttr);
是一个函数调用语句,调用了tcsetattr()
函数。tcsetattr()
是一个系统调用函数,用于设置终端的属性。它接受三个参数:文件描述符、操作选项和指向termios
结构体的指针。fileno(stdin)
是一个函数调用,stdin
是标准输入流的文件指针,而fileno()
函数用于获取对应文件指针的文件描述符。stdin
是一个C语言中预定义的标准输入流。它是与程序相连的默认输入流,通常用于从用户读取输入。TCSAFLUSH
是一个操作选项,用于指定在应用新的终端属性之前如何处理已经存在的输入输出数据。TCSAFLUSH
选项表示丢弃尚未传输给进程的输入数据,并且清空已经缓冲但尚未传输给终端的输出数据。- 具体而言,使用
TCSAFLUSH
选项会执行以下操作:首先等待缓冲区中所有的数据都已传输完毕,接着将终端的属性设置为新值,最后丢弃缓冲中尚未传输的数据。
&newAttr
是传递给tcsetattr()
函数的第三个参数,它是指向newAttr
结构体的指针。这个结构体包含了要应用到终端的新属性值。
综上所述,这行代码的作用是将修改后的终端属性应用到标准输入(stdin),以及通过指定的操作选项(TCSAFLUSH)来处理已经存在的输入输出数据。
在代码tcsetattr(fileno(stdin), TCSANOW, &oldAttr);
中,TCSANOW
是一个操作选项,用于指定终端属性修改的立即生效方式。
具体而言,TCSANOW
选项表示立即将新的终端属性值应用到终端。无需等待现有输入输出数据的处理或刷新,新的属性立即生效。
与之相比,TCSAFLUSH
选项(在上一个问题中提到过)在应用新的终端属性之前会丢弃尚未传输给进程的输入数据,并清空已经缓冲但尚未传输给终端的输出数据。
因此,tcsetattr()
函数根据不同的操作选项来决定何时应用新的终端属性,并处理缓冲区中的输入输出数据。
运行
完全隐藏
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#define MAX_PASSWORD_LENGTH 20
// 从终端获取密码(隐藏输入)
void getPassword(char* password) {
struct termios oldAttr, newAttr;
// 禁止终端回显
tcgetattr(fileno(stdin), &oldAttr);
newAttr = oldAttr;
newAttr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
tcsetattr(fileno(stdin), TCSAFLUSH, &newAttr);
// 读取密码
fgets(password, MAX_PASSWORD_LENGTH, stdin);
// 恢复终端设置
tcsetattr(fileno(stdin), TCSANOW, &oldAttr);
}
int main() {
char password[MAX_PASSWORD_LENGTH];
printf("请输入密码:");
getPassword(password);
printf("\n你输入的密码是:%s\n", password);
return 0;
}
newAttr.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL | ICANON);
这行代码的作用是修改newAttr
结构体中的c_lflag
字段,以关闭终端的回显和规范模式。
让我们逐个解释其中的操作:
~
运算符:它会对括号内的值进行按位取反操作。在这里,它用于将后面括号内的各个标志取反。这样做的目的是为了关闭这些标志所代表的功能。ECHO
:表示在用户从键盘输入时回显(显示)所输入的字符。
ECHOE
:表示擦除错误处理。当用户输入退格键或删除键时,会擦除之前输入的字符。ECHOK
:表示擦除当前行的处理。如果用户输入了一个换行符(Enter键),则会擦除当前行。ECHONL
:表示换行处理。如果设备支持,新的一行将以回车符和换行符结尾。ICANON
:表示规范模式。在规范模式下,终端会对输入进行行缓冲处理,直到接收到一个换行符才将数据传递给程序。
因此,通过对newAttr.c_lflag
字段进行按位与取反的操作,代码将关闭上述描述的回显和规范模式相关的功能,从而达到修改终端属性的目的。