HTML5 Canvas Fingerprinting

Discussion in 'privacy general' started by Sampei Nihira, May 30, 2016.

  1. JRViejo

    JRViejo Super Moderator

    Joined:
    Jul 9, 2008
    Posts:
    97,878
    Location:
    U.S.A.
    Confirmed, and also Firefox as well. :thumb:
     
  2. Sampei Nihira

    Sampei Nihira Registered Member

    Joined:
    Apr 7, 2013
    Posts:
    3,365
    Location:
    Italy
    Last edited: Jul 17, 2016
  3. liba

    liba Registered Member

    Joined:
    Jan 21, 2016
    Posts:
    344
  4. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
  5. Sampei Nihira

    Sampei Nihira Registered Member

    Joined:
    Apr 7, 2013
    Posts:
    3,365
    Location:
    Italy
    @andryou

    ScriptSafe not work on Firefox:


    1.JPG

    2.JPG

    You can fix it?
    TH.
     

    Attached Files:

    • 1.JPG
      1.JPG
      File size:
      155 KB
      Views:
      13
    • 2.JPG
      2.JPG
      File size:
      68.2 KB
      Views:
      14
  6. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    Canvas Defender Firefox version updated to 1.0.9
    ??
     
  7. UnknownK

    UnknownK Registered Member

    Joined:
    Nov 3, 2012
    Posts:
    160
    Location:
    Unknown
    The previous version was not even installable on firefox for android even though it claimed otherwise. The current version--with the fix--is working fine with firefox for android.
     
  8. liba

    liba Registered Member

    Joined:
    Jan 21, 2016
    Posts:
    344
    no any update? :/
     
  9. liba

    liba Registered Member

    Joined:
    Jan 21, 2016
    Posts:
    344
    any update?
     
  10. Sampei Nihira

    Sampei Nihira Registered Member

    Joined:
    Apr 7, 2013
    Posts:
    3,365
    Location:
    Italy
    Added WebRTC Leak Prevent (for Chrome)
     
  11. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    uBlock Origin does that (Firefox and Chrome).
     
  12. Sampei Nihira

    Sampei Nihira Registered Member

    Joined:
    Apr 7, 2013
    Posts:
    3,365
    Location:
    Italy
  13. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    @DenMLA

    I've just replaced Canvas Defender in FF / CF with CanvasBlocker because I've found a setting which allows a persistent canvas fingerprint while the browser is open but creates a new fingerprint next time you open it. I have been hoping a similar feature would make it into Canvas Defender but no luck so far.

    I'm also sick of seeing the Canvas Defender notification window at every launch of my browsers.
     
  14. guest

    guest Guest

    Yes, this can be very annoying after some time.
     
  15. paulderdash

    paulderdash Registered Member

    Joined:
    Dec 27, 2013
    Posts:
    4,644
    Location:
    Under a bushel ...
    +1. IIRC, the devs said some time ago they were going to address this.
     
  16. Anonfame1

    Anonfame1 Registered Member

    Joined:
    May 25, 2016
    Posts:
    224
    I'd certainly like to know how you do this, because I've installed CanvasBlocker and can only get it to generate a new noise hash on every request. I dont see any options listed to accomplish what you say either. Maybe im just having a dumb moment?
     
  17. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    Doesn't this work?

    CanvasBlocker.PNG

    Maybe it's me having a "dumb moment"?
     
  18. Anonfame1

    Anonfame1 Registered Member

    Joined:
    May 25, 2016
    Posts:
    224
    No, indeed the dumb moment was mine :p

    I was stuck at messing with Block Mode and not the number generator. Now thinking about it, I dont know why I didnt think to change this option- temporary lapse in logic :D

    I confirmed with browserleaks that the hash remains constant during a session but changes when the browser is relaunched- perfect. Thanks!
     
  19. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    :thumb:
     
  20. Anonfame1

    Anonfame1 Registered Member

    Joined:
    May 25, 2016
    Posts:
    224
    I noticed an interesting fact about CanvasBlocker with random number generator set to persistent on e10s Firefox. It retains the same canvas hash so long as you stay in the same tab; if you open another tab, the canvas hash is different in that tab (though again it will stay that hash in that tab).

    Example:
    Tab 1 hash: D7U834H (totally made up hash). Browse to different sites, different links within site, etc--> hash is still D7U834H.
    Open a new tab, Tab 2 hash: HK89G3Q. Again browsing to different sites will keep this same hash within this tab.

    On single process FF, I would imagine every tab has the same canvas hash no matter how many you open or close.

    I imagine this is likely a "bug," though really it could be seen as a feature. In effect you can control your tracking by tab where each site sees a different canvas hash (that still remains the same as you navigate within each individual tab), and thus you cant be linked (by the hash at least) between sites.

    The only thing one must remember though is that using "Open link in New Tab" to view a link within the same site (say Wilders Forum Index opened in tab 1 while using "Open link in New Tab" to open All Things Unix in another tab) will allow tracking based on the fact that your canvas hash is changing in this case.

    Hopefully what I say above makes sense? Its sort of hard to explain, but the simple version is that on e10s you have a different canvas hash for each process that you have. I should note I have FF set to separate each tab into a new process (as opposed to current e10s default of all tabs sharing one process and the interface having a separate process), and that is likely why.

    Just FYI...
     
  21. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    How can we tell if we re using e10s again? I know I've seen it posted here in the forums somewhere but...
     
  22. Anonfame1

    Anonfame1 Registered Member

    Joined:
    May 25, 2016
    Posts:
    224
    about:support

    Look down to where it says "Multiprocess Windows" and see what it says. On mine:

    Multiprocess Windows 1/1 (Enabled by user)


    Another way of telling is to open your process manager and see how many Firefox processes there are. Im not sure on Windows, but on Linux all of the tab processes are called "Web Content" while the interface process is called "Firefox".
     
  23. Krusty

    Krusty Registered Member

    Joined:
    Feb 3, 2012
    Posts:
    10,240
    Location:
    Among the gum trees
    Cool, thanks! :thumb:

    As expected, I'm not using e10s as yet so I'll wait until it gets pushed out.
     
  24. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    Thanks for sharing your results. Some login pages use fingerprinting for device detecton, and if the fingerprints do not match there are extra login steps which aren't always convenient. So I think some might like process to process and even browser session to browser session consistency. For specified sites I mean. I haven't thought through whether that should be host based, domain based, or selectable.

    Anyway, I'd like to take a look at this too. May I ask what settings you used to force your multiprocess arrangement?

    I prefer private testing and updated this (which you might find useful):
    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Canvas Fingerprinting Test v1.7</title>
    <style type="text/css">
    body {
      font: 13px arial, helvetica, sans-serif;
    }
    table, td {
      border-collapse: collapse;
      border: 1px solid black;
    }
    table#analysis {
      border: none;
    }
    td {
      text-align: center;
      padding: 3px;
    }
    table#analysis td {
      border: none;
      text-align: left;
      padding: 2px;
    }
    td.heading {
      font-weight: bold;
      background-color: #eeeeee;
    }
    canvas {
      border: 1px solid blue !important;
    }
    textarea.canvasData {
      height: 40px;
      width: 300px;
    }
    div#main {
      display: none;
    }
    #deleteCookiesButton {
      display: none;
    }
    .caution {
      font-weight: bold;
      color: #D25500;
    }
    .newSection {
      margin-top: 11px;
    }
    </style>
    <script type="text/javascript">
    "use strict";
    
    var salt = "ky49Uc0n3VsE4O952fjo6lsYkN057";
    var useCookiesToCompareRuns = true;
    var cookieExpirationInDays = 1;
    var useErrMsgAsCanvasData = true;
    var hideCanvas2 = true;
    
    function modifyAndReadCanvas(canvasId, create) {
      var result = {element:undefined, errorMsg:undefined, canvasData:"undefined"};
      try {
        var canvas;
        if(create) {
          canvas = document.createElement('canvas');
          if(!canvas) {
            result.errorMsg = "Can't create canvas element";
          }
          else canvas.id = canvasId;
        }
        else {
          canvas = document.getElementById(canvasId);
          if(!canvas) {
            result.errorMsg = "Can't get ID of specified canvas";
          }
        }
        if(canvas) {
          result.canvas = canvas;
          if(typeof canvas.getContext === "function") {
            var ctx = canvas.getContext('2d');
            if(ctx) {
              var txt1 = 'o%@H,Db5l9}UJlfA0!qYw:fA.>* p^q?;c7/G#bQ';
              var txt2 = 'Id%-_m,b<7il43&w{lR[0!q)f=h.*1p^qT+OX;QG';
              ctx.font = "14px Arial";
              ctx.textBaseline = "alphabetic";
              ctx.fillStyle = "#2110ab";
              ctx.fillRect(45,15,95,22);
              ctx.fillStyle = "#480427";
              ctx.fillRect(161,15,95,22);
              ctx.fillStyle = "#000000";
              ctx.fillRect(50,70,200,50);
              ctx.fillStyle = "#ed4165";
              ctx.fillText(txt1, 3, 31);
              ctx.font = "15px serif";
              ctx.fillStyle = "rgba(113, 94, 178, 0.7)";
              ctx.fillText(txt2, 5, 33);
              var circle = new Path2D();
              ctx.fillStyle = "rgba(73, 104, 148, 0.6)";
              circle.arc(150, 26, 25, 0, 2 * Math.PI);
              ctx.fill(circle);
              if(typeof canvas.toDataURL === "function") {
                result.canvasData = canvas.toDataURL();
                if(result.canvasData.length == 0) {
                  result.errorMsg = "Zero length dataUrl";
                }
                else if(result.canvasData == "data:,") {
                  result.errorMsg = "No canvas data";
                }
              }
              else result.errorMsg = "canvas.toDataURL is unavailable";
            }
            else result.errorMsg = "canvas.getContext is unavailable";
          }
          else result.errorMsg = "canvas.getContext is unavailable";
        }
      }
      catch(e) {
        console.log(e);
        result.errorMsg = "Exception (check console)";
      }
      if(result.errorMsg && useErrMsgAsCanvasData) {
        result.canvasData = result.errorMsg;
      }
      return(result);
    }
    
    function getHash1(str) {
      // djb2 from:
      // http://erlycoder.com/49/javascript-hash-functions-to-convert-string-into-integer-hash-
      var hash = 5381;
      for (var i = 0; i < str.length; i++) {
        var char = str.charCodeAt(i);
        hash = ((hash << 5) + hash) + char; /* hash * 33 + c */
      }
      return hash;
    }
    
    function getHash2(str) {
      // FNV-1a from:
      // https://gist.github.com/vaiorabbit/5657561
      var FNV1_32A_INIT = 0x811c9dc5;
      var hval = FNV1_32A_INIT;
      for ( var i = 0; i < str.length; ++i )
      {
        hval ^= str.charCodeAt(i);
        hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
      }
      return hval >>> 0;
    }
    
    function getHashStr(str) {
      str += salt;
      return (getHash1(str).toString() + getHash2(str).toString());
    }
    
    function setCookie(name, value) {
      var date = new Date();
      date.setTime(date.getTime() + (cookieExpirationInDays*24*60*60*1000));
      document.cookie = name + "=" + encodeURIComponent(value) + ";" + "expires=" + date.toUTCString() + ";";
    };
    
    function getCookie(name) {
      return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
    };
    
    function deleteCookie(name) {
      document.cookie = name + "=1; expires=Thu, 01 Jan 1970 00:00:01 GMT";
    };
    
    function deleteCookies() {
      deleteCookie("Canvas1Fingerprint");
      deleteCookie("Canvas2Fingerprint");
    };
    
    function cautionStyle(s) {
      return('<span class="caution">' + s + '<span>');
    }
    
    function getAnchorHtml(href, text) {
      return('<a target="_blank" href="' + href + '">' + text + '</a>');
    }
    
    function performTest() {
      document.getElementById("main").style="display:block;";
      var sensedInterference = false;
    
      var c1 = modifyAndReadCanvas("canvas1", false);
      var c2 = modifyAndReadCanvas("canvas2", true);
      c2.holder = document.getElementById("c2-holder");
      if(hideCanvas2) {
        c2.holder.innerHTML = "Not visible by design";
      }
      else {
        if(c2.canvas) {
          c2.holder.appendChild(c2.canvas);
        }
        else c2.holder.innerHTML = "Canvas not created";
      }
    
      document.getElementById("c1-data").value = c1.canvasData;
      document.getElementById("c2-data").value = c2.canvasData;
    
      if(c1.canvasData && c1.canvasData.startsWith("data:image/png;base64,")) {
        document.getElementById("c1-data-heading").innerHTML = getAnchorHtml(c1.canvasData, "Data");
      }
      if(c2.canvasData && c2.canvasData.startsWith("data:image/png;base64,")) {
        document.getElementById("c2-data-heading").innerHTML = getAnchorHtml(c2.canvasData, "Data");
      }
    
      c1.fp = getHashStr(c1.canvasData);
      c2.fp = getHashStr(c2.canvasData);
      document.getElementById("c1-fp").innerHTML = c1.fp;
      document.getElementById("c2-fp").innerHTML = c2.fp;
    
      if(c1.errorMsg) {
        document.getElementById("c1-errorMsg").innerHTML = cautionStyle(c1.errorMsg);
        sensedInterference = true;
      }
      else document.getElementById("c1-errorMsg").innerHTML = "None detected";
      if(c2.errorMsg) {
        document.getElementById("c2-errorMsg").innerHTML = cautionStyle(c2.errorMsg);
        sensedInterference = true;
      }
      else document.getElementById("c2-errorMsg").innerHTML = "None detected";
    
      var footnotes = [];
      var resultHtml;
      if((c1.canvasData == c2.canvasData)) {
        resultHtml = "Same";
      }
      else {
        resultHtml = cautionStyle("Different");
        sensedInterference = true;
      }
      document.getElementById("canvasComparison").innerHTML = resultHtml;
    
      if(useCookiesToCompareRuns) {
        var c1FpCookieName = "Canvas1Fingerprint";
        var c2FpCookieName = "Canvas2Fingerprint";
        c1.prevFp = getCookie(c1FpCookieName);
        c2.prevFp = getCookie(c2FpCookieName);
    
        if((c1.prevFp == null) && (c2.prevFp == null)) {
          footnotes.push("<sup>*</sup> Previous result cookies were not found");
          resultHtml = "Unknown";
        }
        else {
          footnotes.push("<sup>*</sup> Previous result cookies were found");
          if((c1.fp == c1.prevFp) && (c2.fp == c2.prevFp)) {
            resultHtml = "Same";
          }
          else {
            resultHtml = cautionStyle("Different");
            sensedInterference = true;
          }
        }
        document.getElementById("runComparison").innerHTML = resultHtml;
    
        setCookie(c1FpCookieName, c1.fp);
        setCookie(c2FpCookieName, c2.fp);
        if((getCookie(c1FpCookieName) != null) || (getCookie(c2FpCookieName) != null)) {
          footnotes.push("<sup>*</sup> Current results saved in cookies");
          document.getElementById("deleteCookiesButton").style="display:block";
        }
        else {
          footnotes.push(cautionStyle("<sup>*</sup> Current results could not be saved (cookies blocked)"));
        }
        footnotes.push("<sup>*</sup> Private browsing, serving domain/path, etc may affect results");
      }
      else {
        document.getElementById("runComparison").innerHTML = "Disabled";
        footnotes.push("<sup>*</sup> Can be enabled by setting useCookiesToCompareRuns to true");
      }
    
      document.getElementById("sensedInterference").innerHTML = (sensedInterference ? cautionStyle("Yes") : "No");
      footnotes.push("<sup>†</sup> API blocking, canvas difference, or different results across runs");
    
      var footnotesHolder = document.getElementById("footnotes");
      for(var i=0; i<footnotes.length; i++) {
        var d = document.createElement("div");
        d.innerHTML = footnotes[i];
        footnotesHolder.appendChild(d);
      }
    }
    </script>
    </head>
    <body onload="performTest();">
    <noscript>This test page requires Javascript</noscript>
    <div id="main">
    <table>
    <tr>
      <td class="heading">Canvas1</td>
      <td class="heading">Canvas2</td>
    </tr>
    <tr>
      <td id="c1-holder"><canvas id="canvas1"></canvas></td>
      <td id="c2-holder"></td>
    </tr>
    <tr>
      <td id="c1-data-heading" class="heading">Data</td>
      <td id="c2-data-heading" class="heading">Data</td>
    </tr>
    <tr>
      <td><textarea id="c1-data" class="canvasData"></textarea></td>
      <td><textarea id="c2-data" class="canvasData"></textarea></td>
    </tr>
    <tr>
      <td class="heading">Fingerprint</td>
      <td class="heading">Fingerprint</td>
    </tr>
    <tr>
      <td id="c1-fp"></td>
      <td id="c2-fp"></td>
    </tr>
    <tr>
      <td class="heading">API Blocking?</td>
      <td class="heading">API Blocking?</td>
    </tr>
    <tr>
      <td id="c1-errorMsg"></td>
      <td id="c2-errorMsg"></td>
    </tr>
    </table>
    <table id="analysis" class="newSection">
    <tr>
      <td>Canvas1 fingerprint compared to Canvas2 fingerprint:</td>
      <td id="canvasComparison"></td>
    </tr>
    <tr>
      <td>This run compared to previous run<sup>*</sup>:</td>
      <td id="runComparison"></td>
    </tr>
    <tr>
      <td>Sensed fingerprinting interference<sup>†</sup>:</td>
      <td id="sensedInterference"></td>
    </tr>
    </table>
    <div id="footnotes" class="newSection"></div>
    <div class="newSection">
      <button id="deleteCookiesButton" onclick="deleteCookies();">Delete Cookies</button>
    </div>
    </div>
    </body>
    </html>
    
    Edits: Multiple edits to improve code. Latest version is 1.7.
     
    Last edited: Oct 29, 2016
  25. Anonfame1

    Anonfame1 Registered Member

    Joined:
    May 25, 2016
    Posts:
    224
    @TheWindBringeth- I enabled it using an approach similar to this: http://www.ghacks.net/2016/07/22/multi-process-firefox/

    However, I also decided to set it up where each tab has its own process. Therefore I also set: dom.ipc.processCount to 100 (Ill never open that many tabs in one session...)

    Heres my results from your test. I will admit I was dumb enough to wonder at first if this was supposed to be a stylish extension or something to put in CanvasBlocker by unpacking the addon :|

    Then I saw the html tags and no FF specific commands and figured "this must be a webpage." :isay:

    It appears as if the signature shows the same, but wouldnt this be the case since its loaded within the same process/tab? If I load this html page in separate tabs I get different fingerprints for each tab, though canvas 1 and canvas 2 is the same within each tab... Does this make sense?

    E.g. Tab 1 canvas 1 & 2 fingerprints: blahblah435
    Tab 2 canvas 1 & 2 fingerprints: 843blahblah Screenshot_2016-10-27_20-56-46.jpg

    **EDIT** Ahh I see what you were looking for now... The part that says "This run compared to previous run" will say "Different" (instead of "Unknown" like in my uploaded picture) when I first load it in tab 2. If I "refresh" tab 2, it will change to "same." So as I said above, it appears the canvas hash is in fact different between tabs/processes.
     
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.