Safe non-tamperable URL component in Perl using symmetric encryption?
Asked Answered
P

4

10

OK, I'm probably just having a bad Monday, but I have the following need and I'm seeing lots of partial solutions but I'm sure I'm not the first person to need this, so I'm wondering if I'm missing the obvious.

$client has 50 to 500 bytes worth of binary data that must be inserted into the middle of a URL and roundtrip to their customer's browser. Since it's part of the URL, we're up against the 1K "theoretical" limit of a GET URL. Also, $client doesn't want their customer decoding the data, or tampering with it without detection. $client would also prefer not to store anything server-side, so this must be completely standalone. Must be Perl code, and fast, in both encoding and decoding.

I think the last step can be base64. But what are the steps for encryption and hashing that make the most sense?

Prae answered 24/5, 2010 at 22:40 Comment(15)
You'll need to store something server-side. And I don't get what 'stand-alone' means in this case, how will it access the URL then?Winchester
If I'm understanding this, you need a way to encrypt 50-500 bytes of data in a URL without blowing over 1k of text?Raki
I have to go from $data to customer URL back to $data. Nothing will be stored server side.Prae
@Raki - yes, and also detect tampering, although the threat level is pretty low.Prae
If nothing is server side, why use a server? Also, is there any particular reason you can't POST the data?Hubie
Can't POST... ends up in <img src="..."> strings, for example.Prae
I presume we can at least store a key on the server or in the code?Raki
Sounds like you really want Seaside in Perl. :)Needleful
@brian d foy: Continuity perhaps? search.cpan.org/dist/ContinuityRedintegrate
No, Continuity isn't what I'm thinking about. The handles the continuity bit with a session ID, but I was thinking about generating unique and secure links for every link that goes out.Needleful
@brian d foy: By default Continuity uses a cookie but this can be changed (see Continuity::Mapper) so creating and using unique/secure links should be possible.Redintegrate
Yeah, but I can also create unique and secure links in another framework with the same amount of work. Continuity isn't special for that.Needleful
@brian d foy: Sounds like you don't really want Seaside in Perl then :)Redintegrate
You know, it was really just a throwaway line because Randal is a Smalltalk guy. I think you missed the joke. Seaside and Continuity have a minor overlap in features, but that doesn't mean Continuity is the small thing as Seaside.Needleful
@brian d foy: No I got the joke as I avidly follow Randal's Smalltalk blog and have also (on a few occasions) played with Seaside. All the smileys were in the correct place! BarryRedintegrate
T
5

I have some code in a Cat App that uses Crypt::Util to encode/decode a user's email address for an email verification link.

I set up a Crypt::Util model using Catalyst::Model::Adaptor with a secret key. Then in my Controller I have the following logic on the sending side:

my $cu = $c->model('CryptUtil');
my $token = $cu->encode_string_uri_base64( $cu->encode_string( $user->email ) );
my $url = $c->uri_for( $self->action_for('verify'), $token );

I send this link to the $user->email and when it is clicked on I use the following.

my $cu = $c->model('CryptUtil');
if ( my $id = $cu->decode_string( $cu->decode_string_uri_base64($token) ) ) {
    # handle valid link
} else { 
    # invalid link
}

This is basically what edanite just suggested in another answer. You'll just need to make sure whatever data you use to form the token with that the final $url doesn't exceed your arbitrary limit.

Tittivate answered 24/5, 2010 at 23:15 Comment(2)
FWIW this means that someone could capture a specific instance of this, can replay it (i.e. use it again). It's up to the OP, I suppose, to decide what sort of risk that poses.Winchester
As others have pointed out though you could trivially add a datetime/counter/"one time pad" to the message to check for that. Randall didn't explain his use case well enough to fully vet what he wanted, and he's a smart guy I'm sure he'll figure it out. ;)Tittivate
D
4

Create a secret key and store it on the server. If there are multiple servers and requests aren't guaranteed to come back to the same server; you'll need to use the same key on every server. This key should be rotated periodically.

If you encrypt the data in CBC (Cipher Block Chaining) mode (See the Crypt::CBC module), the overhead of encryption is at most two blocks (one for the IV and one for padding). 128 bit (i.e. 16 byte) blocks are common, but not universal. I recommend using AES (aka Rijndael) as the block cipher.

You need to authenticate the data to ensure it hasn't been modified. Depending on the security of the application, just hashing the message and including the hash in the plaintext that you encrypt may be good enough. This depends on attackers being unable to change the hash to match the message without knowing the symmetric encryption key. If you're using 128-bit keys for the cipher, use a 256-bit hash like SHA-256 (you can use the Digest module for this). You may also want to include some other things like a timestamp in the data to prevent the request from being repeated multiple times.

Diphyllous answered 24/5, 2010 at 23:9 Comment(3)
Is it necessary to authenticate the data? How can the client send modified data without knowing the key?Raki
@Schwern, Randal mentions in another comment that the client can provide some of the data included in the data to be encrypted. A clever attacker might be able to use that to alter the data without really knowing the key. At one point StackOverflow was vulnerable to this attack, Jeff and Joel discussed the details in one of the early podcasts.Doreendorelia
@Ven'Tatsu Do you have a link to those podcasts? I'll admit I'm no data security expert and would be interested to learn about this.Raki
R
3

I see three steps here. First, try compressing the data. With so little data bzip2 might save you maybe 5-20%. I'd throw in a guard to make sure it doesn't make the data larger. This step may not be worth while.

use Compress::Bzip2 qw(:utilities);
$data = memBzip $data;

You could also try reducing the length of any keys and values in the data manually. For example, first_name could be reduced to fname.

Second, encrypt it. Pick your favorite cipher and use Crypt::CBC. Here I use Rijndael because its good enough for the NSA. You'll want to do benchmarking to find the best balance between performance and security.

use Crypt::CBC;
my $key = "SUPER SEKRET";
my $cipher = Crypt::CBC->new($key, 'Rijndael');
my $encrypted_data = $cipher->encrypt($data);

You'll have to store the key on the server. Putting it in a protected file should be sufficient, securing that file is left as an exercise. When you say you can't store anything on the server I presume this doesn't include the key.

Finally, Base 64 encode it. I would use the modified URL-safe base 64 which uses - and _ instead of + and / saving you from having to spend space URL encoding these characters in the base 64 string. MIME::Base64::URLSafe covers that.

use MIME::Base64::URLSafe;
my $safe_data = urlsafe_b64encode($encrypted_data);

Then stick it onto the URL however you want. Reverse the process for reading it in.

You should be safe on size. Encrypting will increase the size of the data, but probably by less than 25%. Base 64 will increase the size of the data by a third (encoding as 2^6 instead of 2^8). This should leave encoding 500 bytes comfortably inside 1K.

Raki answered 24/5, 2010 at 23:30 Comment(2)
Uhh tamper protection? Sorry but if I know the message somehow I can replace it undetected with this scheme. The simplest tamper protection is to include in the encrypted container the hash of the message + secret nonce known only to the server.Rondel
How do you replace it without knowing the key? Or do you mean one could take a different encrypted piece of data and slot it in?Raki
E
-1

How secure does it need to be? Could you just xor the data with a long random string then add an MD5 hash of the whole lot with another secret salt to detect tampering?

I wouldn't use that for banking data, but it'd probably be fine for most web things...

big

Espousal answered 24/5, 2010 at 22:55 Comment(3)
Oddly enough, that's sorta what they're doing already, except that (a) the string is too short and (b) the customer can control some of the data, so known-plaintext attacks are possible. Since I'm breaking backward compatibility, I wanted to do something that wouldn't end up in thedailywtf.com, which the current code is clearly worthy of.Prae
Fair enough, I'm sitting here thinking up ways to "complexify" that approach, but if you want "proper" security you probably ought to be talking to someone who's a proper crypto geek. (I'm only just smart enough to know I'll inevitably get crypto wrong by myself...)Espousal
With CPAN it's easier to not "complexify" that approach and just use a CPAN module to encrypt the data using something known to be secure.Tittivate

© 2022 - 2024 — McMap. All rights reserved.