RGPD / CNIL – Nouvelles règles pour utiliser Google Analytics

Voici une solution gratuite pour vous permettre d’utiliser Google Analytics en conformité avec le RGPD.

Google analytics pose plusieurs problèmes d’un point de vue du RGPD, la CNIL en ayant soulevé un majeur qui est relatif aux transferts de données personnelles hors de l’Union européenne.

Par défaut des données personnelles sont transférées aux Etats-Unis
Google interdit les données personnelles dans GA
Toutefois l’IP d’un visiteur sera traitée par Google
Cette IP traitée sera transférée aux États-Unis

Légalement on peut transférer des données aux États-Unis à condition d’avoir des garanties supplémentaires. Google propose des garanties supplémentaires effectives et licites

Comment paramétrer Google Analytics pour être en conformité avec le RGPD

  1. Supprimer les cookies
  2. Générer un userId anonyme
  3. Activez Anonymize IP
  4. Acceptez les clauses contractuelles types
  5. Ne transférez aucune donnée personnelle dans GA

1 – Supprimer les cookies

La première action a mettre en œuvre est de supprimer l’ensemble des cookies qui sont placés côté client qui existe dans Google Analytics dans sa configuration par défaut.

Analytics permet de mettre en place une solution sans cookies au travers des paramètres suivants :

client_storage: ‘none’, client_id: buildUserId()

2 – Générer un userId anonyme

Nous allons fournir à Analytics un userID; cet userId est considéré comme une donnée personnelle par Google en temps normal (cf. les data processings terms de Google), nous allons la supprimer.

La solution fonctionne de la manière suivante :

  • on identifie le navigateur de la personne
  • on identifie les langues configurées sur le navigateur
  • on génère une tranche horaire arrondie à 3h
  • on effectue un hachage de ces données (md5) pour générer l’userId anonyme

3 – Activez Anonymize IP

Une fois que l’on a un userId anonyme, et que l’on a évacué les cookies, nous nous retrouvons avec une seule donnée personnelle au sens du RGPD : l’adresse IP de l’utilisateur qui sera transmise à Google qui permet d’anonymiser cette donnée en Europe

Voici le paramétrage qui vous permettra d’utiliser Google Analytics :

‘anonymize_ip’: true.

En anonymisant l’IP, nous coupons donc totalement au transfert de données hors de l’UE et pouvons exploiter Google analytics en conformité au RGPD.

Voici le code de configuration pour Google analytics :​

				
					<script type="text/plain" data-cli-class="cli-blocker-script" data-cli-label="Google Tag Manager"  data-cli-script-type="" data-cli-block="true" data-cli-block-if-ccpa-optout="false" data-cli-element-position="body" async src="//www.googletagmanager.com/gtag/js?id=XXXXXXX-XX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-XXXXXXX-XX',  
    { 
        'anonymize_ip': true,
        'allow_ad_personalization_signals': false,
        'allow_google_signals': false,
        'ad_storage': 'denied',
        client_storage: 'none',
        client_id: buildUserId()
    }
);
</script>
				
			

On peut observer trois options importantes :

anonymize_ip’: true -> cette option permet d’anonymiser l’adresse IP et donc d’éviter des transferts hors UE

client_storage: ‘none’ -> cela permet d’éviter les cookies, donc à la fois l’application du RGPD pour les cookies, et éviter l’application de la directive e-privacy qui impose le consentement des persones pour le stockage de données sur leurs navigateurs (cookies, localStorage…)

client_id: buildUserId() -> Google analytics nécessite un userId, la fonction buildUserId() va donc renvoyer cet userId qui est un hash des éléments susmentionnés.

Voici le détail du code avec la fonction buildUserId() et son implémentation pratique dans la page web

				
					<script>
            /*
            *   permet de créer un MD5 - hash 
            *   origine : http://www.myersdaily.org/joseph/javascript/md5.js
            *
            */
            function md5cycle(x, k) {
                var a = x[0], b = x[1], c = x[2], d = x[3];

                a = ff(a, b, c, d, k[0], 7, -680876936);
                d = ff(d, a, b, c, k[1], 12, -389564586);
                c = ff(c, d, a, b, k[2], 17,  606105819);
                b = ff(b, c, d, a, k[3], 22, -1044525330);
                a = ff(a, b, c, d, k[4], 7, -176418897);
                d = ff(d, a, b, c, k[5], 12,  1200080426);
                c = ff(c, d, a, b, k[6], 17, -1473231341);
                b = ff(b, c, d, a, k[7], 22, -45705983);
                a = ff(a, b, c, d, k[8], 7,  1770035416);
                d = ff(d, a, b, c, k[9], 12, -1958414417);
                c = ff(c, d, a, b, k[10], 17, -42063);
                b = ff(b, c, d, a, k[11], 22, -1990404162);
                a = ff(a, b, c, d, k[12], 7,  1804603682);
                d = ff(d, a, b, c, k[13], 12, -40341101);
                c = ff(c, d, a, b, k[14], 17, -1502002290);
                b = ff(b, c, d, a, k[15], 22,  1236535329);

                a = gg(a, b, c, d, k[1], 5, -165796510);
                d = gg(d, a, b, c, k[6], 9, -1069501632);
                c = gg(c, d, a, b, k[11], 14,  643717713);
                b = gg(b, c, d, a, k[0], 20, -373897302);
                a = gg(a, b, c, d, k[5], 5, -701558691);
                d = gg(d, a, b, c, k[10], 9,  38016083);
                c = gg(c, d, a, b, k[15], 14, -660478335);
                b = gg(b, c, d, a, k[4], 20, -405537848);
                a = gg(a, b, c, d, k[9], 5,  568446438);
                d = gg(d, a, b, c, k[14], 9, -1019803690);
                c = gg(c, d, a, b, k[3], 14, -187363961);
                b = gg(b, c, d, a, k[8], 20,  1163531501);
                a = gg(a, b, c, d, k[13], 5, -1444681467);
                d = gg(d, a, b, c, k[2], 9, -51403784);
                c = gg(c, d, a, b, k[7], 14,  1735328473);
                b = gg(b, c, d, a, k[12], 20, -1926607734);

                a = hh(a, b, c, d, k[5], 4, -378558);
                d = hh(d, a, b, c, k[8], 11, -2022574463);
                c = hh(c, d, a, b, k[11], 16,  1839030562);
                b = hh(b, c, d, a, k[14], 23, -35309556);
                a = hh(a, b, c, d, k[1], 4, -1530992060);
                d = hh(d, a, b, c, k[4], 11,  1272893353);
                c = hh(c, d, a, b, k[7], 16, -155497632);
                b = hh(b, c, d, a, k[10], 23, -1094730640);
                a = hh(a, b, c, d, k[13], 4,  681279174);
                d = hh(d, a, b, c, k[0], 11, -358537222);
                c = hh(c, d, a, b, k[3], 16, -722521979);
                b = hh(b, c, d, a, k[6], 23,  76029189);
                a = hh(a, b, c, d, k[9], 4, -640364487);
                d = hh(d, a, b, c, k[12], 11, -421815835);
                c = hh(c, d, a, b, k[15], 16,  530742520);
                b = hh(b, c, d, a, k[2], 23, -995338651);

                a = ii(a, b, c, d, k[0], 6, -198630844);
                d = ii(d, a, b, c, k[7], 10,  1126891415);
                c = ii(c, d, a, b, k[14], 15, -1416354905);
                b = ii(b, c, d, a, k[5], 21, -57434055);
                a = ii(a, b, c, d, k[12], 6,  1700485571);
                d = ii(d, a, b, c, k[3], 10, -1894986606);
                c = ii(c, d, a, b, k[10], 15, -1051523);
                b = ii(b, c, d, a, k[1], 21, -2054922799);
                a = ii(a, b, c, d, k[8], 6,  1873313359);
                d = ii(d, a, b, c, k[15], 10, -30611744);
                c = ii(c, d, a, b, k[6], 15, -1560198380);
                b = ii(b, c, d, a, k[13], 21,  1309151649);
                a = ii(a, b, c, d, k[4], 6, -145523070);
                d = ii(d, a, b, c, k[11], 10, -1120210379);
                c = ii(c, d, a, b, k[2], 15,  718787259);
                b = ii(b, c, d, a, k[9], 21, -343485551);

                x[0] = add32(a, x[0]);
                x[1] = add32(b, x[1]);
                x[2] = add32(c, x[2]);
                x[3] = add32(d, x[3]);

            }

            function cmn(q, a, b, x, s, t) {
                a = add32(add32(a, q), add32(x, t));
                return add32((a << s) | (a >>> (32 - s)), b);
            }

            function ff(a, b, c, d, x, s, t) {
                return cmn((b & c) | ((~b) & d), a, b, x, s, t);
            }

            function gg(a, b, c, d, x, s, t) {
                return cmn((b & d) | (c & (~d)), a, b, x, s, t);
            }

            function hh(a, b, c, d, x, s, t) {
                return cmn(b ^ c ^ d, a, b, x, s, t);
            }

            function ii(a, b, c, d, x, s, t) {
                return cmn(c ^ (b | (~d)), a, b, x, s, t);
            }

            function md51(s) {
                txt = '';
                var n = s.length,
                state = [1732584193, -271733879, -1732584194, 271733878], i;
                for (i=64; i<=s.length; i+=64) {
                md5cycle(state, md5blk(s.substring(i-64, i)));
                }
                s = s.substring(i-64);
                var tail = [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0];
                for (i=0; i<s.length; i++)
                tail[i>>2] |= s.charCodeAt(i) << ((i%4) << 3);
                tail[i>>2] |= 0x80 << ((i%4) << 3);
                if (i > 55) {
                md5cycle(state, tail);
                for (i=0; i<16; i++) tail[i] = 0;
                }
                tail[14] = n*8;
                md5cycle(state, tail);
                return state;
            }

            /* il faut qu'il y ait un support pour Unicode ici,
            * à moins que nous prétendions que nous pouvons redéfinir l'algorithme MD-5
            * pour les caractères multi-octets (peut-être
            * en ajoutant tous les quatre caractères de 16 bits et
            * en raccourcissant la somme à 32 bits). Sinon,
            * Je suggère d'exécuter MD-5 comme si chaque caractère était de deux octets.
            * était de deux octets - par exemple, 0040 0025 = @% - mais alors
            * Comment faire correspondre une somme MD-5 ordinaire ?
            * Il n'y a aucun moyen de normaliser le texte en quelque chose comme UTF-8 avant la transformation.
            * comme UTF-8 avant transformation ; le coût de la vitesse est
            * Le coût de la vitesse est tout à fait prohibitif. La norme JavaScript
            * doit elle-même se pencher sur cette question : elle devrait commencer à
            * fournir un accès aux chaînes de caractères en UTF-8 préformé
            * tableaux de valeurs non signées de 8 bits.
            */
            
            function md5blk(s) { /* I figured global was faster.   */
                var md5blks = [], i; /* Andy King said do it this way. */
                for (i=0; i<64; i+=4) {
                md5blks[i>>2] = s.charCodeAt(i)
                + (s.charCodeAt(i+1) << 8)
                + (s.charCodeAt(i+2) << 16)
                + (s.charCodeAt(i+3) << 24);
                }
                return md5blks;
            }

            var hex_chr = '0123456789abcdef'.split('');

            function rhex(n) {
                var s='', j=0;
                for(; j<4; j++)
                s += hex_chr[(n >> (j * 8 + 4)) & 0x0F]
                + hex_chr[(n >> (j * 8)) & 0x0F];
                return s;
            }

            function hex(x) {
                for (var i=0; i<x.length; i++)
                x[i] = rhex(x[i]);
                return x.join('');
            }

            function md5(s) {
                return hex(md51(s));
            }

            /* cette fonction est beaucoup plus rapide,
            donc si possible nous l'utilisons. Certains IE
            sont les seuls que je connaisse qui
            ont besoin de la deuxième fonction idiote
            générée par une clause if.  */

            function add32(a, b) {
                return (a + b) & 0xFFFFFFFF;
            }

            if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
                function add32(x, y) {
                var lsw = (x & 0xFFFF) + (y & 0xFFFF),
                msw = (x >> 16) + (y >> 16) + (lsw >> 16);
                return (msw << 16) | (lsw & 0xFFFF);
                }
            }
        
            /*
            *   Cette fonction va permettre de créer une chaine de caractère (date/h)
            *   différente toutes les 3 heures
            *
            */
            function roundHours(precision, d){
                precision= precision || 1;
                d= d? new Date(d):new Date();
                if(d.getSeconds()>30) d.setMinutes(d.getMinutes()+1);
                if(d.getMinutes>30) hours+= 1;
                var hours= d.getHours(), diff= hours%precision;
                if(diff>precision/2) hours+= (precision-diff);
                else hours-= diff;
                d.setHours(hours, 0, 0, 0);
                return d.toLocaleString();
            }

            /*
            *   Enfin on crée un userId sur la base 
            *   des données du navigateur + roundHour
            */
            function buildUserId(){
                return md5(roundHours(3, new Date()) + window.navigator.userAgent + window.navigator.languages.toString() );
            }
    </script>


    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script type="text/plain" data-cli-class="cli-blocker-script" data-cli-label="Google Tag Manager"  data-cli-script-type="" data-cli-block="true" data-cli-block-if-ccpa-optout="false" data-cli-element-position="body" async src="//www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX-XX"></script>
    <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());

    <!-- REPLACE YOUR ID HERE -->
    gtag('config', 'UA-XXXXXXX-XX',  
        { 
            'anonymize_ip': true,
            'allow_ad_personalization_signals': false,
            'allow_google_signals': false,
            'ad_storage': 'denied',
            client_storage: 'none',
            client_id: buildUserId()
        }
    );
    </script>
				
			

4 Acceptez les clauses contractuelles types dans Google Analytics directement:

5 Ne pas transféré de données personnelles dans Google analytics pour que ce paramétrage soit valable.

Tiri

Je suis un développeur web qui cherche à résoudre les problèmes du monde réel. J'ai la passion d'apprendre et de partager mes connaissances avec les autres, aussi publiquement que possible.

AllEscort