Implement Oplop

5

0

Oplop is an algorithm to generate website specific passwords based on a master password and a keyword.

It is described here: http://code.google.com/p/oplop/wiki/HowItWorks

There's an online implementation here: https://oplop.appspot.com/

My attempt in Q is 204 200 167 165 139 characters ignoring whitespace:

{d:.Q.A,.Q.a,(n:"0123456789"),"-_";
b:d 2 sv'6 cut(,/)0b vs'(md5 x,y),2#0x00;
i:b in n;j:i?1b;k:(j _i)?0b;
8#$[0=0+/i;"1";8>j;();b j+(!)k],b}

EDIT: I achieved significant character savings by removing some redundant code from my Hex->Base64 conversion.

2012.03.07 - Cut out 2 more characters

2012.03.11 - Removed duplication and got accurate char count

2012.03.20 - Down to 141

Criteria

Candidate functions/implementations should take two strings as arguments and return an 8 character password as defined in the algorithm.

Judging

Code golf so shortest code takes the prize.

skeevey

Posted 2012-02-28T00:11:04.180

Reputation: 4 139

When will the judging be done? – Mark Thomas – 2012-03-21T12:22:21.770

I'll give it until the end of the month. 2012-04-01 00:00:00.000 – skeevey – 2012-03-21T13:16:18.420

1Imho, the problem description should be complete in that way, that an average programmer knows what to do. So you should name the steps to generate the code, while it is to the user to find out how to md5sum or to base64encode something. A website for further details is welcome, of course. Preparing a challenge on meta or in chat is - btw. - recommended. – user unknown – 2012-03-28T20:46:18.297

Answers

3

Ruby (113 110 chars)

require 'digest'
b=Digest::MD5.base64digest(ARGV.join)
puts "#{b} 1".sub(/(^\D{8,}(\d*))/,'\2\1')[0..7].tr'+/','-_'

require'digest'
b=Digest::MD5.base64digest(ARGV.join)
puts"#{b} 1".sub(/(^\D{8,}(\d*))/,'\2\1')[0,8].tr'+/','-_'

I notice that all the non-Python languages have to do a translation of the + and / to be - and _, respectively. It seems that Python's base64 is different/wrong?

Mark Thomas

Posted 2012-02-28T00:11:04.180

Reputation: 305

2

There are several variants for different applications where different characters need escaping. Note that the Python method used is called urlsafe_b64encode

– Peter Taylor – 2012-03-08T11:10:04.780

5

Python - 198 166 152 150

import base64,md5,sys,re
b=base64.urlsafe_b64encode(md5.md5(sys.argv[1]+sys.argv[2]).digest())
r=re.findall('\d+',b)if all(not c.isdigit()for c in b[:8])else['']
print((r[0]if len(r)else'1')+b)[:8]

import base64,md5,sys,re
b=base64.urlsafe_b64encode(md5.md5("".join(sys.argv[1:])).digest())
print((''if re.search('\d',b[:8])else re.findall('\d+',b+'1')[0])+b)[:8]

import base64,md5,sys,re
b=base64.urlsafe_b64encode(md5.md5("".join(sys.argv[1:])).digest())
print(re.findall('(?:[^\d]{8,}(\d*)|)',b+' 1')[0]+b)[:8]

This was a fun one. Could be a lot shorter if not for the verbosity of the md5 and base64 modules. The third and fourth lines are the interesting ones. My favourite trick was using a list containing only an empty string to avoid having to use any real if statements.

EDIT: Had to change from checking c.isalpha() to not c.isdigit() (added 4 characters)

EDIT: Shaved off 32 characters. Many thanks to Ugoren!

EDIT: Shaved off 14 more chars again many thanks to Ugoren. Used some regex trickiness

print(re.findall('(?:[^\d]{8,}(\d*)|)',b+' 1')[0]+b)[:8]

instead of

print((re.findall('^[^\d]{8,}(\d*)',b+' 1')+[''])[0]+b)[:8]

To save three chars.

Gordon Bailey

Posted 2012-02-28T00:11:04.180

Reputation: 708

This seems to give incorrect values for some input eg. (a,aaaa) – skeevey – 2012-02-28T02:09:15.480

Oh shoot, I know why. I'll update in a second, thanks for catching that. – Gordon Bailey – 2012-02-28T02:11:10.160

Small improvements: "".join(sys.argv[1:3]) (or even [1:]), if r can replace if len(r) (I think), re.match('^[^\d]{8}',b). – ugoren – 2012-02-28T13:46:57.047

Also - r=re.findall('\d+',b+' 1') allows you to remove the if-else later (and then you don't need r). And reversing if-else on line 3 allows using re.match('\d',b[:8]) instead of my above suggestion. – ugoren – 2012-02-28T14:16:19.357

Nice tips, shaved off 32 characters, thanks! – Gordon Bailey – 2012-02-28T15:44:14.933

Save a few more characters: print((re.findall('[^\d]{8,}(\d*)',b+' 1')+[''])[0]+b)[:8]. It's maybe possible to replace +[''] with some RE stuff. Also, b+'1' is a bug (if b ends with a digit). – ugoren – 2012-02-28T19:40:21.220

Thanks a second time, you're a regex master. I did manage to shave off a single character from your expression though. – Gordon Bailey – 2012-02-28T21:13:25.743

Very nice. I'm not sure how it works exactly - when there are no digits in b, findall returns ['', '', '1', ''], which ends up OK. But you can drop () around findall to round the score down. – ugoren – 2012-02-29T15:00:23.097

Oh weird, I didn't notice that (I was always testing with the [0] I guess). Although for me it just returns ['1','']. Wouldn't it break if it returned what you said there? – Gordon Bailey – 2012-02-29T15:40:39.257

5

Shell - 116

echo -n $1$2|md5sum|xxd -r -p|base64|sed -re'y!+/!-_!;/^.{0,7}[0-9]/!{s/^([^0-9]*)([0-9]+)/\2\1/;t;s/^/1/}'|cut -c-8

Depends on cut, md5sum and base64 from coreutils, xxd and GNU sed.

Hasturkun

Posted 2012-02-28T00:11:04.180

Reputation: 1 206

2

PHP (126)

function f($m,$n){return substr(preg_replace('/^\D{8,}(\d+)/','$1$0',strtr(base64_encode(md5($m.$n,1)),'+/','-_').' 1'),0,8);}

Beautified:

function oplop( $master, $nick ) {
    return substr( preg_replace(
        '/^\D{8,}(\d+)/', '$1$0',
        strtr( base64_encode( md5( $master . $nick, true ) ), '+/', '-_' ) . ' 1'
    ), 0, 8 );
}

PleaseStand

Posted 2012-02-28T00:11:04.180

Reputation: 5 369