I have taken a couple terabytes of photos over the years, and have a mild phobia of deleting data, so I keep my photos on a Synology DiskStation NAS. I’ve got all the files on the thing:
- backed up to the cloud
- encrypted,
because back in 2016, my apartment was broken into and thieves took all the electronics. I’ve been slightly paranoid ever since.
The Synology NAS generally works pretty well, but the interface for decrypting a drive after booting is extremely painful. It’s about 10 clicks, a username, two different passwords, and a UI that is kinda wonky (it’s a desktop in a web browser)? Here’s a screengrab:
There are alternative solutions to this – you can set it up so that it reads the encryption key from a USB disk on startup, for example – but, if I’m worried about the whole thing being stolen, it’s not a good idea to leave the encryption keys plugged into the thing, and it’s also not a nice UX to find and plug in a USB before turning the thing on.
This… seems kinda silly to me, and something that should be solvable with ✨ technology ✨. I’m only ever accessing this from one computer, for example, and every modern operating system has a good credential storage system (think Keychain Access on OSX or Credential Manager on Windows 10). So, maybe my computer could take care of supplying encryption keys to the DiskStation!
I went looking for options, and discovered that the DiskStation Software has a publicly-documented web API (pdf). At least, a mostly publicly documented web API – there’s no documentation for the methods to mount and unmount encrypted shares.
That web API is also used by the desktop software though! So it should theoretically be possible to use browser devtools to see exactly what is being sent to the DiskStation. 🤔
So, I clicked on all the right things in the Web UI, got to the bit where you type in the password, opened the devtools to the “Network Requests” tab, typed in abc123
as my password, hit “send” and then watched for the requests:
Hmmm, that encryption.cgi
looks a little ominous. I wonder what’s in it?
Ok, a public key. And then the web interface loads a spinner, and makes a request to entry.cgi
. Let’s take a look at the contents of that request:
Ok, so if we want to send a magic request to mount the encrypted share, it’s not going to be enough to supply the encryption password; we’re also going to need to figure out how to correctly supply it – how to encrypt the encryption password, so to speak.
So, what is this crypto system?
Right. Here’s a high-level overview:
- First, we fetch the
public_key
from the server. That’s the call to theencryption.cgi
endpoint we saw earlier. This isn’t actually the entire public key, it’s only part of it. In fact, this is a 4096-bit integer, encoded in base-16. The other part of the public key is the number0x10001 == 65537
, which is hardcoded into the JavaScript. - Then, we generate 501 bytes worth of random text from the charset
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ~!@#$%^&*()_+-/
(that’s 77 different possible characters; I have no idea why this set was chosen though!). - We encrypt that random text using our public key. The encryption method is RSA with PKCS1.5 padding.
- We MD5-hash that random text a bunch of times with an 8-byte salt. The end result of this is 48 bytes which certainly look pretty random to the naked eye.
- We split those 48 bytes into two components – a 32-byte (== 256-bit) key, and a 16-byte initialisation vector.
- Remember the password we typed in? Yeah, I’d forgotten about it too! But now, we finally take that password, and encrypt it with AES in Cipher Block Chaining (CBC) mode with PKCSv7 padding, using the 256-bit key and 16-byte IV we derived in the previous step.
- We bundle the RSA ciphertext, the 8-byte salt, and the AES ciphertext together and ship them across to the DiskStation.
Still with me? It’s ok if that didn’t all make loads of sense yet; we’ll now go over the interesting bits more closely.
Symmetric / Asymmetric Ciphers
Fundamentally, we’re using two cryptographic algorithms in concert:
- RSA: RSA is an asymmetric cryptographic algorithm. Asymmetric cryptographic systems have two keys – a public key, which allows only for encryption of the data, and a private key, which allows for both encryption and decryption.
- AES: AES is a symmetric cryptographic algorithm. In symmetric cryptography, all parties have the same key, and that key allows you to both encrypt and decrypt.
It seems weird that we’re using both of these at the same time, right? But both systems have weaknesses, and mixing them together is an attempt at minimising the disadvantages of both schemes.
The problem with symmetric crypto systems by themselves is that you need a way to get that one key to every party that needs it. But you can’t just send it to them, because you don’t have a secure channel yet! Your options are:
- Share the key over a different trusted channel beforehand, or
- have both parties derive the same shared key (from e.g. some partial data that they negotiate over the wire beforehand).
Asymmetric crypto doesn’t have this problem – if you’re able to share a key that can only be used for encrypting and not for decrypting, then the decryption keys never have to leave the recipient machine. You can then encrypt some text, and nobody except for the recipient can decrypt it – not even you! Put it on the internet, write it on a banner attached to the back of a propeller plane – the message will only be readable by the intended recipient.
Asymmetric cryptography is slow, though, so doing it for lots of data is going to really hurt performance.
A common solution is to simply make up a symmetric key, perform some symmetric encryption using that key, then encrypt that key using asymmetric cryptography, and deliver both to the remote party. Then, the remote party can:
- First, perform asymmetric decryption to obtain the symmetric key
- Then, use that symmetric key to decrypt the rest.
That’s what this particular system is doing. ✨
A quick note about RSA
I don’t know much about how RSA works, but as part of researching this article I discovered that it’s pretty easy to implement badly, and extremely difficult to implement well.
As software developers, we spend a lot of time shipping things once they get to a point of “it looks like it works”. You can’t take that approach with cryptography, and because everything is so opaque, it’s a lot easier to declare that your implementation works without realising that it’s missing fundamentals. Like correctness, for example.
Let’s talk about random keys2
Recall that this algorithm is generating 501 random bytes from a 77-character set, and encrypting that with RSA. 501 seemed like an oddly specific number to me; here’s the specific set of circumstances that characterise why we’re generating a string of this length.
RSA is a block cipher, which means it operates on distinct fixed-size blocks of input and produces fixed-size blocks of output. How big can each block be? Well, RSA public keys are made up of two numbers: a (very large) modulus n
, and a much smaller exponent e
. RSA is capable of encrypting any message that’s smaller than n
if you convert it to a number. In our case, we said that our modulus was a seemingly-random 4096-bit integer – that means (approximately) that we can encrypt 512-byte blocks.
There’s an important implication here: if you don’t have enough input for a block, you need to fill it up somehow. Normally this is called a padding scheme3 in block-based cryptography.
Now, the system on this NAS in particular uses PKCS1-v1.5 padding, which prepends the message (before encryption) with 3 bytes of headers plus a minimum of 8 bytes4 of random garbage. That makes 11 bytes out of a 512 byte block, which means we’ve got… 501 bytes left for our message. ✨
Lo-fi key derivation
So… we’ve got this 501-byte string, with each character being one of 77 possibilities, and we think we know why it was chosen to be 501 bytes long. But that’s not going to work for AES-encrypting the password:
- AES requires a key size of 128, 192, or 256 bits. That’s 16, 24, or 32 bytes respectively, but we’ve got lots more than that.
- We can’t, however, just take a subset of bytes from the text and go from there – because they’re selected from a narrow subset of possible bytes (77 out of 255!), you’d be losing an enormous amount of entropy in the key, making the encrypted text about 44,000,000,000,000,000 times5 easier to break.
These constraints need to be managed. We can smooth over both by applying a key derivation function, based on repeated rounds of MD5 hashing and concatenating. Hash algorithms, in general, are good for this because they map input data to… not random, but random-looking output data.
I like to think of hashing as “entropy laundering” – if you’ve got 256 bits of entropy in a 16kB file, taking the first 256 bits will give you only 64 bits of entropy. If you hash the whole 16kB file, then the entropy goes back up to the maximum of the entropy in the file and the entropy provided by the hash algorithm. This works because hash algorithms are designed to exhibit an avalanche effect.
I do not have the tools or experience to evaluate whether this specific scheme is secure or not (this one seems esoteric, and the use of MD5 is scary!)… so let’s move briskly on.
AES-encryption
Finally, after a bunch of outdated and somewhat esoteric primitives, we’re back to something that is good and works and only slightly a loaded footgun and is still recommended for widespread use! It’s AES with a 256-bit key.
AES is our symmetric cipher. Like RSA, it operates on blocks of fixed length. Unlike RSA, these blocks can be chained together. The strategies to do this are called “Block cipher modes of operation”, and AES implementations ship with a number of them. The problem is, this means you have access to bad options as well as better options, which makes it easy to choose the wrong thing. Synology’s code uses Cipher Block Chaining, or CBC, which… unfortunately is pretty susceptible to a number of attacks, the classic being a padding oracle.
If you’re using AES for any of your own stuff:
- Use GCM for your block mode of operation
- Maybe consider following the recommendations here instead
- Have a crack at those Cryptopals challenges I mentioned earlier, they’re a great way of learning about the limitations of the shonkier block modes by guiding you through the process of breaking them.
What does this encryption scheme actually protect against?
That’s all a lot of rigmarole, but what does this scheme actually protect against? It’s helpful to consider the device in context here:
- It’s a special-purpose linux box
- sitting on a network
- accessible via either the public internet6 or a local network.
The problem is, you administer the device over an HTTP interface, and you can’t easily run HTTPS on local networks, because you can’t give the NAS a canonical hostname, and so it’s hard to get an HTTPS certificate that’s trusted by your computer’s default settings. So, Synology have opted to support doing everything over HTTP.
HTTP on a trusted network isn’t terrible, but as a device manufacturer, Synology have no guarantee that the network the device is running on is trusted! For example, if you’ve got one of these NAS boxes running on a passwordless wireless network, it’s trivial to intercept unencrypted HTTP traffic. In these specific situations, having a shonky encryption scheme is better than nothing – it prevents the password from being read by any ol’ eavesdropper sitting on the bench across the street.
It’s worth noting that all of this encryption is moot if someone is able to put another computer on your network that’s pretending to be your NAS. In technical terms, you’ve got no way of authenticating that what your computer is talking to is actually your NAS! So it’s relatively straightforward to mount a muppet-in-the-middle attack and steal passwords in that manner.
Fundamentally, this is hard because the security of the passwords you’re sending to your Synology NAS depends entirely on how you’re using the device, and on how its host network is configured. Synology is in the difficult position of trying to make sure that people don’t have their NAS credentials compromised without actually being in a position to control this particularly well.
Alternative: just use SSH
In the end, I decided not to use this API for my original project – my ideal scenario is that I’d be able to mount the encrypted shares automatically if they were locked, and if I’m automatically attempting to send my passwords across the network, I really want to be sure that my computer can authenticate the NAS. I could theoretically do that by building a method to mark my home network as trusted, but by this point, it seems simpler to pick an alternative approach.
Turns out the Synology NAS also supports SSH, and you can mount encrypted shares over SSH. I’ll probably do that instead, because SSH:
- is encrypted!
- supports authentication after you’ve seen the machine the first time
- is significantly more standard than a hand-rolled RSA/AES hybrid encryption system, and thus way easier to implement 😅
That’s it!
I wrote this blog post as a consolation prize for doing a whole lot of research and reverse-engineering that I’m not going to use after all. 😅 But I learned a little about cryptographic principles from examining this system, and hopefully you have from reading this article, too! If I’ve said something egregiously wrong or you want to commiserate about esoteric encryption systems in consumer devices, feel free to get in touch 📧.
Crypto twitter like, the cryptography people, not the Bitcoiners. ↩︎
It turns out that having padding is security-critical for RSA, which is slightly terrifying. Nate Lawson argues that padding is entirely the wrong name, and it should be called armouring instead, and Cryptopals have an exercise to teach you how to break unarmoured RSA. ↩︎
From the
RSAES-PKCS1-v1_5
specification,The padding string […] is at least eight octets long, which is a security condition for public-key operations that makes it difficult for an attacker to recover data by trying all possible encryption blocks.
I haven’t tried this, but it sounds fun? ↩︎
1/(77/255)^32
, assuming 256-bit keys. ↩︎The idea of putting my Synology NAS on the public internet gives me the heebie-jeebies: these things run loads of software on loads of ports, and I get the vibe that they’re easy to misconfigure and open up to attack. If you want to access your NAS from the internet, put it behind a firewall and set up Tailscale. ↩︎