Skip to content

Instantly share code, notes, and snippets.

@wrayal
Forked from 140bytes/LICENSE.txt
Created June 16, 2011 12:03
Show Gist options
  • Save wrayal/1029102 to your computer and use it in GitHub Desktop.
Save wrayal/1029102 to your computer and use it in GitHub Desktop.
Vigenère cypher

The Vigenère Cypher

In 138b, this will encrypt/decrypt anything using the Vigenère cypher. Input is entered as solely lower case letters (no numbers, spaces, special characters etc). Outputs may be verified against http://sharkysoft.com/misc/vigenere/

Background

As described on http://ub.ly/um (the parser chokes on the full URL), the essence of the Vigenère cypher is that it is a cyclic version of the Caesar shift cypher. It's not unbreakable but it's pretty good. Also, as a corollary we get rot13 (use "n" as an encoding parameter - yes, as originally defined there's a one byte offset from what you might expect...) and of course Caesar.

Entry History

My initial attempts were encryption only (138b):

function(j,s,h,g,i,n){for(g="",i=0;i-j.length;g+=String.fromCharCode(n?n+64:90))n=(j.charCodeAt(i)+s.charCodeAt(i++%s.length)+15)%26;h(g)}

and encryption + decryption (151b):

function(j,s,h,t,g,i,n){for(g="",i=0;i-j.length;g+=String.fromCharCode(n?n+64:90))n=(j.charCodeAt(i)+t*s.charCodeAt(i++%s.length)+(t>0?15:27))%26;h(g)}

with t=+/- 1 being encryption/decryption. Thanks to all the help in the comments (especially @subzey!) this is now down to 138b, with both encryption and decryption.

What I learnt

The phrases "fromCharCode", "charCodeAt" and "String" are FAR too long =(

Savings don't always come where you expect them to (see @jed's ultimately unused 'harCode' hack in the comments). In fact, from my experience so far in 140byt.es, they most often come from some slight (or indeed major!) reworking of the algorithm (c.f. the successive contributions to fibonacci, base64 enc/dec etc).

http://esparser.qfox.nl/ is amazing

function(
a //input string
,b //key string
,c //callback function
,d //direction (1=encrypt, -1=decrypt)
,e //output string
,f //iterator
){
for(
f=e=""; //initialize; f will be coerced to a numerical value
f<a.length; //loop for all character in string
e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+27+~d*6)%26)
//very neat trick to make the fact that 26%26=0 work for you rather than against you - see @subzey's description =)
//note ~d=-2 for d=1, 0 for d=-1; and 27-15=12 (these are the magic unicode offsets =)
)
;
c(e) //and we're done! send back...
}
function(a,b,c,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+27+~d*6)%26));c(e)}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 YOUR_NAME_HERE <YOUR_URL_HERE>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "vigenereCypher",
"description": "This will cypher using the vigenere method",
"keywords": [
"viegnere",
"cypher",
"encryption"
]
}
<!DOCTYPE html>
<title>Vigenère encryption</title>
<pre>
Expected value: <b>HUMZWBGVMVOAORMWILRNVQESBESOSEGHMRSCELNGHQA</b>
Actual value: <b id="ret"></b>
</pre>
<script>
function b(text)
{
document.getElementById( "ret" ).innerHTML=text
}
var myFunction=function(a,b,c,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+27+~d*6)%26));c(e)}
myFunction("thiscodeisonlyforstandardlowercaseplaintext","onehundredandthirtyninebytes",b,1)
//to test decryption:
//myFunction("humzwbgvmvoaormwilrnvqesbesoseghmrscelnghqa","onehundredandthirtyninebytes",b,-1)
</script>
</html>
@wrayal
Copy link
Author

wrayal commented Jun 16, 2011

I fiddled with doing this with .replace() but it seems to inevitably come out longer, e.g. 142 encryption only

function(j,s,h,n){h(j.replace(/./g,function (a,i){n=(a.charCodeAt(0)+s.charCodeAt(i%s.length)+15)%26;return String.fromCharCode(n?n+64:90)}))}

There must be a better method than charCodeAt() surely? Also, if taking liberties with the callback function, it can become 136b enc+dec:

function b(text)
{
    document.getElementById( "ret" ).innerHTML=String.fromCharCode.apply(this,text.split(","))
}
function(j,s,h,t,g,i,n){for(g="",i=0;i-j.length;g+=(n?n+64:90)+",")n=(j.charCodeAt(i)+t*s.charCodeAt(i++%s.length)+(t>0?15:27))%26;h(g)}

@jed
Copy link

jed commented Jun 16, 2011

how about replacing

g="",i=0

with

g=i=""

?

also, seems like you should get some savings from caching charCodeAt.

@mikesherov
Copy link

Some thoughts right off the bat:

  1. instead of String.fromCharCode, can't you do g.fromCharCode?
  2. store "charCodeAt" in a string, and use bracket notation to call it. i.e:
    x="charCodeAt"; s[x](i);

@mikesherov
Copy link

another thought, use alphabetized parameters, instead of j,s,h,g,i,n, use `a,b,c,d,e,f``... makes it easier for others to see what gets used where.

@wrayal
Copy link
Author

wrayal commented Jun 16, 2011

@jed: Thanks, I have no idea how I missed that!

Good call on the caching; my first count suggested I'd actually lose bytes (as the declaration plus semicolon is actually quite long and fromCharCode is only used twice). In fact, it does save a single byte, unless I find a better place to shave that semi colon!

[Edit]Oh hmm on second thoughts I need another two bytes for the x="charCodeAt" trick to avoid polluting the global scope - I do indeed lose 1 byte!

@mikesherov: are you sure your first suggestion works? It errors on Chrome for me.

I'll update the letters; originally they had meaning (e.g. j would have been 'i' for input, except that i was already used for iterating etc).

149b:

function(a,b,c,d,e,f,g){for(f=e="";f<a.length;e+=String.fromCharCode(g?g+64:90))g=(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+(d>0?15:27))%26;c(e)}

@jed
Copy link

jed commented Jun 16, 2011

how about

64+n||26

instead of

n?n+64:90

?

@mikesherov
Copy link

ignore my fromCharCode... it must always be called statically, which sucks.

@wrayal
Copy link
Author

wrayal commented Jun 16, 2011

@jed Again, I tried that but there's a precedence problem - the '||' trick doesn't work as 64+n is truthy even for n=0 (at least I guess this is how it's being interpreted). Similarly, we can't swap it around to

n||26+64

As this returns just n for n!=0. What would work is

(n||26)+64

but this is actually longer unfortunately.

@mikesherov Np, it's annoying isn't it? =/

@jed
Copy link

jed commented Jun 16, 2011

gah. sorry for suggesting that! i have a feeling that @subzey and the other heavy-lifters could get this down.

@wrayal
Copy link
Author

wrayal commented Jun 16, 2011

@jed Np, I figure it probably needs a slightly different technique (e.g. .replace(), except perhaps more fruitfully than I've managed!); let's see if the others weigh in ^_^ It's just annoying when so many characters go on long unique strings (e.g. String.fromCharCode is just deathly to this kind of script...)

@jed
Copy link

jed commented Jun 16, 2011

indeed. seems like harCode could be factored out since it occurs three times. also, i'll bet the solution rolls the two charCodeAts into a loop.

@mikesherov
Copy link

the harCode refactor is an awesome hack. Although, should save at least a few bytes.

@subzey
Copy link

subzey commented Jun 17, 2011

I'm not really sure what this code does, but we can use a little trick:
var myFunction=function(a,b,c,d,e,f,g){for(f=e="";f<a.length;e+=String.fromCharCode(g?g+64:90))g=(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+(d>0?15:27))%26;c(e)}
to
var myFunction=function(a,b,c,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+(d>0?15:1))%26));c(e)}

This how it works:
25 * 0 % 26 == 0,
25 * 1 % 26 == 25,
25 * 2 % 26 == 24,
...
25*25 % 26 == 1
25*26 % 26 == 0
25*27 % 26 == 25
...
Please note that this trick limits 4th argument to be not more than approx. 1e+12. Hope, this is much enough.

Also, d>0?15:27 is changed to d>0?15:1. Anyway, it is %'ed :)

@jed
Copy link

jed commented Jun 17, 2011

wow, good work @subzey! so who's gonna golf the last byte?

@subzey
Copy link

subzey commented Jun 17, 2011

Sorry for 27→1 change. It caused underflow and incorrect decryption.
Fixed:
function(a,b,c,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+(27+~d*6))%26));c(e)}

@wrayal
Copy link
Author

wrayal commented Jun 17, 2011

@subzey: Nice; I tried playing with some way to land things in the correct place without an explicit zero-check but never hit upon this. BTW, the code is just a simple susbstitution-type encryption. The 27 -> 1 mistake is easy to make - I tried it the first time as well.

It's probably worth keeping note of this trick - I guess any sort of vaguely similar encryption mechanism might be amenable to it!

Congrats on bringing it to 140 :) That last trick is the kind of thing I will never ever learn to spot!

By the way, to test decryption one can try

myFunction("humzwbgvmvoaormwilrnvqesbesoseghmrscelnghqa","onehundredandthirtyninebytes",b,-1)

@atk
Copy link

atk commented Jun 17, 2011

It seems the hint from @jed to coerce i from '' was still missing.

@wrayal
Copy link
Author

wrayal commented Jun 17, 2011

Yeah, the code at the top doesn't include any of the suggestions from the commenters - I feel a tad disingenuous modifying it as none of the improvements were mine! What's protocol here? Subzey's last comment is the definitive version

(incidentally a JS vignere cypher was the first piece of script I ever wrote as a kid - it's amazing to see what that original monster has been condensed down to!)

@atk
Copy link

atk commented Jun 17, 2011

Those suggestions were given to you to incorporate them in your code. Were a suggestion mine, I'd rather be a bit disappointed if you didn't include it. The goal is the smallest possible function, so use whatever you find. Since all revisions and comments are preserved, it's ok to do so.

@mikesherov
Copy link

@wrayal, yeah, don't worry too much about it! Use all the suggestions. That's the spirit of WTFPL. You just DO WHAT THE FUCK YOU WANT TO.

@wrayal
Copy link
Author

wrayal commented Jun 17, 2011

Wise points; updated accordingly. One last potential bit of golf; thanks to @subzey's neat ~d trick, isn't the set of brackets enclosing that bit redundant? i.e.138b: function(a,b,c,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+27+~d*6)%26));c(e)}

@atk
Copy link

atk commented Jun 17, 2011

Btw.: I like the idea to omit "return" in favor of a callback. Much more elegant than outsourcing stuff like "String.fromCharCode" like I did in my first try of the base64 decoder.

@michaelficarra
Copy link

@wrayal: The name's spelled wrong in the README. It's "Vigenère cipher", not "vignere cypher".

@neizod
Copy link

neizod commented Oct 5, 2011

"Cypher" also wrong. Use "cipher".

@tsaniel
Copy link

tsaniel commented Jan 31, 2012

I believe there is enough room to return:
function(a,b,d,e,f){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a.charCodeAt(f)+d*b.charCodeAt(f++%b.length)+27+~d*6)%26));return e}

@atk
Copy link

atk commented Jan 31, 2012

Save another byte:

function(a,b,d,e,f,g){for(f=e="";f<a.length;e+=String.fromCharCode(90-25*(a[g='charCodeAt'](f)+d*b[g](f++%b.length)+27+~d*6)%26));return e}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment