fbPGP – Facebook mit GnuPG

Wie ihr letztens wahrscheinlich gelesen habt, hat sich ein Amtsrichter aus Reutlingen dazu entschieden, in einem Einbruchsfall den Facebook-Account eines Angeklagten zu beschlagnahmen. Die Idee: Der Angeklagte soll dem Einbrecher via Facebook einen Tipp gegeben haben, wie dieser am besten einbrechen könne. Mit Hilfe der Beschlagnahmung versucht das Gericht nun, an die privaten Facebook-Nachrichten des Angeklagten zu kommen - soweit so unglaublich Übelkeit erregend.

Aber was würde es denn bedeuten, wenn das Gericht hier tatsächlich Einblick in die privaten Nachrichten erhalten würde? Im ersten Schritt würde das bedeuten, dass private Nachrichten bei Facebook nicht dem gleichen Schutz unterliegen, wie private Briefe. Es würde aber auch bedeuten, dass zukünftig wegen jeder Bagatelle die intimsten Nachrichten Gegenstand eines Prozesses werden könnten.

Um dem ein bisschen entgegen zu wirken, wäre es sinnvoll, über Facebook versendete Nachrichten einfach verschlüsseln zu können, sodass nur der tatsächliche Empfänger sie lesen kann. Leider ist soetwas oft sehr umständlich. Entweder man verwendet einen separaten Client, der diese Funktionalität anbietet, oder aber man muss immer ein Verschlüsselungsprogramm laufen lassen, in das man die Texte rein- und wieder rauskopiert.

Aus diesem Grund habe ich mich mal an einem Proof-of-Concept versucht: fbPGP - Facebook with GnuPG. Dort habe ich ein Greasemonkey-Script geschrieben, das sich in die Nachrichtenseite von Facebook einklinkt. Durch dieses wird die Facebook-Nachrichten-Seite um das ver- und entschlüsseln von versendeten/empfangenen Nachrichten erweitert.

Verschlüsselte Nachricht in Facebook

Das ganze funktioniert wie folgt: Man hat einen Server (z.B. lokal), auf dem zwei kleine PHP-Scripte bereitstehen. Auf diesem Server ist zudem GnuPG installiert. Wenn man auf der Facebook-Nachrichten-Seite einen Text abschickt, wird dieser an eines der PHP-Scripte übergeben, von diesem verschlüsselt und die verschlüsselte Version an die Facebook-Nachrichten-Seite zurückgegeben. Diese sendet diese dann an den Empfänger.
Wenn man sich eine empfangene Nachricht ansieht, wird diese genommen, an das andere PHP-Script übergeben, von diesem wieder entschlüsselt und dann auf der Facebook-Nachrichten-Seite zur Anzeige gebracht.

Entschlüsselte Nachricht in Facebook

Die Besonderheit an der verwendeten Verschlüsselung ist, dass der Schlüsselaustausch problemlos möglich ist. Das kann man sich wie einen Briefkasten vorstellen: Jeder hat Zugang zu dem Briefkasten und kann dort eine Nachricht einwerfen, aber nur der Briefkastenbesitzer kann diesen öffnen und die Nachrichten lesen.
Genauso verhält es sich bei GnuPG. Es gibt dort zwei "Passwörter" (Schlüssel genannt) - ein öffentliches und ein privates. Das öffentliche kann man (wenn man will) auf Plakate drucken und überall aufhängen, jedem per E-Mail schicken, auf seiner Webseite präsentieren und soweiter. Dieses öffentliche "Passwort" wird zum Verschlüsseln von Nachrichten verwendet (= das Einwerfen der Nachricht in den Briefkasten). Das private "Passwort" hingegen ist geheim - es darf an niemanden weitergegeben werden. Jeder, der das private "Passwort" kennt, kann die empfangenen Nachrichten entschlüsseln.

Die Idee ist nun, dass dem PHP-Script, das die Verschlüsselung vornimmt, sämtliche öffentlichen Schlüssel der angeschriebenen Facebook-Kontakte bekannt sind. Praktisch wäre hier natürlich, wenn es sich diese automatisch irgendwoher besorgen könnte. Das ist jedoch noch nicht implementiert.
Das PHP-Script, das die Verschlüsselung vornimmt, muss natürlich den eigenen privaten Schlüssel kennen. Deshalb ist es sinnvoll, dieses Script lokal auf dem eigenen Rechner zu verwenden. Zudem ist es sinnvoll, den privaten Schlüssel noch einmal mit einem Passwort zu versehen.

Warum erzähle ich euch das alles? Ich denke, dass langsam aber sicher etwas getan werden muss. Überall versuchen die Regierungen dieser Welt, immer weiter in den privaten Lebensraum vorzudringen. Das beginnt beim richterlichen Eingriff in die private Kommunikation und hört beim Infizieren von privaten Rechnern mit Schadsoftware durch Bundesbehörden noch lange nicht auf.
Ich denke, dass es notwendig ist, dem normalen Internetnutzer Tools an die Hand zu geben, mit denen er sich ohne viel Aufwand vor solchen Eingriffen schützen kann. Und damit meine ich nicht den e-Postbrief oder DE-Mail, die zwar angeblich "sicher" sind, jedoch genau an ihren Übergangspunkten problemlos mitgelesen werden können. Wie man an der krampfhaften Erklärung oben sieht, ist das jedoch nicht so einfach. Klar: Für mich ist es kein großes Problem, einen Webserver mit PHP einzurichten, GnuPG zu installieren und 2 PHP-Scripte darüber laufen zu lassen. Für andere ist das jedoch unverständliches Hexenwerk. Auch diese Leuten muss man mit einfach nutzbaren, sicheren Lösungen in der heutigen, digitalen Welt unterstützen. In meinen Augen ist es deshalb auch sehr wichtig, dass sich diese Lösungen möglichst nahtlos in die vertraute Umgebung einbetten.

Ich hoffe, es finden sich Leute, die sich dieses Themas annehmen.
Verschlüsselnde Grüße, Kenny

P.S.: Hier als Backup noch einmal die einzelnen Quelltexte.

fbPGP.user.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// ==UserScript==
// @name           fbPGP
// @namespace      https://fbpgp.nhg.name/
// @include        https://www.facebook.com/messages/*
// ==/UserScript==

var FBPGP_DECRYPT = "https://localhost/decrypt.php";
var FBPGP_ENCRYPT = "https://localhost/encrypt.php";

var FBPGP_DECRYPT_PASSWORD = "PassphraseOfKeystore"; //!!! CHANGE THIS

// DO NOT EDIT BELOW THIS LINE

String.prototype.contains   = function(sub) { return this.indexOf(sub) !== -1; };
String.prototype.startsWith = function(sub) { return this.indexOf(sub) == 0; };

// fbPGP message identifier
var FB_FBPGP_MESSAGE_IDENT = "<b>fbPGP encrypted:</b><br/>";

// access token
var FB_ACCESS_TOKEN_LINK   = "https://graph.facebook.com/me/home?access_token=";
var FB_ACCESS_TOKEN_TEXT   = "">https://graph.facebook.com/me/";
var FB_GRAPH_API_REFERENCE = "https://developers.facebook.com/docs/reference/api/";

// FBPGP decrypt/encrypt link
var FB_FBPGP_ME_PARAM      = "me=";
var FB_FBPGP_MESSAGE_PARAM = "&message=";
var FB_FBPGP_PASS_PARAM    = "&pass=";
var FB_FBPGP_YOU_PARAM     = "&you=";

// FBPGP public key link
var FB_FBPGP_TOKEN_START = "#!FBPGP!#";
var FB_FBPGP_TOKEN_STOP  = "#!/FBPGP!#";
var FB_FBPGP_TOKEN_URL_A = "https://graph.facebook.com/";
var FB_FBPGP_TOKEN_URL_B = "?access_token=";

// my link
var FBXWELCOMEBOXNAME   = "fbxWelcomeBoxName";
var PAGELET_WELCOME_BOX = "pagelet_welcome_box";

// your link
var MESSAGINGREADPARTICIPANTS = "MessagingReadParticipants";

// user name
var FB_USERLINK_START_A = "https://www.facebook.com/profile.php?id=";
var FB_USERLINK_START_B = "http://www.facebook.com/profile.php?id=";
var FB_USERLINK_START_C = "https://www.facebook.com/";
var FB_USERLINK_START_D = "http://www.facebook.com/";

// messaging
var ID_PREFIX = "id.";

var MESSAGE_BODY             = "message_body";
var MESSAGINGCOMPOSERBODY    = "MessagingComposerBody";
var MESSAGINGCOMPOSERFORM    = "MessagingComposerForm";
var MESSAGINGINLINECOMPOSER  = "MessagingInlineComposer";
var MESSAGINGMESSAGES        = "MessagingMessages";
var MESSAGINGSENDREPLYBUTTON = "MessagingSendReplyButton";
var MESSAGINGSHELFCONTENT    = "MessagingShelfContent";

// decrypt message with remote GPG PHP script
function decryptMessage(message, myName, yourName) {
  var result = null;

  var decryptedMessage = GM_xmlhttpRequest({data        : FB_FBPGP_ME_PARAM + encodeURIComponent(myName) + FB_FBPGP_MESSAGE_PARAM + encodeURIComponent(message) + FB_FBPGP_PASS_PARAM + encodeURIComponent(FBPGP_DECRYPT_PASSWORD) + FB_FBPGP_YOU_PARAM + encodeURIComponent(yourName),
                                            headers     : {"Content-Type" : "application/x-www-form-urlencoded"},
                                            method      : "POST",
                                            synchronous : true,
                                            url         : FBPGP_DECRYPT});

  if (decryptedMessage != null) {
    decryptedMessage = decryptedMessage.responseText;
    if (decryptedMessage != null) {
      result = decryptedMessage;
    }
  }
 
  return result;
}

// encrypt message with remote GPG PHP script
function encryptMessage(message, myName, yourName) {
  var result = null;

  var encryptedMessage = GM_xmlhttpRequest({data        : FB_FBPGP_ME_PARAM + encodeURIComponent(myName) + FB_FBPGP_MESSAGE_PARAM + encodeURIComponent(message) + FB_FBPGP_YOU_PARAM + encodeURIComponent(yourName),
                                            headers     : {"Content-Type" : "application/x-www-form-urlencoded"},
                                            method      : "POST",
                                            synchronous : true,
                                            url         : FBPGP_ENCRYPT});

  if (encryptedMessage != null) {
    encryptedMessage = encryptedMessage.responseText;
    if (encryptedMessage != null) {
      result = encryptedMessage;
    }
  }
 
  return result;
}

function getFBPGPText(text) {
  var result = null;

  if ((text.contains(FB_FBPGP_TOKEN_START)) &&
      (text.contains(FB_FBPGP_TOKEN_STOP))) {
    result = text;

    result = result.substring(result.indexOf(FB_FBPGP_TOKEN_START) + FB_FBPGP_TOKEN_START.length);
    result = result.substring(0, result.indexOf(FB_FBPGP_TOKEN_STOP));
  }

  return result;
}

// extract link of you
function getMyLink() {
  var result = null;

  var myLink = document.getElementById(PAGELET_WELCOME_BOX);
  if (myLink != null) {
    var links = myLink.getElementsByTagName("a");
    for (var index = 0; index < links.length; index++) {  
      if (links[index].className.contains(FBXWELCOMEBOXNAME)) {
        result = links[index].href;
       
        break;
      }
    }
  }
 
  return result;
}

// extract link of user you're messaging with
function getYourLink() {
  var result = null;

  var yourLink = document.getElementById(MESSAGINGREADPARTICIPANTS);
  if (yourLink != null) {
    var links = yourLink.getElementsByTagName("a");
    if (links.length == 1) {
      if ((links[0].href.startsWith(FB_USERLINK_START_A)) ||
          (links[0].href.startsWith(FB_USERLINK_START_B))) {
        result = links[0].href;
      }
    }
  }
 
  return result;
}

// extract user name from facebook profile link
function getUserName(userLink) {
  var result = null;

  if (userLink != null) {
    if (userLink.toLowerCase().startsWith(FB_USERLINK_START_A)) {
      result = userLink.substring(FB_USERLINK_START_A.length);
    } else {
      if (userLink.toLowerCase().startsWith(FB_USERLINK_START_B)) {
        result = userLink.substring(FB_USERLINK_START_B.length);
      } else {
        if (userLink.toLowerCase().startsWith(FB_USERLINK_START_C)) {
          result = userLink.substring(FB_USERLINK_START_C.length);
        } else {
          if (userLink.toLowerCase().startsWith(FB_USERLINK_START_D)) {
            result = userLink.substring(FB_USERLINK_START_D.length);
          }
        }
      }
    }
  }
 
  return result;
}

function hookGUI(myName, yourName) {
  var shelfContent = document.getElementById(MESSAGINGSHELFCONTENT);
  if (shelfContent != null) {
    // show that hooking has worked
    shelfContent.style.backgroundColor = "#FBBFBF";
  }

  var replyButton = document.getElementById(MESSAGINGSENDREPLYBUTTON);
  if (replyButton != null) {
    // show that hooking has worked
    replyButton.style.backgroundColor = "#FB0000";
    replyButton.style.backgroundImage = "none";

    // get reference of textarea
    var messageBody = null;    
    var inlineComposer = document.getElementById(MESSAGINGINLINECOMPOSER);
    if (inlineComposer != null) {
      var textareas = inlineComposer.getElementsByTagName("textarea");
      if (textareas.length == 1) {
        if (textareas[0].className.contains(MESSAGINGCOMPOSERBODY)) {
          messageBody = textareas[0];
        }
      }
    }

    var replyInput = document.getElementById(replyButton.getAttribute("for"));
    if (replyInput != null) {
      // add fake reply button
      var newButton = document.createElement("input");
      newButton.setAttribute("type", "button");
      newButton.setAttribute("value", replyInput.value);
      replyInput.parentNode.appendChild(newButton);

      // hide real reply button
      replyInput.style.display    = "none";
      replyInput.style.visibility = "hidden";

      // set action handlers
      var newButtonClicked = function () { var result = encryptMessage(messageBody.value, myName, yourName); if ((result != null) && (result.length > 0)) { messageBody.value = FB_FBPGP_TOKEN_START + result + FB_FBPGP_TOKEN_STOP; } };
      newButton.addEventListener("click", newButtonClicked, true);
    }
  }

  // decrypt received messages
  var messaging = document.getElementById(MESSAGINGMESSAGES);
  if (messaging != null) {
    var messages = messaging.getElementsByTagName("div");
    for (var index = 0; index < messages.length; index++) {
      if (messages[index].id.startsWith(ID_PREFIX)) {
        var pgpText = getFBPGPText(messages[index].firstChild.innerHTML);
        if (pgpText != null) {
          messages[index].firstChild.innerHTML = FB_FBPGP_MESSAGE_IDENT + decryptMessage(pgpText, myName, yourName);
        }
      }
    }
  }
}

function main() {
  var myLink = getMyLink();
  if (myLink != null) {
    var myName = getUserName(myLink);
    if (myName != null) {
      var yourLink = getYourLink();
      if (yourLink != null) {
        var yourName = getUserName(yourLink);
        if (yourName != null) {
          hookGUI(myName, yourName);
        }
      }
    }
  }
}
setTimeout(main, 5000);

/*
//!!! quick and dirty hack to get a valid access token
function getAccessToken() {
  var result = null;

  var accessToken = GM_xmlhttpRequest({method      : "GET",
                                       synchronous : true,
                                       url         : FB_GRAPH_API_REFERENCE});

  if (accessToken != null) {
    accessToken = accessToken.responseText;
    if (accessToken != null) {
      if (accessToken.contains(FB_ACCESS_TOKEN_LINK)) {
        accessToken = accessToken.substring(accessToken.indexOf(FB_ACCESS_TOKEN_LINK) + FB_ACCESS_TOKEN_LINK.length);
        accessToken = accessToken.substring(0, accessToken.indexOf(FB_ACCESS_TOKEN_TEXT));
       
        result = accessToken;
      }
    }
  }

  return result;
}

// extract link to someone's PGP public key
function getFBPGPLink(yourName, accessToken) {
  var result = null;

  var fbpgpLink = GM_xmlhttpRequest({method      : "GET",
                                     synchronous : true,
                                     url         : FB_FBPGP_TOKEN_URL_A + encodeURIComponent(yourName) + FB_FBPGP_TOKEN_URL_B + encodeURIComponent(accessToken)});

  if (fbpgpLink != null) {
    fbpgpLink = fbpgpLink.responseText;
    if (fbpgpLink != null) {
      result = getFBPGPText(fbpgpLink);
    }
  }
 
  return result;
}

// find public key
var accessToken = getAccessToken();
if (accessToken != null) {
  alert("accessToken = " + accessToken);

  var fbpgpLink = getFBPGPLink(yourName, accessToken);
  if (fbpgpLink != null) {
    alert("fbpgpLink = " + fbpgpLink);
  }
}
*/

encrypt.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
  $gpg  = "/usr/bin/gpg";
  $tmp  = "/tmp/fbpgp_";

  $recipient = null;
  if (isset($_POST["you"])) {
    $recipient = "fb" . sha1(rawurldecode($_POST["you"])) . "@localhost";
  }
  $message = null;
  if (isset($_POST["message"])) {
    $message = rawurldecode($_POST["message"]);
  }

  if (($recipient != null) && ($message != null)) {
    $file_in  = uniqid($tmp, true);
    $file_out = uniqid($tmp, true);

    file_put_contents($file_in, $message);

    exec("$gpg --recipient " . escapeshellarg($recipient) . " --output " . escapeshellarg($file_out) . " --encrypt " . escapeshellarg($file_in));

    $result = file_get_contents($file_out);
    $result = base64_encode($result);

    print($result);

    unlink($file_in);
    unlink($file_out);
  }
?>

decrypt.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
  $echo = "/bin/echo";
  $gpg  = "/usr/bin/gpg";
  $tmp  = "/tmp/fbpgp_";

  $recipient = null;
  if (isset($_POST["me"])) {
    $recipient = "fb" . sha1(rawurldecode($_POST["me"])) . "@localhost";
  }
  $password = null;
  if (isset($_POST["pass"])) {
    $password = rawurldecode($_POST["pass"]);
  }
  $message = null;
  if (isset($_POST["message"])) {
    $message = base64_decode(rawurldecode($_POST["message"]));
  }

  if (($recipient != null) && ($password != null) && ($message != null)) {
    $file_in  = uniqid($tmp, true);
    $file_out = uniqid($tmp, true);

    file_put_contents($file_in, $message);

    exec("$echo " . escapeshellarg($password) . " | $gpg --batch --passphrase-fd=0 --local-user " . escapeshellarg($recipient) . " --output " . escapeshellarg($file_out) . " --decrypt " . escapeshellarg($file_in));

    $result = file_get_contents($file_out);
    $result = str_replace("<", "&lt;", $result);
    $result = str_replace(">", "&gt;", $result);

    print($result);

    unlink($file_in);
    unlink($file_out);
  }
?>

10 Kommentare » Schreibe einen Kommentar

  1. Ich habe mir gerade http://www.mailvelope.com/ installiert um mein GMAIL-Konto abzusichern. Ich habe mal eben ausprobiert, Facebook zur Watchlist hinzuzufügen. Und ich muss sagen: für die Wall funktioniert es schonmal. Nachrichten müsste ich noch ausprobieren, sollte aber auch gehen.
    Das Konzept ist sehr ähnlich nur eben, dass es komplett JS-basiert ist.

  2. Super Proof of Concept!!
    Eine für die Praxis geeignete Lösung ist PrivKey. Es ist ein Add-on für Firefox und Chrome, das Facebook-Nachrichten auf dem Rechner des Anwenders nach dem OpenPGP-Standard verschlüsselt. Es ist vollständig in JavaScript implementiert und kann daher von jedem auf korrekte Funktionalität hin überprüft werden.
    Freue mich über Anregungen!
    http://www.facebook.com/PrivKey

  3. Klingt interessant. Aber ich denke auch, dass das Aufsetzen für Otto Normaluser zu schwer sein wird. Würde mich echt freuen, sowas als einfach zu bedienendes, portables Programm o. ä. zu sehen.

    • Wie gesagt, das ist genau das, was ich mir wünschen würde. Eine Lösung, die out-of-the-Box mit verschiedenen, viel genutzten Diensten funktionieren und gleichzeitig auch durch unbedarfte Nutzer verwendet werden kann. Vor allem zweites spricht dann leider für eine zentrale Schlüsselverwaltung, die wiederum hohe Anforderungen an die Sicherung stellt.

      • Helfen könnte sicher das hier http://alexanderwillner.github.com/GPGMail_Mobile/#home

        Wo die Publickeys liegen ist eigentlich völlig uninteressant. LocalStorage oder IndexedDB reichen völlig. Wem das nicht reicht, kann die Daten mit seinem Privatekey verschlüsseln und den speichert man passwortverschlüsselt im Browser. Seh ich jetzt eigentlich gar nicht so das große Problem drin.

        Mit echten Browsern (also kein IE) dauert das Ver- und Entschlüsseln auch von längeren Texten auf hoher Sicherheitsstufe wenige Sekunden. Das einzige Problem, was dann noch bleibt ist das Erzeugen der Keys. Das dauert sehr sehr lange. Bei 256 Bit ist man schon locker bei 20 Minuten auch mit V8 oder Jägermonkey. Selbst 128 Bit dauern auf meinem Laptop schon 4 Minuten. Das bekommt man natürlich keinem verkauft, aber ich sehe da auch keine Lösung für. 🙁

    • Sagen wir so, eine reine JS-Lösung wäre sicherlich portabler gewesen. Einfacher wäre sie jedoch unter Umständen nicht. Ich denke dabei z.B. in Richtung Schlüsselverwaltung. Die andere Frage ist, ob diese Lösung wirklich genauso sicher ist. Durch das Verwenden der originalen GnuPG Binary weiß ich (bzw. gehe ich davon aus), dass die Implementierung zigfach geprüft worden ist. Dadurch, dass man den benötigten Server auch lokal installieren kann, hätte man volle Kontrolle über den Keystore (man könnte den verwenden, den auch das Mailprogramm nutzt). Man könnte öffentliche Schlüsselserver anbinden, etc. pp. - all diese Möglichkeiten würden (ohne Eigenimplementierung) erst einmal wegfallen. Aus diesem Grund (und aufgrund der Zeitersparnis) habe ich mich für die hybride Lösung entschieden.

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.