While I think it’s fun to build tools and break from general advice, things like this almost consistently have the same markers that makes me go “oh no”. Why use RSA over a ECC algorithm? Why is this not using an authenticated encryption algorithm over AES-CBC? I don’t feel like a lot of the choices are very well justified in the blog. In the end this just strikes me as a use case for age (which btw is very small 4k LoC with tests), over potentially becoming reliant on broken things.
Tough crowd, but nice to have links that actually explain a problem that’s actually relevant. Thanks @quad! The unauthenticated encryption complaint was convincing. I verified that it is in fact possible to modify the output of the script (via its IV portion) to get the recipient to silently see different data. It’s not an attack mode (MITM) that we actually care about (other problems with that anyway), but it does seem silly not to use authenticated mode when it’s easy.
I also dropped support for encrypting with RSA directly in the same PR. Disappointed that neither reason in the linked article applies for this use-case, so can’t verify anything that’s wrong with it, but I can’t justify keeping it anyway, since it raises questions and is only there because that’s what the script started with, i.e. historical reasons.
It’s not an attack mode (MITM) that we actually care about (other problems with that anyway), but it does seem silly not to use authenticated mode when it’s easy.
Good question, that’s what I start the article with, but don’t say it explicitly. I am assuming I can talk to my colleague freely, no MITM or impersonator involved. I am concerned that putting a secret in plain text would mean it’s present on systems outside of my knowledge or control (like Slack servers). So I am assuming the messages in the channel can leak, and I am concerned about confidentiality.
That strikes me as quite in-the-moment. A future leak of a private key or a spearfish would allow an attacker to get all your secrets quite conveniently from Slack.
Disappointed that neither reason in the linked article applies for this use-case, so can’t verify anything that’s wrong with it, but I can’t justify keeping it anyway, since it raises questions and is only there because that’s what the script started with, i.e. historical reasons.
A big reason to use symmetric crypto over asymmetric has always been performance.
It’s reasonable to believe that most [cryptography] problems are caused by the authors’ unknown unknowns. That’s why you shouldn’t release your crypto code. You don’t know what you don’t know. A cryptographer is a person whose set of unknown unknowns (about cryptography) is a lot smaller than yours or mine.
Easy: Use 1password. It has added benefit of strongly encouraging good web password practices for your team. It is well trusted by my security professional friends, for whatever that’s worth, it’s enough for me to trust it.
The code and comments here are another example of why you shouldn’t roll your own crypto. You are not writing your own encryption algorithm, but still you and/or ChatGPT are not qualified to pick parameters and protocol construction from crypto primitives. If you want to build a tool around a well designed library, use libsodium or similar where the appropriate algorithms construction is encapsulated.
Cross-posting a GitHub comment I just left (I thought about just writing it on Lobsters, but I wanted people not on Lobsters to see the warning):
This contest does not provide the reassurance you think it does. While there’s some differences due to the use case, it reminds me strongly of when Telegram ran a contest for people to break their garbage encryption. Here’s a great post (since taken down both from the live internet and the Wayback Machine for some reason…) on why Telegram’s contest - and this one - prove nothing about the security of the cryptography. Borrowing stealing from that blog post, this contest is missing: no MITM perspective, no known plaintext, no chosen plaintext, no chosen ciphertext, no tampering, no replay access (maybe this one isn’t relevant side it’s not a messenger, but the rest are), etc.
You might argue that some of these are unfair. For example, in the use case the blog post describes (two humans manually encrypting blobs to each other), an attacker probably won’t get a chosen plaintext because that would require some social engineering. To which I say: do you really think it is a good idea to have the security of the cryptography rely on nobody ever building some kind of automation wrapper around this to make it a little more convenient to use? Not just anybody, a software engineer?
What you really want is a password manager. So just pay for Bitwarden or 1Password and get on with it. But okay, no, the blog post wants something that can be easily understood and audited. So just use age instead. It isn’t as small as this script, sure, but my guess is it’s not too hard to audit. And meanwhile, it’s actually secure and peer-reviewed (plus now you don’t have to maintain it).
Using this code is a gamble, and a bad one. This contest does nothing to demonstrate otherwise.
I doubt anyone will break it. Typically attacks on this sort of thing are going to consist of attacking the live system or surrounding code. IMO your live system is informal enough that attacks are probably impractical (it’s one human sending another human a blob, the other human decrypts it, and then tries to use it). The issues would be if this were ever put behind an API or if it evolved at all.
If I were to attack a system using this cryptography I’d start with things like:
Seeing how it handles modifications to the ciphertext. Extending values, etc. If it’s aes-256-gcm, could I collide the tag? I wouldn’t spend the time to do it, but I’d think about it at least.
I’d see if there’s something in that service that deserializes the value after.
But if all I have is a static blob to decrypt, yeah I doubt anyone is going to decrypt it. The fact that you’re using gcm now makes attacks far less practical, assuming you dropped the RSA encryption.
That said, people are still going to recommend you use the cryptographic primitives that maximize performance, maximize security properties and their strengths, and minimize footguns. Even without a break, people will recommend that, and they’ll be right.
Finally a comment I mostly agree with! It’s been frustrating, when I say “I use this thing for purpose x”, to get responses “It’s so very wrong to use this thing for y”. There is no service. The use-case is explained.
I still believe that both RSA and RSA+AES are equally secure for this use case. (The bounty provides ciphertext produced using both approaches.) Though I succumbed to the pressure to leave in only RSA+AES (and I like that it reduces my tiny script further), that may even be weaker: now either a weakness in RSA or a weakness in AES will make the result weak. As for all the advice to drop OpenSSL, that’s missing the point of the original post. It was about using tools we already have.
Probably because I’m a developer so I’ve actually been where you are lol. The reality is that a lot of cryptographers would see something like “2^32 keys before it’s unsafe” as ridiculously bad, but also for a use case where one value gets encrypted one time by a human… 2^32 will never be reached. But obviously 2^48 is better! But is 2^32 so horrific? No, obviously not.
age looks good, I didn’t know about it. As for choice of primitives, maybe AES-GCM would be better, or chacha20-poly1305 (is that what age uses?) Neither RSA nor AES-CBC is broken yet though, are they?
Neither RSA nor AES-CBC is broken yet though, are they?
RSA if correctly implemented is broken for smallish keys. RSA with large keys is still secure if used correctly, the problem with RSA is that it is really hard to use correctly (from key generation onwards). More modern cypher suites are designed without footguns. There’s no good reason to use RSA these days, it’s harder to use correctly and slower than a modern ecliptic curve cypher.
AES-CBC has similar problems. See the CBC Penguin. It’s very easy to use in a what that leaked all of the data.
As another poster pointed out, libsodium’s secret box is an easy-to-use wrapper around X25519 and XSalsa20-Poly1305, both of which avoid most of the pitfalls of RSA and AES-CBC, respectively. For resource-constrained environments, libhydrogen provides the same abstraction with Curve25519 and the Gimli permutation.
Rolling your own (and, yes, contrary to the title in the article, using the low-level libcrypto APIs is rolling your own) is almost certainly the wrong thing.
For other people, this is the most convincing post on “why not RSA” I read today https://blog.trailofbits.com/2019/07/08/fuck-rsa/, but even that one is a proof by intimidation (“don’t use it because i said so”), and not actually convincing (hard choice of parameters? sure, but the choice is in OpenSSL; is that one vulnerable? padding oracles? sure, but those depend on attacker being able to submit arbitrary ciphertext, that’s just not applicable here.)
It’s not about being broken as much as what they can do. aes-cbc does not provide authentication. I can modify the ciphertext as the attacker and it will still decrypt to some value. Now, I may not be able to predict that value (although maybe I can! If you reuse your IV things get real bad.), but that can lead to all sorts of fun things. Like maybe you encrypted JSON, then you decrypt it, then you parse it. If I can observe a parser failure, or details of that parser failure (difference between ‘expected {’ and ‘expected }’), etc, I can start leaking information about decryption.
Think of it like aes-cbc providing “attacker can’t read” and aes-gcm as “attacker can’t read/write”. You’re just removing an entire capability from the attacker (barring attacks on aes-gcm, sigh, but still).
RSA is fraught with footguns like padding attacks. OpenSSL will often not verify padding at all and just spit out garbled decrypted text when the ciphertext is messed with.
I can modify the ciphertext as the attacker and it will still decrypt to some value. Now, I may not be able to predict that value (although maybe I can! If you reuse your IV things get real bad.),
You can also freeform manipulate the IV and flip arbitrary bits on the first 16 characters of the plaintext, without having to worry about any padding.
This was fun to try, since it is in fact easy to fiddle with IV to get desired results (at least knowing the ciphertext). The goal was “attacker can’t read”, but since “attacker can’t read/write” is in fact better, and is almost free to get, I went ahead and made the change: https://github.com/gristlabs/secrets.js/pull/4/files
I just want to note that the inability to write also often closes read-loopholes. For example, if I modify the ciphertext without an authenticated encryption algorithm it means that I can maybe get you to leak information like “after decryption it failed to deserialize” or “it deserialized but the value was invalid” - two really important bits of info for me if I want to eventually read the data.
While I think it’s fun to build tools and break from general advice, things like this almost consistently have the same markers that makes me go “oh no”. Why use RSA over a ECC algorithm? Why is this not using an authenticated encryption algorithm over AES-CBC? I don’t feel like a lot of the choices are very well justified in the blog. In the end this just strikes me as a use case for age (which btw is very small 4k LoC with tests), over potentially becoming reliant on broken things.
Unauthenticated encryption is insecure. Full stop.
@dsagal, if you’re determined to write your own tool, please consider using one of the bindings to libsodium and its sealed box abstraction.
Also, please stop encrypting with RSA directly
I couldn’t believe the code went out of its way to have a path where it encrypted the plaintext with the public key.
Ugh. I promised myself that I wouldn’t dunk.
Tough crowd, but nice to have links that actually explain a problem that’s actually relevant. Thanks @quad! The unauthenticated encryption complaint was convincing. I verified that it is in fact possible to modify the output of the script (via its IV portion) to get the recipient to silently see different data. It’s not an attack mode (MITM) that we actually care about (other problems with that anyway), but it does seem silly not to use authenticated mode when it’s easy.
PR here: https://github.com/gristlabs/secrets.js/pull/4
I also dropped support for encrypting with RSA directly in the same PR. Disappointed that neither reason in the linked article applies for this use-case, so can’t verify anything that’s wrong with it, but I can’t justify keeping it anyway, since it raises questions and is only there because that’s what the script started with, i.e. historical reasons.
Those changes help a lot, things are much simpler and more robust with just that imo.
What attacks do you care about?
Good question, that’s what I start the article with, but don’t say it explicitly. I am assuming I can talk to my colleague freely, no MITM or impersonator involved. I am concerned that putting a secret in plain text would mean it’s present on systems outside of my knowledge or control (like Slack servers). So I am assuming the messages in the channel can leak, and I am concerned about confidentiality.
That strikes me as quite in-the-moment. A future leak of a private key or a spearfish would allow an attacker to get all your secrets quite conveniently from Slack.
Personally, I use https://magic-wormhole.readthedocs.io/ to ship sensitive material between machines.
A big reason to use symmetric crypto over asymmetric has always been performance.
Sops and age/gnupg.
why not sops+gpg or age or any other tool. You literally rolled your own
I’d highly recommend running your own instance of HashiCorp Vault (or OpenBao), or use 1Password or self-hosted Bitwarden if you’re pedantic.
Why would self-hosting be considered pendantry here?
lol, that’s what I get for being loose with punctuation. I meant self-host if you’re pedantic about using 1Pass.
I’m so confused. Why wouldn’t you just use a password safe?
previously
Doh I forgot this made its way to lobsters before :) Sorry for the duplication!
We use 1Password. We have to share a lot of secrets among us. 1Password is a great tool. Especially for storing SSH keys and acting like an ssh-agent.
We use Hashicorp Vault(well OpenBao now). Works great. Avoid doing it yourself.
“Write crypto code! Don’t publish it!”
[Comment removed by author]
Easy: Use 1password. It has added benefit of strongly encouraging good web password practices for your team. It is well trusted by my security professional friends, for whatever that’s worth, it’s enough for me to trust it.
Hard: use
ageand encrypt to the target’s public SSH key. If you trust GitHub (most seem to), you can easily look up and encrypt to someone’s public key given their GitHub email address: https://github.com/FiloSottile/age?tab=readme-ov-file#encrypting-to-a-github-userThe code and comments here are another example of why you shouldn’t roll your own crypto. You are not writing your own encryption algorithm, but still you and/or ChatGPT are not qualified to pick parameters and protocol construction from crypto primitives. If you want to build a tool around a well designed library, use libsodium or similar where the appropriate algorithms construction is encapsulated.
1Password and stop procrastinating whatever it is your startup should be doing.
Summary after a day of comments. Since many claim something is insecure, bad params, bad library, etc, (many unrelated to the use-case in question), I’ll put my money where my mouth is. Bounty for breaking my terrible choices: https://github.com/dsagal/plainopen-mill-blog/discussions/2#discussioncomment-12028272.
Cross-posting a GitHub comment I just left (I thought about just writing it on Lobsters, but I wanted people not on Lobsters to see the warning):
I doubt anyone will break it. Typically attacks on this sort of thing are going to consist of attacking the live system or surrounding code. IMO your live system is informal enough that attacks are probably impractical (it’s one human sending another human a blob, the other human decrypts it, and then tries to use it). The issues would be if this were ever put behind an API or if it evolved at all.
If I were to attack a system using this cryptography I’d start with things like:
Seeing how it handles modifications to the ciphertext. Extending values, etc. If it’s aes-256-gcm, could I collide the tag? I wouldn’t spend the time to do it, but I’d think about it at least.
I’d see if there’s something in that service that deserializes the value after.
But if all I have is a static blob to decrypt, yeah I doubt anyone is going to decrypt it. The fact that you’re using gcm now makes attacks far less practical, assuming you dropped the RSA encryption.
That said, people are still going to recommend you use the cryptographic primitives that maximize performance, maximize security properties and their strengths, and minimize footguns. Even without a break, people will recommend that, and they’ll be right.
Finally a comment I mostly agree with! It’s been frustrating, when I say “I use this thing for purpose x”, to get responses “It’s so very wrong to use this thing for y”. There is no service. The use-case is explained.
I still believe that both RSA and RSA+AES are equally secure for this use case. (The bounty provides ciphertext produced using both approaches.) Though I succumbed to the pressure to leave in only RSA+AES (and I like that it reduces my tiny script further), that may even be weaker: now either a weakness in RSA or a weakness in AES will make the result weak. As for all the advice to drop OpenSSL, that’s missing the point of the original post. It was about using tools we already have.
Probably because I’m a developer so I’ve actually been where you are lol. The reality is that a lot of cryptographers would see something like “2^32 keys before it’s unsafe” as ridiculously bad, but also for a use case where one value gets encrypted one time by a human… 2^32 will never be reached. But obviously 2^48 is better! But is 2^32 so horrific? No, obviously not.
idk it’s a whole thing.
So, to clarify for a n00b: Couldn’t this use case have been solved by the
gpgtool and the sharing of public keys? Or is there something I don’t get?Gpg is also considered weak by cryptographers: https://soatok.blog/2024/11/15/what-to-use-instead-of-pgp/
gpg is a pain in the ass, as commented on by the author in the article
One useful and relevant tool: https://magic-wormhole.readthedocs.io/en/latest/welcome.html#motivation
age looks good, I didn’t know about it. As for choice of primitives, maybe AES-GCM would be better, or chacha20-poly1305 (is that what age uses?) Neither RSA nor AES-CBC is broken yet though, are they?
RSA if correctly implemented is broken for smallish keys. RSA with large keys is still secure if used correctly, the problem with RSA is that it is really hard to use correctly (from key generation onwards). More modern cypher suites are designed without footguns. There’s no good reason to use RSA these days, it’s harder to use correctly and slower than a modern ecliptic curve cypher.
AES-CBC has similar problems. See the CBC Penguin. It’s very easy to use in a what that leaked all of the data.
As another poster pointed out, libsodium’s secret box is an easy-to-use wrapper around X25519 and XSalsa20-Poly1305, both of which avoid most of the pitfalls of RSA and AES-CBC, respectively. For resource-constrained environments, libhydrogen provides the same abstraction with Curve25519 and the Gimli permutation.
Rolling your own (and, yes, contrary to the title in the article, using the low-level libcrypto APIs is rolling your own) is almost certainly the wrong thing.
The penguin is ECB :-) They fucked up CBC by omitting any MAC, tho, and raw RSA is a mistake even for short messages.
See my response here https://lobste.rs/s/wfmynv/how_we_share_secrets_at_fully_remote#c_msmt6n (I got convinced to do AE and drop direct RSA). For my education, I am still interested in understanding what’s wrong with RSA for short messages.
For other people, this is the most convincing post on “why not RSA” I read today https://blog.trailofbits.com/2019/07/08/fuck-rsa/, but even that one is a proof by intimidation (“don’t use it because i said so”), and not actually convincing (hard choice of parameters? sure, but the choice is in OpenSSL; is that one vulnerable? padding oracles? sure, but those depend on attacker being able to submit arbitrary ciphertext, that’s just not applicable here.)
[Comment removed by author]
It’s not about being broken as much as what they can do.
aes-cbcdoes not provide authentication. I can modify the ciphertext as the attacker and it will still decrypt to some value. Now, I may not be able to predict that value (although maybe I can! If you reuse your IV things get real bad.), but that can lead to all sorts of fun things. Like maybe you encrypted JSON, then you decrypt it, then you parse it. If I can observe a parser failure, or details of that parser failure (difference between ‘expected {’ and ‘expected }’), etc, I can start leaking information about decryption.Think of it like aes-cbc providing “attacker can’t read” and aes-gcm as “attacker can’t read/write”. You’re just removing an entire capability from the attacker (barring attacks on aes-gcm, sigh, but still).
RSA is fraught with footguns like padding attacks. OpenSSL will often not verify padding at all and just spit out garbled decrypted text when the ciphertext is messed with.
You can also freeform manipulate the IV and flip arbitrary bits on the first 16 characters of the plaintext, without having to worry about any padding.
This was fun to try, since it is in fact easy to fiddle with IV to get desired results (at least knowing the ciphertext). The goal was “attacker can’t read”, but since “attacker can’t read/write” is in fact better, and is almost free to get, I went ahead and made the change: https://github.com/gristlabs/secrets.js/pull/4/files
I just want to note that the inability to write also often closes read-loopholes. For example, if I modify the ciphertext without an authenticated encryption algorithm it means that I can maybe get you to leak information like “after decryption it failed to deserialize” or “it deserialized but the value was invalid” - two really important bits of info for me if I want to eventually read the data.
Ah yes, true, even without IV reuse I own the first block either way.
If you avoid the footguns, RSA is not broken for large keys. The footguns pop up in unexpected places and can be a challenge to avoid, though.
That depends on what you call RSA. Textbook RSA is trivially breakable in itself. You need wrapping and authentication etc etc