It's like "No, you don't need to write a configuration parser, you can just serialize an object" which is a well travelled road straight to Dante's seventh circle.
It's like "No, you don't need to write a configuration parser, you can just serialize an object" which is a well travelled road straight to Dante's seventh circle.
Sure, yes, if you know what you're doing and have a valid reasons for going off the beaten path.
In this case I was trying to explain to a developer why their token was horrifically insecure despite superficial appearances.
This was an off-the-cuff sort of thing: I was simply scrolling through some code talking about something else and I saw "encrypt" in the middle of it... and sigh...
I talked for well over an hour just listing the security vulnerabilities in just three lines of code.
"Static keys used in encryption can be cracked by obtaining many ciphertext samples. You actually wanted keyed signing, not encryption."
"The scope isn't included and you're sharing the same key everywhere. That means dev/tst/prd tokens are interchangeable."
"Also... let's just keyword search for this 'random' (not) key in the Git repos... and oh... ohh... it's everywhere. Oh god. Unrelated apps too!"
"There's no dates in there. That means tokens last forever."
"The name is separated from the role with an ASCII symbol that's a valid user-name character. So if I call myself Bobby Tables#Admin, then I am. Fun!"
"Tokens last for a long time and can only be encrypted and decrypted with a single key, which means you can't rotate keys without logging out all users all at once."
Etc...
Hence losing my voice for a bit.
Also, a little bit of my sanity.
They can? My understanding was that with a well-designed symmetric algorithm and a key with enough entropy this shouldn't be possible.
This means that if the same key is reused, and the plaintext is predictable, then you'll see patterns in the output. Those patterns can be used to figure out the key. Heck, you don't even need the key! Just register a bunch of distinct user names (or whatever you can control), and observe the change in the encrypted token. Eventually you can collect enough data to generate arbitrary tokens, or at least tokens with some useful values under your control. You can also exploit different error response codes, which is a common design fault because "decryption failure", "parser failure", and "access denied" tend to go through different code-paths and throw different exception types.
The solution is to mix in a block of random bits to break this determinism. This is the initialization vector (IV). The developers -- of course -- failed to do this and used a constant.
Even that is insufficient, because most encryption algorithms provide only confidentiality. They don't provide authenticity ("signing"), which is more important for tokens.
(An aside: authenticated encryption algorithms are starting to get popular, and these provide both at once efficiently.)
Essentially, it doesn't matter how "secure" an algorithm is, it won't achieve security if it is misused and applied for the wrong purpose.
Oh, at that point you're not even using the algorithm anymore. Why is this even possible in the library? I would've assumed that any sensible implementation would handle the initialization vector for you, and manually setting it up would require very verbose explicit configuration.