Mission: E-Mail-Tarnung
Erst nachdem ich einen Endpunkt zur Newsletter-Anmeldung auf der Webseite unseres Vereins bereitgestellt hatte, wurde mir klar wie viele Bots bzw. Crawler im Internet herumirren und jede noch so unbekannte Webseite nach Daten absuchen und missbrauchen. Bereits eine Stunde nach dem Release begann mein Mailserver, mir in Dauerschleife Benachrichtigungen über unzustellbare E-Mails zu senden – verursacht durch zuvor generierte Adressen, die ein Bot in das Eingabefeld eingefügt und abgesendet hatte. Erst mit der Integration eines Captchas konnte ich dem ganzen Abhilfe verschaffen.
Da stellte sich bei der Erstellung meiner eigenen Webseite die Frage: Wie verstecke ich meine E-Mail vor diesen Bots?
Nach kurzer Recherche stieß ich auf diesen Artikel: https://spencermortensen.com/articles/email-obfuscation
Dort werden mehrere Methoden zur E-Mail Verschleierung vorgestellt.
Hinsichtlich der Kompatibilität zum hier genutzten Hugo-Theme entschied ich mich für die Rot18-Methode.
Dabei wird im Vorfeld die E-Mail in eine Zeichenkette verschlüsselt und wie üblich dem href-Attribut zugewiesen. Erst beim Laden der Seite wird diese wieder entschlüsselt.
So wird zum Beispiel <a class="email" href="znvygb:nd@rznvy.fcraprezbegrafra.pbz">email</a>
zu <a class="email" href="mailto:aq@email.spencermortensen.com">email</a>
umgewandelt.
Besonders interessant erschien mir außerdem die User-Interaction-Obfuscation, bei der die E-Mail-Adresse erst bei einer Mausinteraktion hinzugefügt wird.
Warum also nicht beides kombinieren? Gesagt, getan - hier das Ergebnis:
document.addEventListener('DOMContentLoaded', function () {
const rot18 = new Rot18();
const coder = new LinkCoder(rot18);
const listener = new Listener();
listener.decode = function () {
document.querySelectorAll('.email').forEach(coder.decode.bind(coder));
};
listener.on();
});
// Rot18
function Rot18() {
this.lowercase = new Rotater('a', 'z', 21);
this.uppercase = new Rotater('A', 'Z', 21);
this.digits = new Rotater('0', '9', 8);
}
Rot18.prototype.encode = function (text) {
text = this.lowercase.encode(text);
text = this.uppercase.encode(text);
text = this.digits.encode(text);
return text;
};
Rot18.prototype.decode = function (text) {
text = this.lowercase.decode(text);
text = this.uppercase.decode(text);
text = this.digits.decode(text);
return text;
};
// Rotater
function Rotater(c0, cN, offset) {
const code0 = c0.charCodeAt(0);
const codeN = cN.charCodeAt(0);
const length = codeN - code0 + 1;
this.re = new RegExp('[' + c0 + '-' + cN + ']', 'g');
this.forward = Rotater.rotate.bind(Rotater, code0, length, offset);
this.backward = Rotater.rotate.bind(Rotater, code0, length, length - offset);
}
Rotater.rotate = function (code0, length, offset, ci) {
const iBefore = ci.charCodeAt(0) - code0;
const iAfter = (iBefore + offset) % length;
return String.fromCharCode(iAfter + code0);
};
Rotater.prototype.encode = function (text) {
return text.replace(this.re, this.forward);
};
Rotater.prototype.decode = function (text) {
return text.replace(this.re, this.backward);
};
// LinkCoder
function LinkCoder(coder) {
this.coder = coder;
}
LinkCoder.prototype.encode = function (a) {
const text = a.getAttribute('href');
const code = this.coder.encode(text);
a.setAttribute('href', code);
};
LinkCoder.prototype.decode = function (a) {
const code = a.getAttribute('href');
const text = this.coder.decode(code);
a.setAttribute('href', text);
};
// Listener
function Listener() { }
Listener.prototype.decode = null;
Listener.prototype.on = function () {
this.listener = this.__onInteraction.bind(this);
document.addEventListener('mouseenter', this.listener, true);
document.addEventListener('focus', this.listener, true);
};
Listener.prototype.off = function () {
document.removeEventListener('mouseenter', this.listener, true);
document.removeEventListener('focus', this.listener, true);
delete this.listener;
};
Listener.prototype.__onInteraction = function () {
this.off();
this.decode();
};