Hide password input (Replace input with asterisk)

Hello, all.

How can I hide the user’s password input using asterisk?
For example,
To make it

44> io:fread('Password> ', "~s").
Password> pwd
{ok,["pwd"]}

look like

44> io:fread('Password> ', "~s").
Password> ***
{ok,["pwd"]}

I found 2 functions in the module /path/to/otp//lib/stdlib/src/io.erl

 393 -doc false.                                                                                         
 394 get_password() ->                                                               
 395     get_password(default_input()).                                              
 396                                                                                 
 397 -doc false.                                                                     
 398 get_password(Io) ->                                                             
 399     request(Io, {get_password,unicode}).
 45> io:get_password().

"pwd"
46> io:get_password(standard_io).

"pwd"

They do without an asterisk.
There is no documentation on them here stdlib/io.
Why are they missing from the documentation?

I also found that it is possible to disable echo using io:setopts/1, io:setopts/2 functions.

It is undocumented for a reason, and so I would say use that function at your own risk :slight_smile:

There are a few ways to get passwords while trying to hide input.

Here is one, which there problems with last time I tried, but it may be fine now :

-module(test_password).

-define(OP_PUTC, 0).

-export([get_passwd/0]).

get_passwd() ->
    get_passwd("Password: ").

get_passwd(Msg) ->
    case io:setopts([binary, {echo, false}]) of
        ok ->
            PwLine = io:get_line(Msg),
            ok = io:setopts([binary, {echo, true}]),
            io:format("\n"),
            [Pw | _] = binary:split(PwLine, <<"\n">>),
            Pw;
        _ ->
            error_logger:tty(false),
            Port = open_port({spawn, "tty_sl -e"}, [binary, eof]),
            port_command(Port, <<?OP_PUTC, Msg/binary>>),
            receive
                {Port, {data, PwLine}} ->
                    [Pw | _] = binary:split(PwLine, <<"\n">>),
                    port_command(Port, <<?OP_PUTC, $\n>>),
                    port_close(Port),
                    error_logger:tty(true),
                    Pw
            end
    end.

This will fully hide input vs putting asterisks on the screen, there’s another issue with this, it will not work on windows iirc.

The other issue documented here which either needs to be closed or a PR done (I never got around to doing it) was specifically related to escripts. I’m eager to try this again though, it’d be quite nice utilize this on windows.

io:get_password/1 could be “fixed”, but I’m not sure the OTP team would be interested in this (documenting and supporting it).

If you are okay with a “it basically works” solution that works on all platforms, then you could go with what we currently do in rebar3_hex (and mix hex as well) as seen here

Edit:

Note that the tty_sl solution only works with OTP versions prior to OTP 26.

1 Like

Hello, @starbelly.

Thank you so much.

1 Like

You are welcome, to note the tty_sl method won’t work for an escript still, it seems.

Edit:

io:get_password/0/1 works in OTP 27. I wonder if this should be documented now :slight_smile:

Hello, @starbelly.

I was looking for information about tty_sl.
From here The Beam Book/:
Erlang/OTP comes with a number port drivers implementing the predefined port types. There are the common drivers available on all platforms: tcp_inet, udp_inet, sctp_inet, efile, zlib_drv, ram_file_drv, binary_filer, tty_sl. These drivers are used to implement e.g. file handling and sockets in Erlang.
From here /path/to/otp/lib/kernel/src/user_drv.erl

   144 %% Backwards compatibility with pre OTP-26 for Elixir/LFE etc                   
   145 -spec start(['tty_sl -c -e'| shell()]) -> pid();                                
   146            (arguments()) -> pid().                                              
   147 start(['tty_sl -c -e', Shell]) ->                                                                                                                                                                            
   148     start(#{ initial_shell => Shell });                                         
   149 start(Args) when is_map(Args) ->                                                
   150     case gen_statem:start({local, ?MODULE}, ?MODULE, Args, []) of               
   151         {ok, Pid} -> Pid;                                                       
   152         {error, Reason} ->                                                      
   153             {error, Reason}                                                     
   154     end.                                                                        

What does it (-c, -e) stand for?

Hello, @starbelly.

io:get_password/0/1 works in OTP 27.

Yes. I am using OTP 27.

Right, so it’s still use at your own risk (it could change, it could be removed, etc.), this won’t put asterisk in place ofc, but is a much better solution than either I shared above.

That said, I’m now interested in if it should be documented…

Hello, @starbelly.

Thanks. Ok.
What does it (-c, -e) stand for in 'tty_sl -c -e'?

-c and -e should put the driver into non-canonical mode with no echo support. It also appears this driver was removed, and instead what you pasted from user_drv is for backwards compat and will now simply use the shell (which is an mfa, or mfargs if you want to be pedantic).

So, the tty_sl solution I posted for will only work with version prior to OTP 26 I believe.

Hello, @starbelly.

Ok. Thank you.