TONY ARCIERI: We ready to go here?
So it used to be you didn't have to
think about security when you're writing a Ruby program.
Well, guess what, pall? Times have changed! There's all
sorts of bad people out there, and you gotta
know how to stop them!
All right. I'm not gonna do the entire talk
as Crocket impressions. Sorry.
So I am Tony Arcieri, or @bascule on Twitter.
I work on the, or work at the inform-
I work at Square on the information security team.
And my talk today is about cryptography and, you
know, I just want to encrypt something. How hard
could it possibly be?
The answer to this is definitely hard. So how
hard is it? Well, today you're gonna drink from
the fire hose, and I'm gonna show you exactly
how hard it is.
So quick itinerary here. We're gonna talk about attacks.
We're going to talk about how to defeat them
using something called authenticated encryption. And then we're going
to learn how to completely avoid all these problems
by just letting cryptographers do all this stuff for
us.
So, I think Ruby's traditionally been in a pretty
bad situation when it comes to cryptography, and the
main reason for this is the OpenSSL library has
been the only game in town for quite some
time.
So I think we can change this. I think
this is a fixable problem. But to do that,
we really need to know, how the hell does
crypto actually work?
So, the answer to this is magic. But, not
really magic. It's actually just math. So there's two
types of encryption cyphers I'm gonna talk about today.
We'll show how one builds on the other. The
first is called Symmetric. So here we have Alice
who just wants to send a message to herself,
or rather, just put this message away in a
box and lock it, and then she can use
the key to open it again and get the
original message out.
The other is asymmetric. So Alice wants to send
a message to Bob. Same idea here. She's gonna
put it in a box and lock it with
one key, and then Bob magically has another key
that lets him get the message out.
The big problem with encryption is it lies at
the intersection of math and security. So, like, math
is hard and security is hard, and when we
put all these things together, especially when we're talking
about doing this in a programming language where we
have bugs, things are gonna get pretty hard.
And this is one of my favorite quotes from
Cryptonomicon. So, this is kind of the situation we're
in, I think, in the Ruby world. That most
of the encryption gems out there aren't designed by
cryptographers, they're designed by amateurish people who are trying
to, trying to put everything together but maybe just
don't quite know how.
So I'm gonna go through all the attacks on
symmetric crypto. Unfortunately, I don't think I have time
to do asymmetric crypto. So I left a bunch
of those attacks out. But, guess what, it's even
worse. So we're gonna talk about AES today. AES
is a symmetric encryption cipher. It's great. I would
recommend you use AES, but it's sort of like
aim away from face, read all instructions before proceeding
kind of thing.
So this is how AES works. It's a block
cypher, so it works on fix-sized blocks. So we're
gonna take a sixteen byte piece of plain text
and we're gonna use a key, which can either
be sixteen, twenty-four, or thirty-two bytes. You want to
generate that randomly.
And from that we're gonna get a sixteen byte
block of cyphertext.
So, that's all well good. AES works great. There's
just this little problem. How do we encrypt something
that is large than sixteen bytes?
Well, one solution to this, PHP decided to use
a different ver- so AES is derived from a
cypher called Rijndael. PHP should be the version of
Rijndael with the 256 byte block size. So this
is awesome. It's a completely non-standard version of AES
that only PHP uses.
So let's not do that.
So let's figure out how to do this, right.
So the naive solution to this is to use
something called ECB mode. So there's this guy here,
he apparently couldn't find out how to use, do
encryption with OpenSSL itself, so he went off and
he made his own gem to encrypt stuff with
ECB mode.
You see there, he has C security nodes, ECB
mode only. The problem with the ECB mode is
it's really, really bad. So what ECB mode would
have you to is just take all these blocks
of plain text and encrypt them under the same
key.
So what's the problem with that? Well, it leaks
information. So let's say this is our plain text
we want to encrypt. If we run that through
ECB mode, we get something that looks like this.
So I think you can all see from this
that this thing isn't very well-encrypted. We can totally,
can totally see what it is.
So what's the solution to this? So, we need
to use a different block cipher mode of operation.
There's lots of these guys. We just kind of
have to pick one. There's all sorts of various
trade-offs. The one that people mostly settled on today
is countermode.
So counter mode is fairly easy to understand, I
think. It's sort of similar to a one-time pad.
So what we're gonna do is take a block
cypher, like AES and try to turn it into
a stream cypher. So what we're gonna do is
effectively generate a bunch of pseudo-random numbers.
So we feed in a nance, which is some
secret starting place, and a key, which is the
same as what we were putting in before. And
then there's this little counter, and every time we
crypt a block, it's just gonna count it by
one.
So what we're actually gonna do is combine the
nance and the counter, and encrypt that with AES,
and what we're gonna get is a little bit
of pseudo-random pad for each part of the plain
text. And for each of these paths, for each
of these blocks, we're gonna x over the pad
with a plain text, and that's gonna give us
our cypher text.
So if we go back to our little Ruby
gem image here, what we're gonna do is combine
this with a pseudo random pad, and what we're
gonna get is the cypher text. So I hope
you can see that little subtle change there. So
this is actually encrypted, right. We can no longer
see that gem and now we're done. Success!
We've, we've obtained confidentiality. Except a problem.
Our repeating nonces will leak information. So we can't
ever use the same nance and key. So the
solution is just don't do that.
So we've got another problem here. Support for counter
mode in Ruby OpenSSL is spotty. So unfortunately we
can't use sort of the industry best practices here.
And what we're gonna use is CBC mode. CDC
mode is fine. There are a few small issues
with it, but unfortunately I don't have time to
go into them.
So, next problem. Attacker, who we hand this message
to, can manipulate, with something called malleability. So let's
say our plain text is attack at dawn, and
we encrypt that and we have a cypher text
and we hand that to an attacker. Let's say
this attacker is able to guess what the plain
text was.
So what this attacker can then do is x
over part of the cypher text with what he
thinks the original plain text is, and then x
over that again with what he wants it to
be. So what you get is sort of this
like manipulated cypher text, and now when we decrypt
it, it gives us the wrong thing. This isn't
what we expected it to be.
So the solution to this is to use something
called a message authentication code. When we combine this
with a encryption cypher, what we get is something
called authenticate encryption.
So with a Mac, what we do is we
take the message and then we have a key,
and when we combine the message and the key,
we get this fix-lengthed tag. So we know we
have the right message when we take the same
message and the same key and then we get
the mac we expect.
So once again, there's a whole lot of these.
Like, HMAC is the one I'm sure you've heard
of if you've heard of one of these. The
others are somewhat less common.
So now we have yet another problem. What order
do we combine the encryption and the mac? So
there's effectively three ways to do this, and common
internet tools have all sort of chosen their own
different way. So the first is called mac-then-encrypt. This
is what's used by SSL and TLS. So the
idea is you take the plain text and you
compute the mac to the plain text, and then
you sort of combine those together and encrypt both.
Another way to do it is called encrypt-then-mac. This
is used by the IP sect protocol for, like,
yeah. So we have plain text. What we're gonna
do is encrypt it first, and then we're going
to calculate the mac of the cypher text. So
the third way, which is used by SSH, we
take the plain text and we encrypt it, then
we get the cypher text, and then we take
the original plain text and we compute the mac
of that and then we put the cypher text
and the mac of the plain text together.
So which, which of these sort of three standards
or three approaches got it right? Anybody want to
take a guess which of these is actually the
right way?
None. There's actually a right answer. One of them
did get it right. So the answer is encrypt-then-mac
used by IP sect. So why? What's wrong with
these other methods?
So SSL/TLS, you might remember there was this attack
called Beast. It's using something called a padding oracle.
I didn't really go into how padding actually works,
but it's how you deal with, when your plain
text isn't actually aligned to a block.
So, using Beast, they were able to get a
little bit of information out of when it decrypts
and it does this padding check and you can
tell if it got through the padding or not
before it hit the mac.
So the solution TLS uses now is to make
sure it always checks the mac, even if the
padding fails. So that's a little bit of a
band aid.
Encrypt-and-mac, used by SSH, is vulnerable to chosen cyphertext
attacks. There's a fun little paper on this. Fortunately
the SSH protocol was extensible enough they managed to
avoid, they managed to effectively fix this retroactively.
So in review here, if we were trying to
build our own authenticated encryption scheme, what we have
so far is sort of using AES in CBC
mode, since Ruby OpenSSL doesn't support countermode. We're gonna
do the IP sect thing and encrypt-then-mac and I
showed HMAC. HMAC is really nice cause it takes
a lot of the sharp edges off some of
the other macs.
So what this actually ends up looking like is
what's in Rails. The ActiveSupport message encrypter. So this
is definitely a cool thing to use if you
just need to encrypt something and you're using Rails.
It's definitely a way to go.
So let's talk about what else could go wrong.
Not done yet.
So the next thing is timing attacks on. So
speaking of that ActiveSupport message encryptor, it used to
be vulnerable to these. So this is a patch
by Koda Hail to implement a constant time comparison
of the mac. And the problem is if you
don't do this, the attacker can use this sort
of, like, infinitesimal timing information, especially if they're on
the same LAN as you or on the same
host, to just try to guess at the mac
a byte at a time.
So you see before what it was doing was
not equals. So the problem with not equals is
it'll sort of bail fast and exit early as
soon as it sees something that doesn't match. So
by using the timing information off of that, an
attacker can effectively guess the mac a byte at
a time.
So the solution is all that nonsense you see
down there at the bottom, where they're doing xor
between the two bytes and doing or equals and
doing this over all the bytes and then looking
at the actual value of the result.
So this is all pretty crazy stuff, right. And
we haven't even talked about pubkey. So really, I
don't think amateurs should be trying to put all
this stuff together. Unfortunately that's sort of been the
state of the world in Ruby.
So what can we do better? We can have
more boring crypto constructs. So what, what qualifies as
boring? Well, to do that, let's talk about what
isn't boring.
So this has been how most things have worked
in the Ruby world. So OpenSSL is kind of
a terrible library to begin with, and then pretty
much every Ruby gem you see that deals with
encryption just layers on a bunch of amateur code,
trying to put all this stuff I just described
to you together.
And typically, they'll get at least one thing wrong.
So, I'm not gonna name anymore names besides that
Fast APS gem, because I think that one was
just pretty crazy and dangerous. But pretty much every
gem out there, if you go through and you
try to look for all these things to make
sure they got it all right, they will generally
get something wrong. Oftentimes they will not use a
mac at all, so they'll just use encryption, and
the attacker can screw with your cypher text.
And then there's all sorts of other little things
that they can get wrong which I didn't even
cover here.
So the boring approach in my opinion is to
use a crypto library written by cryptographers. And when
I talk about a cryptographer, who am I talking
about? And it isn't someone like me, right. I'm
a crypto enthusiast, but I'm not a cryptographer. I
cannot design my own cyphers that will be secure
yet. It's something I aspire to maybe, but not
quite yet.
So what we really want is to bind to
a library that was actually written by cryptographers, people
who spend all their time thinking about these kinds
of attacks.
So I have written a library like this that
is actually a FFI binding to an actual library
written by cryptographers. So the name is, unfortunately, a
little bit confusing. I call it RbNaCl, but you
may also know there is Google native client. It
isn't that. And there's also a, a, an ACL
organization that, in Japan, you might be familiar with.
It isn't that.
So this is a library by this guy Dan
Burnstein. You may know him for libraries like, or
projects like Qmail, DJBDNS, and DaemonTools. And if you
haven't been paying attention to him for the past
decade or so, what he's really going hardcore for
is cryptography.
So he has designed quite a few of his
own cyphers. And all sorts of algorithms and created
this NaCl library, which is slowly gaining a little
bit of popularity. And you see the URL down
there. It's under the cryptosphere organization, slash rbnacl.
So to make things even more confusing, this isn't
actually binding to nacl, it's binding to a portable
repackaging of nacl called libsodium. So the best description
I've heard of this, is it's de-Burnsteinized. A lot
of, a lot of people don't like DJB's bold
system, and it's kind of crazy cause the way
it works, once you bolt the library it's completely
non-relocatable, so you can't really package it as a
binary for a distribution.
So libsodium took nacl and added a standard automake
style build system. So it's easy to install, it
is in Mac ports, and you can install it
with brew install libsodium. There are packages for various
Linux distributions. Some of them, you know, you'll have
to actually build yourself from source, but the basic
work is there, and it's getting more and more
widespread option.
So nacl has many primitives. I'm only gonna talk
about two here. So these are the symmetric and
asymmetric encryption I was referring to earlier. But it
also has a ton of other stuff, so it
has HMAC. It has digital signatures. It has all
sorts of fun stuff for elliptic curve cryptography.
So I definitely recommend checking out. You can go
to the rbnacl wiki and it has all the
features listed out for you.
So first I'm gonna talk about the symmetric encryption
primitive it provides. So this is authenticated symmetric encryption
and it is called secretbox.
So this may be a little hard to see.
But it should be fairly straightforward to follow, I
hope. So what we're gonna do is we're gonna
make a key that is the size of a
secret box key. It's actually thirty-two bytes.
So you get a random key and that is
using rbnacl's own random number generation. On Unix-like operating
systems it upholds from devurandom and on Windows it
uses, I forget what it's called. Yeah. Windows.
Anyway. It does work on Windows.
So after we've done that, we're gonna make a
new secret box with the key, and then here's
the fun part. We need to make a nance.
So this is just generating a random nance of
the secret box's nance byte's length.
There's also another feature in rbnacl called random-nance box
that'll do all this stuff for you. The important
part is a nance is a single-use value. So
every time you encrypt something with this box, you
need to make a new nance.
So it doesn't actually have to be random. It
could just be a counter. The nice thing about
it being random is it's long, so it's twenty-four
bytes, so if you just do a random number
every time it's hard to screw up. You don't
need to keep track of the state of how
many nances you've used.
So after that you hand the nance and the
message to the secret box. It'll make the cypher
text for you. And to decrypt it, you give
the same nance and the cypher text and it
will decrypt. And behind the scenes, this is doing
all the authenticate encryption stuff. So it's adding a
mac, it's checking it, and it's using an encrypted
mac.
And then down there at the bottom, you see
if you try to open the box and something
is wrong, either the nance is wrong or the
cypher text has been corrupted, it'll raise an exception
for you reliably.
So this is using a couple of algorithms designed
by Dan Burnstein. So the encryption cypher is called
the XSalsa20, and the Mac is called Poly1305. So
probably right now, you're thinking, what the hell is
XSalsa20. Like, never heard of this. That can't be
boring, right.
So I think this is boring because it is
a cypher that has sort of been standardized through
this contest in Europe called ecrypt. And the goal
of ecrypt is sort of like when there is
an AES contest. They're trying to produce better stream
cyphers so the Salsa20 cypher was one four they
selected for inclusion in what they call the estream
portfolio.
I say this is boring because things have gotten
very not boring in the cryptography world lately. So
you may have heard nist standardized a random number
generator designed by the NSA called duel EC DRBG,
and turns out that thing had a back door.
So there's still open questions about the nist elliptic
curves as well and whether or not they contain
possible NSA backdoors. I mean, if you're asking one
layman's opinion, I'd say it's probably unlikely they have
backdoors in nist elliptic curves, but it's hard to
know, and I think the boring thing is to
use cyphers that are beyond reproach.
So to take, to do asymmetric crypto using the
same sort of primitives, we have this thing called
box. And here is an example on how to
use box. So this time we have to actually
generate a random key.
So this is using that elliptic curve stuff I
was talking about earlier. I'll get into how that
works a bit later. So we take a private
key and, unlike RSA, with elliptic curves we can
calculate the public key from the private key. I
think I spot a typo there. Awesome. Oh no.
So yeah. So once we've done that, we have
a public key and you'll notice here when we
make a new box we're giving it both somebody
else's public key and our private key, and the
neat thing about this is it's performing something called
mutual authentication.
So all these boxes are based around a set
of keys. It's who you're sending the message to,
and your private key, so that way when somebody
opens up the box, they'll know they got it
from the right person.
So make a new box. They'll also have a
box on the other side. It is effectively the
same box, just computed from the irreversible keys.
So then after here it's pretty straightforward, just like
the symmetric crypto. So what we're gonna do is
make a nance again. We have the message and
we're going to encrypt the message under that nance
and under that pair of keys. We get a
cypher text again and then the person on the
other side can open it, and once again, like
before, if it has been tampered with it will
raise an exception.
So box is built on the same primitives as
secret box, so it's still using the xSalsa20 cypher
and the Poly1305 mac. The main difference here is
we're adding in a Diffie-Hellman function. So that is
called curve25519. And that is an elliptic curve algorithm
designed by Dan Burnstein.
So one of the big worries about elliptic curve
cryptography, in addition to possible NSA tampering has been
patents. So DJB has designed this curve specifically to
side-step all the patents. It's pretty awesome. If you
go to the website for it you'll also add
the patent, and he lists out the prior art
saying this patent is bullshit, anyway. But then he's
like, here I completely avoided that problem entirely.
So really quick, here's how Diffie-Hellman works. SO it's
pretty simple. What we do is a thing called
scalar multiplication. So we take Alice's private key and
perform a scalar multiplication operation with Bob's public key
and we get a shared secret.
And Bob is able to calculate the same secret
by multiplying his private key by Alice's public key.
So these two, two operations are sort of like
a reflection of each other.
So secret, box works by taking that shared secret
and using it to drive a symmetric key.
And that's all I got. Keep it boring.