=^.^=

Implementing One-Time Pads in JavaScript

I have always been fond of the One-Time Pad. There are a lot of paranoid people who think admins have nothing better to do than sit around reading private messages all day and I thought it would be neat (even if never used) to implement client-side OTP encryption. Not having the time to do this kind of thing myself I asked Tucker, a programmer who helps moderate Yiffy International if he would like to contribute the code. Fortunately for us he did, and he is graciously allowing me to re-post it here for your pleasure.

It should be noted that using the built in pad generator is the least secure method since it is easy to predict that pads generated with it used a browser's RNG.

The particularly clever thing about this implementation is that special characters, numbers and spaces - even line breaks - are preserved through a mechanism we call "Z-encoding."

Thanks Tucker, that's super cool!

Here is the JavaScript element:

function format (string) {
  var retval = string.charAt (0);
  for (var i = 1; i < string.length; i ++) {
    if ((i % 48) == 0) retval += "\n";
    else if ((i % 6) == 0) retval += " ";
    retval += string.charAt (i);
  }
  return retval;
}

function unformat (string) {
  string = string.toUpperCase ();
  var retval = "";
  for (var i = 0; i < string.length; i ++) {
    if ((string.charCodeAt (i) >= 65) && (string.charCodeAt (i) <= 90)) {
      retval += string.charAt (i);
    }
  }
  return retval;
}

function checkformat (string) {
  for (var i = 0; i < string.length; i ++) {
    if ((string.charCodeAt (i) < 65) || (string.charCodeAt (i) > 90)) {
      return true;
    }
  }
  return false;
}

function inttoletters (number) {
  var bigbyte = Math.floor (number / 16);
  var smallbyte = number - (bigbyte * 16);
  return String.fromCharCode (65 + bigbyte, 65 + smallbyte);
}

function letterstoint (letter1, letter2) {
  var bigbyte = letter1 - 65;
  var smallbyte = letter2 - 65;
  return String.fromCharCode ((16 * bigbyte) + smallbyte);
}

function process (source) {
  source = source.toUpperCase ();
  var retval = "";
  for (var i = 0; i < source.length; i ++) {
    if ((source.charCodeAt (i) >= 65) && (source.charCodeAt (i) < 90)) {
      retval += source.charAt (i);
    } else if (source.charCodeAt (i) == 90) {
      retval += "ZZ";
    } else {
      retval += "Z" + inttoletters (source.charCodeAt (i));
    }
  }
  return retval;
}

function unprocess (source) {
  var retval = "";if (checkformat (source)) {
    dispmsg ("Error: Invalid characters in source string.");
    return "";
  } else {
    for (var i = 0; i < source.length; i ++) {
      if ((source.charCodeAt (i) >= 65) && (source.charCodeAt (i) < 90)) {
        retval += source.charAt (i);
      } else if (source.charCodeAt (i) == 90) {
        i ++;
        if (source.charCodeAt (i) == 90) {
          retval += source.charAt (i);
        } else {
          retval += letterstoint (source.charCodeAt (i), source.charCodeAt (i + 1));
          i ++;
        }
      }
    }
    return retval.toLowerCase ();
  }
}

function genprs (source) {
  var retval = "";
  if (checkformat (source)) {
    dispmsg ("Error: Invalid characters in source string.");
    return "";
  } else {
    for (var i = 0; i < source.length; i ++) {
      retval += String.fromCharCode (65 + Math.floor (Math.random () * 26));
    }
    dispmsg ("Ready.");
    return retval;
  }
}

function encrypt (source, pad) {
  var retval = "";
  var newchar = 0;
  if (checkformat (source)) {
    dispmsg ("Error: Invalid characters in source string.");
    return "";
  } else if (source.length > pad.length) {
    dispmsg ("Error: Too many characters in source string.");
    return "";
  } else {
    while (pad.length > source.length) source += String.fromCharCode (65 + Math.floor (Math.random () * 25));
    for (var i = 0; i < source.length; i ++) {
      newchar = source.charCodeAt (i) + pad.charCodeAt (i) - 65;
      if (newchar > 90) newchar -= 26;
      retval += String.fromCharCode (newchar);
    }
    dispmsg ("Ready.");
    return retval;
  }
}

function decrypt (source, pad) {
  var retval = "";
  var newchar = 0;
  if (source.length > pad.length) {
    dispmsg ("Error: Too many characters in source string.");
    return "";
  } else {
    for (var i = 0; i < source.length; i ++) {
      newchar = source.charCodeAt (i) - pad.charCodeAt (i) + 65;
      if (newchar < 65) newchar += 26;
      retval += String.fromCharCode (newchar);
    }
    dispmsg ("Ready.");
    return retval;
  }
}

function fullencrypt (source, pad) {
  return format (encrypt (process (source), unformat (pad)));
}

function fulldecrypt (source, pad) {
  return unprocess (decrypt (unformat (source), unformat (pad)));
}

function html_generate () {
  document.getElementById ("pad").value = format (genprs (process (document.getElementById ("sender").value)));
}

function html_format () {
  document.getElementById ("pad").value = format (unformat (document.getElementById ("pad").value));
}

function html_copypad () {
  document.getElementById ("pastepad").value = document.getElementById ("pad").value;
  document.getElementById ("reciever").value = document.getElementById ("sender").value;
}

function html_encrypt () {
  document.getElementById ("sender").value = fullencrypt (document.getElementById ("sender").value, document.getElementById ("pad").value);
}

function html_decrypt () {
  document.getElementById ("reciever").value = fulldecrypt (document.getElementById ("reciever").value, document.getElementById ("pastepad").value);
}

function dispmsg (msgcode) {
  document.getElementById ("message").innerHTML = msgcode;
}

Here is a demo HTML form:

<html>
<head>
<title>Encryption Demo</title>
<style>textarea { resize: none !important; } #message { color: red; }</style>
<script type="text/javascript" src="encrypt.js"></script>
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<table><tr>
<td>Sender: <input onclick="html_encrypt()" type="button" value="Encrypt" /></td>
<td>One Time Pad: <input onclick="html_generate()" type="button" value="Generate" /> <input onclick="html_format()" type="button" value="Format" /></td>
</tr><tr>
<td><textarea id="sender" cols="80" rows="20"></textarea></td>
<td><textarea id="pad" cols="80" rows="20"></textarea></td>
</tr><tr>
<td>Reciever: <input onclick="html_decrypt()" type="button" value="Decrypt" /></td>
<td>Paste One Time Pad: <input onclick="html_copypad()" type="button" value="Copy Pad" /></td>
</tr><tr>
<td><textarea id="reciever" readonly="readonly" cols="80" rows="20"></textarea></td>
<td><textarea id="pastepad" readonly="readonly" cols="80" rows="20"></textarea></td>
</tr></table>
<span id="message">Ready.</span>
</body>
</html>

You can read more about our implementation at: http://forum.yiffy.tk/viewtopic.php?f=2&t=3712.

Comments

Woe Too

Are not firefox extensions mostly javascript? Wouldn't you love to create an OPT firefox extension?

(userscripts can be fed into online widgets to crank out extensions)

• Anon

Wow, this looks pretty intense ! I think I'll have a play around with it for some when I have the spare time.