I’m a Computer Scientist. Here’s Why You Should Never Trust a Computer.

It’s 2018. We live in the future. We can order a pizza, watch it get made, and watch it get delivered to our house. So why can’t we vote online?Let’s start with some background on what programming languages are, and why we need them. Soon, you’ll see why you should never want to vote online (and why you never want a computer anywhere near you when you’re voting).You probably know computers run on binary, 1s and 0s. And writing in binary is hard—so hard, in fact, that basically nobody wants to do it. Even if you succeed in doing it, what you’re producing is just a bunch of numbers, and it’ll be very hard for anyone—including you in a few weeks, once you forget what you wrote—to figure out what your code actually does.

So instead, we computer scientists invented “machine languages.” These are abstractions that change binary code into something that’s at least a little closer to languages humans speak. They’re still basic, but they’re a step in the right direction. Machine languages are based on—and tied to—the hardware of whatever machine they’re designed for. So while you can’t say something easy like “add 10 and 20 together and print that result to the screen,” you can say “place the value 10 in register one, place the value 20 in register two, feed both these registers into adder one, and put the output in register three, and print the contents of register three to the screen.” The machine language is then translated—this is called “compiled”—into the binary 1s and 0s required to actually run on your computer.

There are obvious downsides here: You need to be familiar with your computer’s hardware to write in a machine language, and every computer’s architecture is slightly different. Plus, you have to explicitly specify every step of the process. That’s a pain. But the upside is that when you’re looking at a program written in machine language down the road, what’s happening is much clearer—especially compared to looking at an endless stream of 1s and 0s in binary.No matter what you write, you’re trusting the compiler to accurately turn what you wrote into binary code.

If I wanted to mess with your results, all I’d need to do is mess with your compiler.The next step up is to abstract away the hardware, so you don’t actually need to know the location of things like “adders” and “registers.” If you build a smart enough compiler, you can design machine-independent programming languages, with more abstract instructions that could easily handle things like “add 10 and 20 together, and print that result to the screen.” You’d then rely on the compiler to translate that into machine language and then into binary.While all of these programming languages take different approaches to solving this problem, they share the same goal: to make computer code easier for humans to read, which makes it easier to understand and easier to maintain. Programming languages today make printing the result of 10+20 as simple as writing this:print 10+20Did you spot the reason you can’t trust any computer?I’ll give you a hint: It’s there in the compiler.

No matter what you write, you’re trusting the compiler to accurately turn what you wrote into binary code. If I wanted to mess with your results, all I’d need to do is mess with your compiler.For example, if I changed the “print” command so it always added 1 to the numbers you gave it, your program wouldn’t run properly—even though you programmed it correctly. You’d never find the glitch just by looking at your source code because that’s not where the glitch is. It’s hidden in the compiler.That example is basic and you’d detect it pretty quickly because your program would obviously be broken. But what if I did something more subtle? What if instead of messing with the “print” command, I changed the compiler so that whenever it detected code involving passwords, it made it so the password “ryaniscool” also worked?It’s not the end of the world if someone hacks in and sees my pizza being delivered. Nobody cares enough to try to break it. But voting is not one of those cases.

If I did that, I’d have what’s called a “back door” into every computer program you build with my compiler. In other words, you can lock your front door all you want, but it doesn’t matter because I have a secret door around back nobody knows about. No matter what you write, no matter how secure your password code is, my password of “ryaniscool” is also going to work—and you won’t even know it.Obviously, this is a problem. And you might think, “But compilers are computer programs like any other. I could look at the source code of my compiler to make sure there’s no malicious code there. All I’d need to do is find the part that talks about adding ‘ryaniscool’ as a password, take it out, and I’d be fine. Right?”And you could.

Except, as you said, compilers are computer programs like any other. And that means they themselves are compiled.Here’s all I’d need to do to exploit that:

Step 1

As before, I’d write code that adds “ryaniscool” as a valid password to anything it compiles and put this in the compiler. At this point, I’m adding a back door to anything the compiler compiles, but I’ll get caught if anyone looks at the source of my compiler. So I go on to Step 2.

Step 2

I write code for the compiler that detects when it’s compiling itself, and when that happens, it adds in the code for step 1 into the compiler. Now, when I compile the compiler, it makes a new version of itself that will add in compiler instructions for how to insert the “ryaniscool” password whenever the compiler is rebuilt. And to cover my tracks, all I’d need to do is remove the malicious instructions from the compiler source, and I’m done.Whenever the compiler is rebuilt, it’ll build itself such that it’ll contain instructions to add my back door. Whenever that compiler builds something else, it’ll follow those instructions and build my back door right in. And there won’t be a single line of malicious code left in any source code that reveals it.

The only way to detect this bug is to go over the binary code yourself—a task that starts out hard and becomes literally impossible as programs become more complex. The complete works of William Shakespeare come in at under 6 megabytes. The Firefox browser alone requires 200 megabytes just to install it, and that’s only one program on your computer. There is not a human alive who has read all 200 megabytes of that code. It’s not even written in a language designed for humans to read.Why are we using these nightmare machines?None of this is new.

In 1984, Ken Thompson—the man who designed and implemented Unix, the progenitor of the operating systems most computers and phones run on—presented a paper called “Reflections on Trusting Trust” and reached this conclusion:The moral is obvious. You can’t trust code that you did not totally create yourself… No amount of source-level verification or scrutiny will protect you from using untrusted code.By “totally create yourself,” Ken doesn’t just mean a program that you wrote, but one you wrote the entire stack for: everything down to the compiler.

Very few people have the time, skills, and money to build a computer from the ground up, including all the software on it. This would seem to be a bullet in the head for trusting computers with anything.And yet, we trust computers with all sorts of things. So, what gives? Why are we using these nightmare machines?Well, for one thing, computers are really fun and convenient. And they’re practical in a lot of ways. Besides, a compiler hack can be tricky to pull off in practice: You’d need time and motivation to target someone. The truth is, there are many cases where you don’t need absolute trust in your computer: After all, it’s not the end of the world if someone hacks in and sees my pizza being delivered. Nobody cares enough to try to break it.But voting is not one of those cases.The only safe-ish way to vote with a computer is one in which a paper ballot is printed in sight of the voter, approved, and then stored in a ballot box.Voting is a case where the outcome of a hack can have huge effects.

Voting is also relatively easy to target (you know when and where it’s going to happen), and there’s a very strong motivation to alter the outcome. As easily as I could add that “ryaniscool” password, I could change the “add” command so that, when it was tallying votes, it added some extra for the party of my choice.How much should I add? Honestly, at this point, it’s entirely up to me. Hence this conclusion: Online voting will never be safe. Computer voting will never be safe.The only safe-ish way to vote with a computer is one in which a paper ballot is printed in sight of the voter, approved, and then stored in a ballot box. That way, if someone thinks the computer systems were compromised—if there’s any reason at all to suspect someone added the votes improperly—then there’s a paper trail. In other words, the computer adding up the votes is a convenience, nothing more. The real vote, the real power, still lies in the paper ballot.Without that paper trail, you’re left trusting the computer.And nobody should ever trust a computer.