How to read passwords and sensitive data from the command-line using Swift Jul 17 2020

Shoulder surfing is a real threat. And we, as software developers, should strive to provide safety to our users. One way to mitigate the inadvertent exposure of sensitive data is related to how we handle the input of sensitive data in our applications. Every time the user inputs sensitive data, we should hide it from prying eyes. In this post, we'll learn how to read passwords and passphrases on a command-line tool built using Swift.

Let's get started.

Reading passwords

The typical scenario we should handle is reading passwords. When we prompt the user for a password, we should not echo the characters the user types to the screen. I mention passwords, but the same applies to reading PINs, passphrases, or any other sensitive data. You've probably seen this behaviour when you type sudo(8), the program prompts for your password, but it doesn't display anything on the screen.

Let's create a small Swift script to learn how to do this. If you want to review how to use Swift for scripting read my post:  Using Swift for scripting .

Create a file with the name secret.swift, and add the following content:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env swift
import Foundation

print("Please enter your password:")
guard let password = readLine(strippingNewline: true) else {
    print("Password is required")
    exit(1)
}
print("Your password is: \(password)")

We need to grant execution permission to the script:

1
$ chmod u+x secret.swift

And we can run it:

1
2
3
4
5
$ ./secret.swift
Please enter your password:
mysecretPassword
Your password is: mysecretPassword
$ 

I typed mysecretPassword, but you should see whatever you typed on the screen. This behaviour is terrible (I'm not talking about tool displaying my password afterwards), the tool allows anyone looking at my screen to read my password as I typed it.

Luckily, we can make use of the C function getpass(3) from the standard C library. Let's replace our call to readLine with a call to getpass.

If you want to learn more about the getpass(3) function, you should read the man page. But I'll show you the general use case. The following is getpass signature:

1
char * getpass(const char *prompt);

It expects the function to receive a C string with the prompt, and it returns a pointer to a C string. For example:

1
let myCString = getpass("Password please:")

That command should show the prompt Password please: on-screen and then wait for you to type the password and then store that on the myCString variable (which is a pointer to a cString). Let's modify our code:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env swift
import Foundation

print("Please enter your password:")
guard let password = getpass("") else {
    print("Password is required")
    exit(1)
}
print("Your password is: \(password)")

We are giving it an empty prompt because we are printing it before. I tried to change the least amount of code from the example, but you would probably replace the print with the prompt in your command-line tool. Let's rerun your program:

1
2
3
4
$ ./secret.swift
Please enter your password:

Your password is: 0x00007fb1995eba80

Great! It didn't show my password when I typed it, but why did it display that strange number?

Well, as I mentioned, it is a cString, that means that it is a pointer to the memory address that stores the String. Keep this in mind when we are using calls to C functions we are dealing with C interoperability. If you are unfamiliar with it, you might want to read my post on  Using BSD Sockets in Swift , especially the section on C interoperability.

The good news is that Swift's String already has an initialiser that supports cStrings. So let's fix that:

1
2
3
4
5
6
7
8
9
#!/usr/bin/env swift
import Foundation

print("Please enter your password:")
guard let password = getpass("") else {
    print("Password is required")
    exit(1)
}
print("Your password is: \(String(cString: password))")

If you run your script, it should produce the desired effect.

getpass(3) limitations

If you read the man page, you'll see that the getpass(3) has a limit of characters it reads. In my case 128. We can test this by generating a String of 129 Characters and see if we can read it.

1
2
$ echo 'print("\(String(repeating:"A", counting: 126))BCD")' | swift -
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABCD

We are going to use that string as our password, copy and paste it when your script asks for it.

1
2
3
4
$ ./secret.swift
Please  your password:

Your password is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABC

As you can see, we lost our D Character. If we need to read more than 128 Characters, we can use the readpassphrase(3) function, also from the C standard library.

This function is a little bit more complicated, not much, but more moving parts than getpass(3). As always, I recommend you to read the man page for more details. The following is the readpassphrase(3) function signature:

1
2
char *
     readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags);

As you can see it also returns a pointer to a C String. The interesting part is the parameters it receives.

The possible flags are:

1
2
3
4
5
6
7
     RPP_ECHO_OFF     turn off echo (default behaviour)
     RPP_ECHO_ON      leave echo on
     RPP_REQUIRE_TTY  fail if there is no tty
     RPP_FORCELOWER   force input to lower case
     RPP_FORCEUPPER   force input to upper case
     RPP_SEVENBIT     strip the high bit from input
     RPP_STDIN        force read of passphrase from stdin

Ok, let's see an example, it'll make everything easier to understand. Let's say we want to read 129 Characters. Our script will look like this:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env swift
import Foundation

print("Please enter your password when prompted")
var buf = [CChar](repeating:0, count: 129)
guard let password = readpassphrase("(129)Password:", &buf, buf.count, 0) else {
    print("Password is required")
    exit(1)
}
print("Your password is: \(String(cString: password))")

If we run it and pass the same input as before we get:

1
2
3
4
5
$./secret.swift
Please enter your password when prompted
(129)Password:

Your password is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABC

Oh, what happened? We didn't get the D character at the end. Well, remember that the cStrings are null-terminated (the last character is \0 to indicate the end of the string), so we get bufsize - 1. With that in mind, we need to pass 130 so we reserve a space for \0. Let's also make use of one of the flags (RPP_FORCELOWER) just for fun.

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env swift
import Foundation

print("Please enter your password when prompted")
var buf = [CChar](repeating:0, count: 130)
guard let password = readpassphrase("(129)Password:", &buf, buf.count, RPP_FORCELOWER) else {
    print("Password is required")
    exit(1)
}
print("Your password is: \(String(cString: password))")

Notice the change the count to 130. Let's rerun the script, and we should get what we expected.

1
2
3
4
$ ./secret.swift
Please enter your password when prompted
(129)Password:
Your password is: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabcd

Nice. If you would like to use more than one flag, remember you would have to use an |(or) operation on the flags. For example:

1
readpassphrase("(129)Password:", &buf, buf.count, RPP_ECHO_ON | RPP_FORCELOWER)

The RPP_ECHO_ON will display what the user is typing so that doesn't apply to our case, but it is good to know how to use multiple flags.

Ok, that's it for this post. I hope it was useful.

Final thoughts

One major advantage of Swift as a language for building command-line tools is that it can interact with C libraries, giving us a plethora of libraries to work with. Swift allows us to use its high-level syntax to express complex routines, and go down to low-level code by interfacing with C.

As I've mentioned in other posts, security should be at the forefront of the features we provide to our users. Build tools that you would feel safe using, the world will be a better place for it.

Now that you know how, every time you need to read sensitive data on your command-line tools, make use of the getpass(3) and getpassphrase(3). Both functions strive to clear the memory as soon as possible to prevent sensitive data from being stored longer than it needs on the active memory, which is a nice feature to have.

References


** If you want to check what else I'm currently doing, be sure to follow me on twitter @rderik or subscribe to the newsletter. If you want to send me a direct message, you can send it to derik@rderik.com.