Silk Road forums

Discussion => Security => Topic started by: raven92 on July 11, 2012, 09:08 pm

Title: PrivNote Security Monitor & Key Strength Update - GreaseMonkey Script
Post by: raven92 on July 11, 2012, 09:08 pm
This is NOT an endorsement to use PrivNote, I recommend using GPG over using PrivNote. This script is a bandaid on semi-weak security, but if your not going to use GPG and insist on PrivNote, this is a must.

Join Pine At The PGP Club ->
http://dkn255hz262ypmii.onion/index.php?topic=30938

If Your Stupid Ass Insists On PrivNote ->

With this script PrivNote's key generation is patched to acceptable security (Without the script PrivNote keys are weak, and breakable by remote parties).

This update also calcualtes SHA-1 hash's for the script and HTML, if the Hash is ok the text will read "Write your note below (Hash OK, Key Enhanced)", otherwise it will be "WARNING!! PRIVNOTE HAS BEEN MODIFIED!!" and you will receive message box letting you know hash details.

The script will not auto-update for security reasons, the SHA-1 hash is below, and i'd recommend waiting until another member verifies the work before using if you aren't comfortable with JScript yourself.

Code: [Select]
// ==UserScript==
// @id             PrivNote Randomizer Fix And Monitor
// @name           PrivNote Randomizer Fix And Monitor
// @version        1.0
// @namespace      PrivNoteFix
// @author         
// @description   
// @include        https://privnote.com/*
// @run-at         document-end
// ==/UserScript==

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/*  SHA-1 implementation in JavaScript       */
/*   - see http://csrc.nist.gov/groups/ST/toolkit/secure_hashing.html                             */
/*         http://csrc.nist.gov/groups/ST/toolkit/examples.html                                   */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

var Sha1 = {};  // Sha1 namespace

/**
 * Generates SHA-1 hash of string
 *
 * @param {String} msg                String to be hashed
 * @param {Boolean} [utf8encode=true] Encode msg as UTF-8 before generating hash
 * @returns {String}                  Hash of msg as hex character string
 */
Sha1.hash = function(msg, utf8encode) {
  utf8encode =  (typeof utf8encode == 'undefined') ? false : utf8encode;
 
  // convert string to UTF-8, as SHA only deals with byte-streams
  if (utf8encode) msg = Utf8.encode(msg);
 
  // constants [§4.2.1]
  var K = [0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6];
 
  // PREPROCESSING
 
  msg += String.fromCharCode(0x80);  // add trailing '1' bit (+ 0's padding) to string [§5.1.1]
 
  // convert string msg into 512-bit/16-integer blocks arrays of ints [§5.2.1]
  var l = msg.length/4 + 2;  // length (in 32-bit integers) of msg + ‘1’ + appended length
  var N = Math.ceil(l/16);   // number of 16-integer-blocks required to hold 'l' ints
  var M = new Array(N);
 
  for (var i=0; i<N; i++) {
    M[i] = new Array(16);
    for (var j=0; j<16; j++) {  // encode 4 chars per integer, big-endian encoding
      M[i][j] = (msg.charCodeAt(i*64+j*4)<<24) | (msg.charCodeAt(i*64+j*4+1)<<16) |
        (msg.charCodeAt(i*64+j*4+2)<<8) | (msg.charCodeAt(i*64+j*4+3));
    } // note running off the end of msg is ok 'cos bitwise ops on NaN return 0
  }
  // add length (in bits) into final pair of 32-bit integers (big-endian) [§5.1.1]
  // note: most significant word would be (len-1)*8 >>> 32, but since JS converts
  // bitwise-op args to 32 bits, we need to simulate this by arithmetic operators
  M[N-1][14] = ((msg.length-1)*8) / Math.pow(2, 32); M[N-1][14] = Math.floor(M[N-1][14])
  M[N-1][15] = ((msg.length-1)*8) & 0xffffffff;
 
  // set initial hash value [§5.3.1]
  var H0 = 0x67452301;
  var H1 = 0xefcdab89;
  var H2 = 0x98badcfe;
  var H3 = 0x10325476;
  var H4 = 0xc3d2e1f0;
 
  // HASH COMPUTATION [§6.1.2]
 
  var W = new Array(80); var a, b, c, d, e;
  for (var i=0; i<N; i++) {
 
    // 1 - prepare message schedule 'W'
    for (var t=0;  t<16; t++) W[t] = M[i][t];
    for (var t=16; t<80; t++) W[t] = Sha1.ROTL(W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16], 1);
   
    // 2 - initialise five working variables a, b, c, d, e with previous hash value
    a = H0; b = H1; c = H2; d = H3; e = H4;
   
    // 3 - main loop
    for (var t=0; t<80; t++) {
      var s = Math.floor(t/20); // seq for blocks of 'f' functions and 'K' constants
      var T = (Sha1.ROTL(a,5) + Sha1.f(s,b,c,d) + e + K[s] + W[t]) & 0xffffffff;
      e = d; d = c;
      c = Sha1.ROTL(b, 30);
      b = a; a = T;
    }
   
    // 4 - compute the new intermediate hash value
    H0 = (H0+a) & 0xffffffff;  H1 = (H1+b) & 0xffffffff;     H2 = (H2+c) & 0xffffffff;  H3 = (H3+d) & 0xffffffff;     H4 = (H4+e) & 0xffffffff;
  }

  return Sha1.toHexStr(H0) + Sha1.toHexStr(H1) +
    Sha1.toHexStr(H2) + Sha1.toHexStr(H3) + Sha1.toHexStr(H4);
}

//
// function 'f' [§4.1.1]
//
Sha1.f = function(s, x, y, z)  {
  switch (s) {
  case 0: return (x & y) ^ (~x & z);           // Ch()
  case 1: return x ^ y ^ z;                    // Parity()
  case 2: return (x & y) ^ (x & z) ^ (y & z);  // Maj()
  case 3: return x ^ y ^ z;                    // Parity()
  }
}

//
// rotate left (circular left shift) value x by n positions [§3.2.5]
//
Sha1.ROTL = function(x, n) {
  return (x<<n) | (x>>>(32-n));
}

//
// hexadecimal representation of a number
//   (note toString(16) is implementation-dependant, and 
//   in IE returns signed numbers when used on full words)
//
Sha1.toHexStr = function(n) {
  var s="", v;
  for (var i=7; i>=0; i--) { v = (n>>>(i*4)) & 0xf; s += v.toString(16); }
  return s;
}

unsafeWindow.Sha1 = Sha1;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

unsafeWindow.random_seeder = 2345234561236161;
unsafeWindow.random_str = "abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ!@$%^*";


lineDenote = '\n';
unsafeWindow.pendingDocs = 0;
unsafeWindow.totalDocs = 0;
unsafeWindow.docSrc = {};
unsafeWindow.currentHash = '';

// check if we are creating or reading notes, reading notes the body hash changes to much, but isn't really needed, all script blocks are checked
unsafeWindow.calculateHash = function() {
if(document.location.pathname.indexOf("/n/destroyed") == 0) {
unsafeWindow.expectedHash = 'script: https://ssl.google-analytics.com/ga.js\ninline-script: 3176eb62ad483e51089bb86fd0cc6817c2ca2ebd\ninline-script: 44b9385a410bc720f5329fd9dc78beabed4e8e2d\nhttps://privnote.com/static-1741/js/pack.js:d591f2018edab5d1cce50940f9cdce1940cff4a1';
}
else if(document.location.pathname.indexOf("/n/") == 0) {
console.log('waiting...');

setTimeout(unsafeWindow.calculateHash , 50);

return;
}
else {
unsafeWindow.currentHash  = unsafeWindow.currentHash+'bodyHash: ' +Sha1.hash(document.body.innerHTML)+lineDenote;
unsafeWindow.expectedHash = 'bodyHash: d409b5a7d516739786321dabf744d1ec9ed4d03f\nscript: https://ssl.google-analytics.com/ga.js\ninline-script: 3176eb62ad483e51089bb86fd0cc6817c2ca2ebd\ninline-script: 44b9385a410bc720f5329fd9dc78beabed4e8e2d\ninline-script: fe78ec3083f8c4584f6c504a3ad781f3aa63f354\nhttps://privnote.com/static-1741/js/pack.js:d591f2018edab5d1cce50940f9cdce1940cff4a1';
}

console.log('Calculating...');

for(i in document.scripts) {
var s = document.scripts[i];
if(s.src == '') {
unsafeWindow.currentHash = unsafeWindow.currentHash+'inline-script: '+Sha1.hash(s.innerHTML)+lineDenote;
}
else {

// ignore google analytic code, assuming URL doesnt change
if(s.src == 'https://ssl.google-analytics.com/ga.js')  {
unsafeWindow.currentHash = unsafeWindow.currentHash+'script: '+s.src+lineDenote;
}
else {
unsafeWindow.pendingDocs = unsafeWindow.pendingDocs+1;

// grab the script resource to hash validate it
GM_xmlhttpRequest({ method: 'GET',
url: s.src,
onload: function(r) {
unsafeWindow.docSrc[s.src] = r.responseText;
unsafeWindow.pendingDocs = unsafeWindow.pendingDocs-1;
}
  });
}
}
}

unsafeWindow.validateInterval = setInterval(unsafeWindow.validateHash, 5);
}

unsafeWindow.validateHash = function()
{
if(unsafeWindow.pendingDocs > 0) return;

// stop the check timer
clearInterval(unsafeWindow.validateInterval);

// add up all the script resource hashes
for(i in unsafeWindow.docSrc) {
unsafeWindow.currentHash = unsafeWindow.currentHash+i+':'+Sha1.hash(unsafeWindow.docSrc[i]);
}

// if matches
if(unsafeWindow.currentHash != unsafeWindow.expectedHash)
{
var curHashFormat = unsafeWindow.currentHash.replace(new RegExp("\n", "g"), "\\n");

alert(document.URL+"\nPrivnote Not Validated, Hash has been modified, they may have added a back door!"+
  "\n\nExpected\n----------------------\n"+unsafeWindow.expectedHash+
  "\n\nNew Hash Values\n---------------------------\n" +unsafeWindow.currentHash+
  "\n\nHash String\n---------------------------\n" +curHashFormat);

document.getElementById("noteformw").children[0].innerHTML = "<span style='color:red'>WARNING!! PRIVNOTE HAS BEEN MODIFIED!!</span>";
}
else
{
document.getElementById("noteformw").children[0].innerHTML = "Write your note below (Hash OK, Key Enhanced)";
}
}

unsafeWindow.calculateHash ();

function setCharAt(str,index,chr) {
    if(index > str.length-1) return str;
    return str.substr(0,index) + chr + str.substr(index+1);
}

//
// Enhance the random string based on the mouse movement so key can not be guessed by time of generation due to firefox having bad Math.random implementations
unsafeWindow.onmousemove = function(e) {
    var B  = unsafeWindow.random_str;
    var p1 = Math.abs(Math.floor(Math.random() * B.length));
    var p2 = Math.abs(Math.floor(Math.random() * B.length));
   
    unsafeWindow.random_str = setCharAt(unsafeWindow.random_str, p1, B.charAt(p2));
    unsafeWindow.random_str = setCharAt(unsafeWindow.random_str, p2, B.charAt(p1));
   
    unsafeWindow.random_seeder = unsafeWindow.random_seeder + ((Math.random()*99999999) * e.clientX + e.clientY);
}

unsafeWindow.random_string = function(C) {
    if (C === null)
        C = 16   
   
    var B = unsafeWindow.random_str;
    var D = "";
   
    //alert(unsafeWindow.random_seeder);

    for (var A = 0; A < C; A++) {
        pos = Math.floor( (unsafeWindow.random_seeder * (Math.random() * B.length)) % B.length );
        D += B.charAt(pos)
    }
   
    return D
}

GPG SHA-1 SUM: 949c1495979872330e75c14eff9f07a510cf68e9



Old Details
Code: [Select]
That being said I am a big believer in the truth being spread instead of half-assed uneducated guesses and rumors. As well I realize people are insanely lazy and have a chance of maybe using a GreaseMonkey script but no chance of using GPG and i'd rather people be slightly more secure than not.

After KMFKwerm's post regarding PrivNote security, I started being curious just how secure PrivNote is, and found the key generation to be weak, but seemed to verify that your actual decrypted text nor key was ever sent to PrivNote.

Being bored in the last few days, I successfully was able to guess the random number within enough precision to bruteforce the key within 2 hours. In short, I could decrypt the text of one note stored on PrivNote's servers without the key given 2 hours and some extra information that would be relatively hard to obtain

I've created a small GreaseMonkey script that will make the key generation stronger and harder to be guessed. This still does not fix the fact that if anyone has the ClientURL+EncryptedText or ClientURL+MITM they could decrypt the text.

***NOTE***
The key is limited to 16 only because the response jscript on PrivNote's side expects location.hash to be 16, while this is a clientside check, im not sure how from GreaseMonkey to intercept and allow keys > 16, but you can verify that PrivNote still doesnt actually care.

When you use a key > 16, and attempt to decrypt privnote will end you up at /error, simply change the url to /n/destroyed/#hash after the error page comes (privnote obviously stores your internal hash in your sessionID, which is not insecure) and it will still decrypt even with 256 byte passwords

Information On How PrivNote works from reverse engineering:
http://dkn255hz262ypmii.onion/index.php?topic=24799.msg323573#msg323573
Title: Re: PrivNote Key Flaw - Update & GreaseMonkey Script
Post by: vlad1m1r on July 11, 2012, 09:33 pm
First off this is NOT an endorsement to use PrivNote, I recommend using GPG over using PrivNote.

That being said I am a big believer in the truth being spread instead of half-assed uneducated guesses and rumors. As well I realize people are insanely lazy and have a chance of maybe using a GreaseMonkey script but no chance of using GPG and i'd rather people be slightly more secure than not.

After KMFKwerm's post regarding PrivNote security, I started being curious just how secure PrivNote is, and found the key generation to be weak, but seemed to verify that your actual decrypted text nor key was ever sent to PrivNote.

Being bored in the last few days, I successfully was able to guess the random number within enough precision to bruteforce the key within 2 hours. In short, I could decrypt the text of one note stored on PrivNote's servers without the key given 2 hours and some extra information that would be relatively hard to obtain

I've created a shitty GreaseMonkey script that will slightly enhance the security of PrivNote by making the key slightly stronger, this could be dramatically improved, but I did not have the time to complete it (if someone wants to go for it, I recommend using keystroke / mouse movement to fuck with the seed)

It's imperative to help increase the key strength that you use your own random_seed number, and never tell anyone what you used, i've thrown a random seed in.

***NOTE***
The key is limited to 16 only because the response jscript on PrivNote's side expects location.hash to be 16, while this is a clientside check, im not sure how from GreaseMonkey to intercept and allow keys > 16, but you can verify that PrivNote still doesnt actually care.

When you use a key > 16, and attempt to decrypt privnote will end you up at /error, simply change the url to /n/destroyed/#hash after the error page comes (privnote obviously stores your internal hash in your sessionID, which is not insecure) and it will still decrypt even with 256 byte passwords




// ==UserScript==
// @id             a
// @name           PrivNote Random
// @version        1.0
// @namespace     
// @author         
// @description   
// @include        https://privnote.com/*
// @run-at         document-end
// ==/UserScript==

unsafeWindow.random_seeder = 93248532485239;
unsafeWindow.random_string = function(C) {
    if (C === null) {
        C = 16
    }
   
    var B = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@$%^*";
    var D = "";
   

    for (var A = 0; A < C; A++) {
        pos = Math.floor( (unsafeWindow.random_seeder * (Math.random() * B.length)) % B.length );
        D += B.charAt(pos)
    }
   
    return D
}

And from me, many thanks Raven.

V.
Title: Re: PrivNote Key Flaw - GreaseMonkey Script *UPDATE*
Post by: kmfkewm on July 12, 2012, 07:42 am
The decrypted message must be available to privnote, there is no such thing as entirely server side encryption that is secure from the server providing it. If I understand privnote correctly, the key to decrypt is in the URL. So they generate the key for you. That means they have access to the plaintext.
Title: Re: PrivNote Key Flaw - GreaseMonkey Script *UPDATE*
Post by: raven92 on July 12, 2012, 07:52 am
The decrypted message must be available to privnote, there is no such thing as entirely server side encryption that is secure from the server providing it. If I understand privnote correctly, the key to decrypt is in the URL. So they generate the key for you. That means they have access to the plaintext.

You're absolutely correct that there is no such thing as server side encryption that is secure from the server seeing it. But that's not how privnote works.

The encryption/decryption is 100% client side, not server side. The server hashes the Encrypted text to obtain a hashid. The URL used to retrieve is /n/<hashid>/#key, the appending of #key is done entirely client-side, the key is never transmitted to the server, just the encrypted text. When you retrieve it, you send them only the HashID, they send the encrypted text back, and javascript locally decrypts the text and injects it into the HTML page. I explain this in more detail in the other thread you started.
Title: Re: PrivNote Key Flaw - GreaseMonkey Script *UPDATE*
Post by: kmfkewm on July 12, 2012, 08:29 am
Ah wasn't aware that they were doing it in such a way , that does make it much safer than I thought it was although nothing stops them from changing the javascript.
Title: Re: PrivNote Security Monitor & Key Strength Update - GreaseMonkey Script
Post by: raven92 on September 08, 2012, 05:06 am
See the first post, I updated it with all the details.

I added important monitoring features to this script based on requests from pine to monitor PrivNote.
Title: Re: PrivNote Security Monitor & Key Strength Update - GreaseMonkey Script
Post by: pine on September 09, 2012, 11:55 pm
Thanks Raven! :)