Silk Road forums
Discussion => Security => Topic started 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.
// ==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
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
-
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.
-
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.
-
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.
-
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.
-
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.
-
Thanks Raven! :)