evercookie相关内容学习以及探究

简介

Evercookie is a Javascript API that produces extremely persistent cookies in a browser. Its goal is to identify a client even after they've removed standard cookies, Flash cookies (Local Shared Objects or LSOs), and others.
This is accomplished by storing the cookie data on as many browser storage mechanisms as possible. If cookie data is removed from any of the storage mechanisms, evercookie aggressively re-creates it in each mechanism as long as one is still intact.
If the Flash LSO, Silverlight or Java mechanism is available, Evercookie can even propagate cookies between different browsers on the same client machine!

原生项目地址为: https://github.com/samyk/evercookie

在Github上有多个evercookie的项目,大家都有不同的实现方法。

基本上就是使用JavaScript的各种api,浏览器的各种存储空间去存储用户数据,以保证,用户在删除了数据,但是却没有删完的情况下能够恢复数据。

原项目中使用到了很多兼容性不好的方法以及需要后端支持的方法,甚至还有适用exp的,通过对每种方法的查资料了解,以及查看源码,增加删除的情况下,精简出了一份兼容性高的(小程序不适用,浏览器适用)代码。

代码展示

try {
    (function () {
        'use strict';
        var document = window.document;
 
        try {
            var localStore = window.localStorage
        } catch (ex) {
        }
 
        try {
            var sessionStorage = window.sessionStorage;
        } catch (e) {
        }
 
        var first = true;
 
        function _ec_replace(str, key, value) {
            if (str.indexOf("&" + key + "=") > -1 || str.indexOf(key + "=") === 0) {
                // find start
                var idx = str.indexOf("&" + key + "="),
                    end, newstr;
                if (idx === -1) {
                    idx = str.indexOf(key + "=");
                }
                // find end
                end = str.indexOf("&", idx + 1);
                if (end !== -1) {
                    newstr = str.substr(0, idx) + str.substr(end + (idx ? 0 : 1)) + "&" + key + "=" + value;
                } else {
                    newstr = str.substr(0, idx) + "&" + key + "=" + value;
                }
                return newstr;
            } else {
                return str + "&" + key + "=" + value;
            }
        }
 
        function idb() {
            if ('indexedDB' in window) {
                return true
            } else if (window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB) {
                return true
            } else {
                return false
            }
        }
 
        var defaultOptionMap = {
            lso: true, // local storage
            domain: '.' + window.location.host.replace(/:\d+/, ''), // Get current domain
            db: true, // Database
            idb: true, // Indexed DB
            picCookie: true,
            tests: 10,
            worker: true,
        };
 
        var _baseKeyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
 
        function Evercookie(options) {
            options = options || {};
            var opts = {};
            for (var key in defaultOptionMap) {
                var optValue = options[key];
                if (typeof optValue !== 'undefined') {
                    opts[key] = optValue
                } else {
                    opts[key] = defaultOptionMap[key];
                }
            }
            if (typeof opts.domain === 'function') {
                opts.domain = opts.domain(window);
            }
 
            var _ec_tests = opts.tests,
                _ec_domain = opts.domain;
            var self = this;
            this._ec = {};
 
            this.get = function (name, cb) {
                if (!cb) {
                    cb = function () {
                    }
                }
                return new Promise(function (resolve, reject) {
                    self._evercookie(name, cb, undefined, undefined);
                    let tmp_ec = self._ec,
                        bestnum = 0,
                        item, candidates = [], best_candidate = '';
                    for (item in tmp_ec) {
                        if (tmp_ec[item] && tmp_ec[item] !== "null" && tmp_ec[item] !== "undefined") {
                            candidates[tmp_ec[item]] = candidates[tmp_ec[item]] === undefined ? 1 : candidates[tmp_ec[item]] + 1;
                        }
                    }
                    for (item in candidates) {
                        if (candidates[item] > bestnum) {
                            bestnum = candidates[item];
                            best_candidate = item;
                        }
                    }
                    self._ec.bestcandidate = best_candidate;
                    resolve({bestcandidate: best_candidate, all_candidates: self._ec});
                });
            };
 
            this.set = function (name, value) {
                return new Promise(function (resolve, reject) {
                    self._evercookie(name, function () {
                    }, value);
                    resolve({done: true});
                });
            };
 
            this._evercookie = function (name, cb, value, i) {
                if (self._evercookie === undefined) {
                    self = this;
                }
                if (i === undefined) {
                    i = 0;
                }
                // first run
                if (i === 0) {
                    if (opts.db) {
                        self.evercookie_database_storage(name, value);
                    }
                    if (opts.idb) {
                        self.evercookie_indexdb_storage(name, value);
                    }
                    if (opts.picCookie) {
                        self.evercookie_pic(name, value);
                    }
 
                    self._ec.cookieData = self.evercookie_cookie(name, value);
                    self._ec.localData = self.evercookie_local_storage(name, value);
                    self._ec.sessionData = self.evercookie_session_storage(name, value);
                    self._ec.windowData = self.evercookie_window(name, value);
 
                }
                if (value !== undefined) {
                    if (i++ < _ec_tests) {
                        setTimeout(function () {
                            self._evercookie(name, cb, value, i);
                        }, 200);
                    }
                } else {
                    if (
                        (
                            (opts.db && window.openDatabase && typeof self._ec.dbData === "undefined") ||
                            (opts.idb && idb() && (typeof self._ec.idbData === "undefined" || self._ec.idbData === "")) ||
                            (opts.picCookie && document.createElement("canvas").getContext && (typeof self._ec.picData === "undefined" || self._ec.picData === ""))
                        ) &&
                        i++ < _ec_tests
                    ) {
                        setTimeout(function () {
                            self._evercookie(name, cb, value, i);
                        }, 200);
                    } else {
                        var tmpec = self._ec,
                            candidates = [],
                            bestnum = 0,
                            candidate,
                            item;
                        self._ec = {};
 
                        for (item in tmpec) {
                            if (tmpec[item] && tmpec[item] !== "null" && tmpec[item] !== "undefined") {
                                candidates[tmpec[item]] = candidates[tmpec[item]] === undefined ? 1 : candidates[tmpec[item]] + 1;
                            }
                        }
 
                        for (item in candidates) {
                            if (candidates[item] > bestnum) {
                                bestnum = candidates[item];
                                candidate = item;
                            }
                        }
 
                        if (candidate !== undefined) {
                            self.set(name, candidate);
                        }
 
                        if (typeof cb === "function") {
                            cb(candidate, tmpec);
                        }
                    }
                }
            };
 
            this.evercookie_indexdb_storage = function (name, value) {
                try {
                    if (!('indexedDB' in window)) {
 
                        indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
                        IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction;
                        IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
                    }
 
                    if (indexedDB) {
                        var ver = 1;
                        //FF incognito mode restricts indexedb access
                        var request = indexedDB.open("idb_evercookie", ver);
                        request.onerror = function (e) {
                        };
                        request.onupgradeneeded = function (event) {
                            var db = event.target.result;
                            var store = db.createObjectStore("evercookie", {
                                keyPath: "name",
                                unique: false
                            })
                        };
 
                        if (value !== undefined) {
                            request.onsuccess = function (event) {
                                var idb = event.target.result;
                                if (idb.objectStoreNames.contains("evercookie")) {
                                    var tx = idb.transaction(["evercookie"], "readwrite");
                                    var objst = tx.objectStore("evercookie");
                                    var qr = objst.put({
                                        "name": name,
                                        "value": value
                                    })
                                }
                                idb.close();
                            }
                        } else {
                            request.onsuccess = function (event) {
                                var idb = event.target.result;
                                if (!idb.objectStoreNames.contains("evercookie")) {
                                    self._ec.idbData = undefined;
                                } else {
                                    var tx = idb.transaction(["evercookie"]);
                                    var objst = tx.objectStore("evercookie");
                                    var qr = objst.get(name);
                                    qr.onsuccess = function (event) {
                                        if (qr.result === undefined) {
                                            self._ec.idbData = undefined
                                        } else {
                                            self._ec.idbData = qr.result.value;
                                        }
                                    }
                                }
                                idb.close();
                            }
                        }
                    }
                } catch (e) {
                }
            };
 
            this.evercookie_database_storage = function (name, value) {
                try {
                    if (window.openDatabase) {
                        var database = window.openDatabase("sqlite_evercookie", "", "evercookie", 1024 * 1024);
                        if (value !== undefined) {
                            database.transaction(function (tx) {
                                tx.executeSql("CREATE TABLE IF NOT EXISTS cache(" +
                                    "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " +
                                    "name TEXT NOT NULL, " +
                                    "value TEXT NOT NULL, " +
                                    "UNIQUE (name)" +
                                    ")", [], function (tx, rs) {
                                }, function (tx, err) {
                                });
                                tx.executeSql("INSERT OR REPLACE INTO cache(name, value) " +
                                    "VALUES(?, ?)",
                                    [name, value], function (tx, rs) {
                                    }, function (tx, err) {
                                    });
                            });
                        } else {
                            database.transaction(function (tx) {
                                tx.executeSql("SELECT value FROM cache WHERE name=?", [name],
                                    function (tx, result1) {
                                        if (result1.rows.length >= 1) {
                                            self._ec.dbData = result1.rows.item(0).value;
                                        } else {
                                            self._ec.dbData = "";
                                        }
                                    }, function (tx, err) {
                                    });
                            });
                        }
                    }
                } catch (e) {
                }
            };
 
            this.evercookie_pic = function (name, value) {
                if (value === undefined) {
                    let text_data = '';
                    if (!name) {
                        return undefined;
                    }
                    let canvas = document.getElementById('ddec_' + name);
                    if (!canvas) {
                        return undefined;
                    }
                    let ctx = canvas.getContext('2d');
                    let img_data = ctx.getImageData(0, 0, 300, 1), pix = img_data.data;
                    for (let i = 0; i < pix.length; i += 4) {
                        if (pix[i] === 255) {
                            break;
                        }
                        text_data += String.fromCharCode(pix[i]);
                        if (pix[i + 1] === 255) {
                            break;
                        }
                        text_data += String.fromCharCode(pix[i + 1]);
                        if (pix[i + 2] === 255) {
                            break;
                        }
                        text_data += String.fromCharCode(pix[i + 2]);
                    }
                    self._ec.picData = text_data;
                    return text_data;
                }
                if (value.length > 900) {
                    return {context_obj: undefined, state: 0, error: 'value too long'};
                }
                let idObject = document.getElementById('ddec_' + name);
                if (idObject) {
                    idObject.parentNode.removeChild(idObject);
                }
                let canvas = document.createElement('canvas');
                let ctx = canvas.getContext('2d');
                canvas.id = 'ddec_' + name;
                canvas.style.visibility = "hidden";
                canvas.style.position = "absolute";
                canvas.height = 1;
                canvas.width = 300;
                let imgData = ctx.createImageData(300, 1);
                let pix = imgData.data;
                for (let i = 0, a = 0; i < canvas.width; i += 4) {
                    for (let t = 0; t < 4; t++) {
                        if (t % 4 === 3) {
                            pix[i + t] = 255;
                        } else {
                            pix[i + t] = value.length > a ? value.charCodeAt(a++) : 255;
                        }
                    }
                }
                ctx.putImageData(imgData, 0, 0);
                document.body.appendChild(canvas);
                return {context_obj: canvas, state: 1, error: null};
            };
 
            this.evercookie_cookie = function (name, value) {
                if (value !== undefined) {
                    document.cookie = name + "=; expires=Mon, 20 Sep 2010 00:00:00 UTC; path=/; domain=" + _ec_domain;
                    document.cookie = name + "=" + value + "; expires=Tue, 31 Dec 2030 00:00:00 UTC; path=/; domain=" + _ec_domain;
                } else {
                    return this.getFromStr(name, document.cookie);
                }
            };
 
            this.evercookie_local_storage = function (name, value) {
                try {
                    if (localStore) {
                        if (value !== undefined) {
                            localStore.setItem(name, value);
                        } else {
                            return localStore.getItem(name);
                        }
                    }
                } catch (e) {
                }
            };
 
            this.evercookie_session_storage = function (name, value) {
                try {
                    if (sessionStorage) {
                        if (value !== undefined) {
                            sessionStorage.setItem(name, value);
                        } else {
                            return sessionStorage.getItem(name);
                        }
                    }
                } catch (e) {
                }
            };
 
            this.evercookie_window = function (name, value) {
                try {
                    if (value !== undefined) {
                        window.name = _ec_replace(window.name, name, value);
                    } else {
                        return this.getFromStr(name, window.name);
                    }
                } catch (e) {
                }
            };
 
            this.encode = function (input) {
                var output = "",
                    chr1, chr2, chr3, enc1, enc2, enc3, enc4,
                    i = 0;
                input = this._utf8_encode(input);
                while (i < input.length) {
                    chr1 = input.charCodeAt(i++);
                    chr2 = input.charCodeAt(i++);
                    chr3 = input.charCodeAt(i++);
                    enc1 = chr1 >> 2;
                    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
                    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
                    enc4 = chr3 & 63;
                    if (isNaN(chr2)) {
                        enc3 = enc4 = 64;
                    } else if (isNaN(chr3)) {
                        enc4 = 64;
                    }
                    output = output +
                        _baseKeyStr.charAt(enc1) + _baseKeyStr.charAt(enc2) +
                        _baseKeyStr.charAt(enc3) + _baseKeyStr.charAt(enc4);
                }
 
                return output;
            };
 
            this.decode = function (input) {
                var output = "",
                    chr1, chr2, chr3,
                    enc1, enc2, enc3, enc4,
                    i = 0;
                input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
                while (i < input.length) {
                    enc1 = _baseKeyStr.indexOf(input.charAt(i++));
                    enc2 = _baseKeyStr.indexOf(input.charAt(i++));
                    enc3 = _baseKeyStr.indexOf(input.charAt(i++));
                    enc4 = _baseKeyStr.indexOf(input.charAt(i++));
                    chr1 = (enc1 << 2) | (enc2 >> 4);
                    chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
                    chr3 = ((enc3 & 3) << 6) | enc4;
                    output = output + String.fromCharCode(chr1);
                    if (enc3 !== 64) {
                        output = output + String.fromCharCode(chr2);
                    }
                    if (enc4 !== 64) {
                        output = output + String.fromCharCode(chr3);
                    }
                }
                output = this._utf8_decode(output);
                return output;
            };
 
            this._utf8_encode = function (str) {
                str = str.replace(/\r\n/g, "\n");
                var utftext = "", i = 0, n = str.length, c;
                for (; i < n; i++) {
                    c = str.charCodeAt(i);
                    if (c < 128) {
                        utftext += String.fromCharCode(c);
                    } else if ((c > 127) && (c < 2048)) {
                        utftext += String.fromCharCode((c >> 6) | 192);
                        utftext += String.fromCharCode((c & 63) | 128);
                    } else {
                        utftext += String.fromCharCode((c >> 12) | 224);
                        utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                        utftext += String.fromCharCode((c & 63) | 128);
                    }
                }
                return utftext;
            };
 
            this._utf8_decode = function (utftext) {
                var str = "",
                    i = 0, n = utftext.length,
                    c = 0, c1 = 0, c2 = 0, c3 = 0;
                while (i < n) {
                    c = utftext.charCodeAt(i);
                    if (c < 128) {
                        str += String.fromCharCode(c);
                        i += 1;
                    } else if ((c > 191) && (c < 224)) {
                        c2 = utftext.charCodeAt(i + 1);
                        str += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                        i += 2;
                    } else {
                        c2 = utftext.charCodeAt(i + 1);
                        c3 = utftext.charCodeAt(i + 2);
                        str += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                        i += 3;
                    }
                }
                return str;
            };
 
            this.getFromStr = function (name, text) {
                if (typeof text !== "string") {
                    return;
                }
                var nameEQ = name + "=",
                    ca = text.split(/[;&]/),
                    i, c;
                for (i = 0; i < ca.length; i++) {
                    c = ca[i];
                    while (c.charAt(0) === " ") {
                        c = c.substring(1, c.length);
                    }
                    if (c.indexOf(nameEQ) === 0) {
                        return c.substring(nameEQ.length, c.length);
                    }
                }
            };
 
            this.getHost = function () {
                return window.location.host.replace(/:\d+/, '');
            };
        }
 
        window.evercookie = window.Evercookie = Evercookie;
    })(window);
} catch (e) {
}

其中还有两个函数,encode和decode是可以删除的,但是我没有删除,我觉得可能会有用,那是base64的代码,作者写得挺简洁的

各种方法解析

Standard HTTP Cookies

这就是指的标准cookie通过设定很长的过期时间还有用设置域 . + hostname 的方式来支持主域名下的所有子域名

window.name caching

在浏览器窗口中有个设置名字的选项就是window.name这里可以存储数据,具体多大不填清楚,不过挺大的,在evercookie项目中这一部分的存储,用的结构有点像url的parameter

HTML5 Session Storage

这是目前浏览器都有的一个存储空间

HTML5 Local Storage

同上也是一种普遍存储方式

HTML5 Database Storage via SQLite

这是一种构建在客户端浏览器上的数据库存储方式,查询和增删修改都使用SQL语句

HTML5 IndexedDB

同上这也是客户端的数据库实现

HTML5 Canvas

原版是调用后端的python,使用PIL库生成图片,改进之后直接js生成图片,每个像素可以存3字节的数据,通过读取图片元数据来获取存储在本都的数据

使用方法示例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>test</title>
</head>
<body>
<button onclick="dp.get('poker', getCookie);">点击触发第二种方法</button>
<p>第一种方法看console的日志能看到,速度要快点而且是异步,结果由then获取</p>
<p>第二种方法不适合在页面一加载的时候就调用,因为数据还没处理完</p>
<script type="text/javascript" src="my_evercookie.js"></script>
<script type="text/javascript">
    // 使用方法,先初始化对象
    var dp = new evercookie();
    // 唯一设置方法
    dp.set('poker', 'this_is_cookie');
    // 获取方法1,返回Promise对象
    dp.get("poker").then(info => {console.log(info)});
    console.log('speed test');
    // 获取方法2,直接传入callback函数,这种方法有一定延迟不推荐使用,是原来js的方法,未删除
    function getCookie(best_candidate, all_candidates)
    {
        alert("The retrieved cookie is: " + best_candidate + "\n" +
            "You can see what each storage mechanism returned " +
            "by looping through the all_candidates object.");
        for (var item in all_candidates)
            document.write("<p>Storage mechanism " + item +
                " returned " + all_candidates[item] + " votes</p>");
    }
</script>
</body>
</html>

本文链接:

https://0xpoker.cn/normal/6.html
1 + 1 =
快来做第一个评论的人吧~