diff --git a/data/gui/chat_control.ui b/data/gui/chat_control.ui
index 7301c02a346b231bd6c97a3d79ab85e6b471be40..323998e5b468de4e4fff08acae7707561e579bf5 100644
--- a/data/gui/chat_control.ui
+++ b/data/gui/chat_control.ui
@@ -939,6 +939,26 @@
                     <property name="position">11</property>
                   </packing>
                 </child>
+                <child>
+                  <object class="GtkMenuButton" id="encryption_menu">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="relief">none</property>
+                    <child>
+                      <object class="GtkImage">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="icon_name">channel-secure-symbolic.symbolic</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">True</property>
+                    <property name="position">12</property>
+                  </packing>
+                </child>
                 <child>
                   <object class="GtkBox" id="audio_buttons_hbox">
                     <property name="can_focus">False</property>
@@ -1039,7 +1059,7 @@ audio-mic-volume-low</property>
                   <packing>
                     <property name="expand">False</property>
                     <property name="fill">True</property>
-                    <property name="position">12</property>
+                    <property name="position">13</property>
                   </packing>
                 </child>
                 <child>
@@ -1050,12 +1070,9 @@ audio-mic-volume-low</property>
                   <packing>
                     <property name="expand">True</property>
                     <property name="fill">True</property>
-                    <property name="position">13</property>
+                    <property name="position">14</property>
                   </packing>
                 </child>
-                <child>
-                  <placeholder/>
-                </child>
                 <child>
                   <object class="GtkButton" id="send_button">
                     <property name="label" translatable="yes">_Send</property>
@@ -1077,6 +1094,9 @@ audio-mic-volume-low</property>
                     <property name="position">15</property>
                   </packing>
                 </child>
+                <child>
+                  <placeholder/>
+                </child>
               </object>
               <packing>
                 <property name="expand">False</property>
diff --git a/data/gui/contact_context_menu.ui b/data/gui/contact_context_menu.ui
index 85f1c038701b9049a09924774d164956cf9b7082..98d846d40abdbc5891c4cfbba95a3454bfb5f840 100644
--- a/data/gui/contact_context_menu.ui
+++ b/data/gui/contact_context_menu.ui
@@ -45,28 +45,6 @@
         <property name="use_underline">True</property>
       </object>
     </child>
-    <child>
-      <object class="GtkSeparatorMenuItem" id="encryption_separator">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkCheckMenuItem" id="toggle_gpg_menuitem">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="label" translatable="yes">Toggle Open_PGP Encryption</property>
-        <property name="use_underline">True</property>
-      </object>
-    </child>
-    <child>
-      <object class="GtkCheckMenuItem" id="toggle_e2e_menuitem">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="label" translatable="yes">Toggle End to End Encryption</property>
-        <property name="use_underline">True</property>
-      </object>
-    </child>
     <child>
       <object class="GtkSeparatorMenuItem" id="menuitem3">
         <property name="visible">True</property>
diff --git a/data/gui/groupchat_control.ui b/data/gui/groupchat_control.ui
index 7ab14ecca830d687858ee6377b5674aa0e4c0cd8..7a0d7e8f4181fabf2f590751ff820c1c6bfd89f9 100644
--- a/data/gui/groupchat_control.ui
+++ b/data/gui/groupchat_control.ui
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.18.3 -->
+<!-- Generated with glade 3.20.0 -->
 <interface>
   <requires lib="gtk+" version="3.12"/>
   <object class="GtkImage" id="image1">
@@ -127,19 +127,56 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkScrolledWindow" id="message_scrolledwindow">
+                  <object class="GtkBox" id="hbox">
                     <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hscrollbar_policy">never</property>
-                    <property name="vscrollbar_policy">never</property>
-                    <property name="shadow_type">in</property>
+                    <property name="can_focus">False</property>
+                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                    <property name="spacing">3</property>
                     <child>
-                      <placeholder/>
+                      <object class="GtkButton" id="authentication_button">
+                        <property name="name">ChatControl-AuthenticationButton</property>
+                        <property name="can_focus">True</property>
+                        <property name="focus_on_click">False</property>
+                        <property name="receives_default">True</property>
+                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="no_show_all">True</property>
+                        <property name="relief">none</property>
+                        <child>
+                          <object class="GtkImage" id="lock_image">
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="stock">gtk-dialog-authentication</property>
+                            <property name="icon_size">1</property>
+                          </object>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkScrolledWindow" id="message_scrolledwindow">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="hscrollbar_policy">never</property>
+                        <property name="vscrollbar_policy">never</property>
+                        <property name="shadow_type">in</property>
+                        <child>
+                          <placeholder/>
+                        </child>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
                     </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
-                    <property name="fill">True</property>
+                    <property name="fill">False</property>
                     <property name="position">1</property>
                   </packing>
                 </child>
@@ -214,12 +251,12 @@
               <object class="GtkButton" id="formattings_button">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
+                <property name="focus_on_click">False</property>
                 <property name="receives_default">True</property>
                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                 <property name="has_tooltip">True</property>
                 <property name="tooltip_text" translatable="yes">Show a list of formattings</property>
                 <property name="relief">none</property>
-                <property name="focus_on_click">False</property>
                 <child>
                   <object class="GtkImage" id="image11">
                     <property name="visible">True</property>
@@ -364,12 +401,12 @@
               <object class="GtkButton" id="muc_window_actions_button">
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
+                <property name="focus_on_click">False</property>
                 <property name="receives_default">True</property>
                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                 <property name="has_tooltip">True</property>
                 <property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
                 <property name="relief">none</property>
-                <property name="focus_on_click">False</property>
                 <child>
                   <object class="GtkImage" id="image1344">
                     <property name="visible">True</property>
@@ -397,7 +434,24 @@
               </packing>
             </child>
             <child>
-              <placeholder/>
+              <object class="GtkMenuButton" id="encryption_menu">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="relief">none</property>
+                <child>
+                  <object class="GtkImage">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="icon_name">channel-secure-symbolic.symbolic</property>
+                  </object>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">10</property>
+              </packing>
             </child>
             <child>
               <object class="GtkButton" id="send_button">
@@ -418,6 +472,9 @@
                 <property name="position">11</property>
               </packing>
             </child>
+            <child>
+              <placeholder/>
+            </child>
           </object>
           <packing>
             <property name="expand">False</property>
diff --git a/data/gui/message_window.ui b/data/gui/message_window.ui
index abc5613f1081e1b13fb6e4ad814eceec40c5566d..0aab2b026609d289960b69e8704db2eed856457a 100644
--- a/data/gui/message_window.ui
+++ b/data/gui/message_window.ui
@@ -64,7 +64,7 @@
       </object>
     </child>
   </object>
-  <object class="GtkWindow" id="message_window">
+  <object class="GtkApplicationWindow" id="message_window">
     <property name="can_focus">False</property>
     <property name="default_width">480</property>
     <property name="default_height">440</property>
diff --git a/src/chat_control.py b/src/chat_control.py
index 83eecc57fe288ad6f089deae49608a377cb2a7c1..2b1e8fa050737915f4309cf4a1175c31eea0a9d2 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -90,10 +90,10 @@ class ChatControl(ChatControlBase):
         ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
             'chat_control', contact, acct, resource)
 
-        self.gpg_is_active = False
         self.last_recv_message_id = None
         self.last_recv_message_marks = None
         self.last_message_timestamp = None
+
         # for muc use:
         # widget = self.xml.get_object('muc_window_actions_button')
         self.actions_button = self.xml.get_object('message_window_actions_button')
@@ -280,28 +280,13 @@ class ChatControl(ChatControlBase):
 
         # Enable encryption if needed
         self.no_autonegotiation = False
-        e2e_is_active = self.session and self.session.enable_encryption
-        gpg_pref = gajim.config.get_per('contacts', contact.jid, 'gpg_enabled')
-
-        # try GPG first
-        if not e2e_is_active and gpg_pref and \
-        gajim.config.get_per('accounts', self.account, 'keyid') and \
-        gajim.connections[self.account].USE_GPG:
-            self.gpg_is_active = True
-            gajim.encrypted_chats[self.account].append(contact.jid)
-            msg = _('OpenPGP encryption enabled')
-            ChatControlBase.print_conversation_line(self, msg, 'status', '',
-                None)
-
-            if self.session:
-                self.session.loggable = gajim.config.get_per('accounts',
-                    self.account, 'log_encrypted_sessions')
-            # GPG is always authenticated as we use GPG's WoT
-            self._show_lock_image(self.gpg_is_active, 'OpenPGP',
-                self.gpg_is_active, self.session and self.session.is_loggable(),
-                True)
 
         self.update_ui()
+        self.set_lock_image()
+
+        self.encryption_menu = self.xml.get_object('encryption_menu')
+        self.encryption_menu.set_menu_model(
+            gui_menu_builder.get_encryption_menu(self.contact, self.type_id))
         # restore previous conversation
         self.restore_conversation()
         self.msg_textview.grab_focus()
@@ -344,7 +329,8 @@ class ChatControl(ChatControlBase):
             send_button = self.xml.get_object('send_button')
             send_button.set_sensitive(True)
         # Formatting
-        if self.contact.supports(NS_XHTML_IM) and not self.gpg_is_active:
+        # TODO: find out what encryption allows for xhtml and which not
+        if self.contact.supports(NS_XHTML_IM):
             self._formattings_button.set_sensitive(True)
             self._formattings_button.set_tooltip_text(_(
                 'Show a list of formattings'))
@@ -856,114 +842,64 @@ class ChatControl(ChatControlBase):
     def on_video_button_toggled(self, widget):
         self.on_jingle_button_toggled(widget, 'video')
 
-    def _toggle_gpg(self):
-        if not self.gpg_is_active and not self.contact.keyID:
-            dialogs.ErrorDialog(_('No OpenPGP key assigned'),
-                _('No OpenPGP key is assigned to this contact. So you cannot '
-                'encrypt messages with OpenPGP.'))
-            return
-        ec = gajim.encrypted_chats[self.account]
-        if self.gpg_is_active:
-            # Disable encryption
-            ec.remove(self.contact.jid)
-            self.gpg_is_active = False
-            loggable = False
-            msg = _('OpenPGP encryption disabled')
-            ChatControlBase.print_conversation_line(self, msg, 'status', '',
-                None)
-            if self.session:
-                self.session.loggable = True
-
-        else:
-            # Enable encryption
-            ec.append(self.contact.jid)
-            self.gpg_is_active = True
-            msg = _('OpenPGP encryption enabled')
-            ChatControlBase.print_conversation_line(self, msg, 'status', '',
-                None)
-
-            loggable = gajim.config.get_per('accounts', self.account,
-                'log_encrypted_sessions')
-
-            if self.session:
-                self.session.loggable = loggable
+    def set_lock_image(self):
+        visible = self.encryption != 'disabled'
+        loggable = self.session and self.session.is_loggable()
 
-                loggable = self.session.is_loggable()
-            else:
-                loggable = loggable and gajim.config.should_log(self.account,
-                        self.contact.jid)
-
-            if loggable:
-                msg = _('Session WILL be logged')
-            else:
-                msg = _('Session WILL NOT be logged')
-
-            ChatControlBase.print_conversation_line(self, msg,
-                    'status', '', None)
+        encryption_state = {'visible': visible,
+                            'enc_type': self.encryption,
+                            'authenticated': False}
 
-        gajim.config.set_per('contacts', self.contact.jid,
-                'gpg_enabled', self.gpg_is_active)
+        gajim.plugin_manager.gui_extension_point(
+            'encryption_state' + self.encryption, self, encryption_state)
 
-        self._show_lock_image(self.gpg_is_active, 'OpenPGP',
-                self.gpg_is_active, loggable, True)
+        self._show_lock_image(**encryption_state)
 
-    def _show_lock_image(self, visible, enc_type='', enc_enabled=False,
-                    chat_logged=False, authenticated=False):
+    def _show_lock_image(self, visible, enc_type='',
+                         authenticated=False):
         """
         Set lock icon visibility and create tooltip
         """
-        #encryption %s active
-        status_string = enc_enabled and _('is') or _('is NOT')
-        #chat session %s be logged
-        logged_string = chat_logged and _('will') or _('will NOT')
-
         if authenticated:
-            #About encrypted chat session
             authenticated_string = _('and authenticated')
             img_path = gtkgui_helpers.get_icon_path('security-high')
         else:
-            #About encrypted chat session
             authenticated_string = _('and NOT authenticated')
             img_path = gtkgui_helpers.get_icon_path('security-low')
         self.lock_image.set_from_file(img_path)
 
-        #status will become 'is' or 'is not', authentificaed will become
-        #'and authentificated' or 'and not authentificated', logged will become
-        #'will' or 'will not'
-        tooltip = _('%(type)s encryption %(status)s active %(authenticated)s.\n'
-                'Your chat session %(logged)s be logged.') % {'type': enc_type,
-                'status': status_string, 'authenticated': authenticated_string,
-                'logged': logged_string}
+        tooltip = _('%(type)s encryption is active %(authenticated)s.') % {'type': enc_type, 'authenticated': authenticated_string}
 
         self.authentication_button.set_tooltip_text(tooltip)
         self.widget_set_visible(self.authentication_button, not visible)
-        self.lock_image.set_sensitive(enc_enabled)
+        self.lock_image.set_sensitive(visible)
 
     def _on_authentication_button_clicked(self, widget):
-        if self.gpg_is_active:
-            dialogs.GPGInfoWindow(self, self.parent_win.window)
-        elif self.session and self.session.enable_encryption:
-            dialogs.ESessionInfoWindow(self.session, self.parent_win.window)
+        gajim.plugin_manager.gui_extension_point(
+            'encryption_dialog' + self.encryption, self)
 
     def send_message(self, message, keyID='', chatstate=None, xhtml=None,
     process_commands=True, attention=False):
         """
         Send a message to contact
         """
+
+        if self.encryption:
+            self.sendmessage = True
+            gajim.plugin_manager.gui_extension_point(
+                    'send_message' + self.encryption, self)
+            if not self.sendmessage:
+                return
+
         message = helpers.remove_invalid_xml_chars(message)
         if message in ('', None, '\n'):
             return None
 
         contact = self.contact
+        keyID = contact.keyID
 
-        encrypted = bool(self.session) and self.session.enable_encryption
-
-        keyID = ''
-        if self.gpg_is_active:
-            keyID = contact.keyID
-            encrypted = True
-            if not keyID:
-                keyID = 'UNKNOWN'
+        chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \
+                'disabled'
 
         chatstate_to_send = None
         if contact is not None:
@@ -999,7 +935,7 @@ class ChatControl(ChatControlBase):
 
         ChatControlBase.send_message(self, message, keyID, type_='chat',
             chatstate=chatstate_to_send, xhtml=xhtml, callback=_on_sent,
-            callback_args=[message, encrypted, xhtml, self.get_seclabel()],
+            callback_args=[message, self.encryption, xhtml, self.get_seclabel()],
             process_commands=process_commands,
             attention=attention)
 
@@ -1040,8 +976,8 @@ class ChatControl(ChatControlBase):
             msg = _('E2E encryption disabled')
             ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
 
-        self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
-                        self.session.is_loggable(), self.session and self.session.verified_identity)
+        self._show_lock_image(e2e_is_active, 'E2E',
+                              self.session and self.session.verified_identity)
 
     def print_session_details(self, old_session=None):
         if isinstance(self.session, EncryptedStanzaSession) or \
@@ -1092,20 +1028,6 @@ class ChatControl(ChatControlBase):
                     msg = _('The following message was NOT encrypted')
                     ChatControlBase.print_conversation_line(self, msg, 'status',
                         '',  tim)
-            else:
-                # GPG encryption
-                if encrypted and not self.gpg_is_active:
-                    msg = _('The following message was encrypted')
-                    ChatControlBase.print_conversation_line(self, msg, 'status',
-                        '', tim)
-                    # turn on OpenPGP if this was in fact a XEP-0027 encrypted
-                    # message
-                    if encrypted == 'xep27':
-                        self._toggle_gpg()
-                elif not encrypted and self.gpg_is_active:
-                    msg = _('The following message was NOT encrypted')
-                    ChatControlBase.print_conversation_line(self, msg, 'status',
-                        '', tim)
             if not frm:
                 kind = 'incoming'
                 name = contact.get_shown_name()
@@ -1115,7 +1037,7 @@ class ChatControl(ChatControlBase):
             else:
                 kind = 'outgoing'
                 name = self.get_our_nick()
-                if not xhtml and not (encrypted and self.gpg_is_active) and \
+                if not xhtml and not encrypted and \
                 gajim.config.get('rst_formatting_outgoing_messages'):
                     from common.rst_xhtml_generator import create_xhtml
                     xhtml = create_xhtml(text)
@@ -1192,9 +1114,8 @@ class ChatControl(ChatControlBase):
     def prepare_context_menu(self, hide_buttonbar_items=False):
         """
         Set compact view menuitem active state sets active and sensitivity state
-        for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
-        tranasports) and file_transfer_menuitem and hide()/show() for
-        add_to_roster_menuitem
+        for history_menuitem (False for tranasports) and file_transfer_menuitem 
+        and hide()/show() for add_to_roster_menuitem
         """
         if gajim.jid_is_transport(self.contact.jid):
             menu = gui_menu_builder.get_transport_menu(self.contact,
@@ -1481,19 +1402,9 @@ class ChatControl(ChatControlBase):
     def _on_message_tv_buffer_changed(self, textbuffer):
         super()._on_message_tv_buffer_changed(textbuffer)
         if textbuffer.get_char_count():
-            e2e_is_active = self.session and \
-                    self.session.enable_encryption
-            e2e_pref = gajim.config.get_per('accounts', self.account,
-                    'enable_esessions') and gajim.config.get_per('accounts',
-                    self.account, 'autonegotiate_esessions') and gajim.config.get_per(
-                    'contacts', self.contact.jid, 'autonegotiate_esessions')
-            want_e2e = not e2e_is_active and not self.gpg_is_active \
-                    and e2e_pref
-
-            if want_e2e and not self.no_autonegotiation \
-            and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
-                self.begin_e2e_negotiation()
-            elif (not self.session or not self.session.status) and \
+            gajim.plugin_manager.gui_extension_point(
+                'typing' + self.encryption, self)
+            if (not self.session or not self.session.status) and \
             gajim.connections[self.account].archiving_136_supported:
                 self.begin_archiving_negotiation()
 
@@ -1684,30 +1595,28 @@ class ChatControl(ChatControlBase):
     def _on_contact_information_menuitem_activate(self, widget):
         gajim.interface.roster.on_info(widget, self.contact, self.account)
 
-    def _on_toggle_gpg_menuitem_activate(self, widget):
-        self._toggle_gpg()
-
     def _on_convert_to_gc_menuitem_activate(self, widget):
         """
         User wants to invite some friends to chat
         """
         dialogs.TransformChatToMUC(self.account, [self.contact.jid])
 
-    def _on_toggle_e2e_menuitem_activate(self, widget):
-        if self.session and self.session.enable_encryption:
-            # e2e was enabled, disable it
-            jid = str(self.session.jid)
-            thread_id = self.session.thread_id
+    def activate_esessions(self):
+        if not (self.session and self.session.enable_encryption):
+            self.begin_e2e_negotiation()
 
-            self.session.terminate_e2e()
+    def terminate_esessions(self):
+        # e2e was enabled, disable it
+        jid = str(self.session.jid)
+        thread_id = self.session.thread_id
 
-            gajim.connections[self.account].delete_session(jid, thread_id)
+        self.session.terminate_e2e()
 
-            # presumably the user had a good reason to shut it off, so
-            # disable autonegotiation too
-            self.no_autonegotiation = True
-        else:
-            self.begin_e2e_negotiation()
+        gajim.connections[self.account].delete_session(jid, thread_id)
+
+        # presumably the user had a good reason to shut it off, so
+        # disable autonegotiation too
+        self.no_autonegotiation = True
 
     def begin_negotiation(self):
         self.no_autonegotiation = True
diff --git a/src/chat_control_base.py b/src/chat_control_base.py
index a81533b6a76f63f36b0e6bbab6d395ef4b53b7e8..299dda9cac96bc9b09b734abbc6b679719acecc7 100644
--- a/src/chat_control_base.py
+++ b/src/chat_control_base.py
@@ -34,6 +34,7 @@ from gi.repository import Gdk
 from gi.repository import Pango
 from gi.repository import GObject
 from gi.repository import GLib
+from gi.repository import Gio
 import gtkgui_helpers
 import message_control
 import dialogs
@@ -395,6 +396,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
                 self._on_window_motion_notify)
             self.handlers[id_] = parent_win.window
 
+        self.encryption = 'disabled'
+        self.set_encryption_state()
+        if self.parent_win:
+            self.add_window_actions()
+
         # PluginSystem: adding GUI extension point for ChatControlBase
         # instance object (also subclasses, eg. ChatControl or GroupchatControl)
         gajim.plugin_manager.gui_extension_point('chat_control_base', self)
@@ -412,6 +418,41 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
         # to properly use the super, because of the old code.
         CommandTools.__init__(self)
 
+    def add_window_actions(self):
+        action = Gio.SimpleAction.new_stateful(
+            "%s-encryptiongroup" % self.contact.jid,
+            GLib.VariantType.new("s"),
+            GLib.Variant("s", self.encryption))
+        action.connect("change-state", self.activate_encryption)
+        self.parent_win.window.add_action(action)
+
+    def activate_encryption(self, action, param):
+        encryption = param.get_string()
+        if self.encryption == encryption:
+            return
+
+        if encryption != 'disabled':
+            plugin = gajim.plugin_manager.encryption_plugins[encryption]
+            if not plugin.activate_encryption(self):
+                return
+        else:
+            if not self.widget_name == 'groupchat_control':
+                self.terminate_esessions()
+        action.set_state(param)
+        gajim.config.set_per(
+            'contacts', self.contact.jid, 'encryption', encryption)
+        self.encryption = encryption
+        self.set_lock_image()
+
+    def set_encryption_state(self):
+        enc = gajim.config.get_per('contacts', self.contact.jid, 'encryption')
+        if enc not in gajim.plugin_manager.encryption_plugins:
+            self.encryption = 'disabled'
+            gajim.config.set_per(
+                'contacts', self.contact.jid, 'encryption', 'disabled')
+        else:
+            self.encryption = enc
+
     def set_speller(self):
         # now set the one the user selected
         per_type = 'contacts'
@@ -723,7 +764,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
             keyID=keyID, type_=type_, chatstate=chatstate, msg_id=msg_id,
             resource=resource, user_nick=self.user_nick, xhtml=xhtml,
             label=label, callback=_cb, callback_args=[callback] + callback_args,
-            control=self, attention=attention, correction_msg=correction_msg, automatic_message=False))
+            control=self, attention=attention, correction_msg=correction_msg,
+            automatic_message=False, encryption=self.encryption))
 
         # Record the history of sent messages
         self.save_message(message, 'sent')
diff --git a/src/command_system/implementation/standard.py b/src/command_system/implementation/standard.py
index f7e1daa910e11794354ee65adcff0549e99613e8..4caba25d7c30a209aeaacb04ec6b534dae8cf1f6 100644
--- a/src/command_system/implementation/standard.py
+++ b/src/command_system/implementation/standard.py
@@ -179,11 +179,6 @@ class StandardCommonChatCommands(CommandContainer):
     def clear(self):
         self.conv_textview.clear()
 
-    @command
-    @doc(_("Toggle the OpenPGP encryption"))
-    def gpg(self):
-        self._toggle_gpg()
-
     @command
     @doc(_("Send a ping to the contact"))
     def ping(self):
diff --git a/src/common/config.py b/src/common/config.py
index 3411bbb4c07ca281e04cf11c4d779286ade27c24..9c8585fedfe23979df2c48aead01eff9ab5816be 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -480,8 +480,7 @@ class Config:
                     'state_muc_directed_msg_color': [ opt_color, 'red2' ],
             }, {}),
             'contacts': ({
-                    'gpg_enabled': [ opt_bool, False, _('Is OpenPGP enabled for this contact?')],
-                    'autonegotiate_esessions': [opt_bool, False, _('Should Gajim automatically start an encrypted session with this contact when possible?')],
+                    'encryption': [ opt_str, '', _('Encryption used for this contact.')],
                     'speller_language': [ opt_str, '', _('Language for which we want to check misspelled words')],
             }, {}),
             'rooms': ({
diff --git a/src/common/connection.py b/src/common/connection.py
index efc77bba98a3a6a3f08c9e6bdec4f46f513ebd0c..094625bb5b4fbd1083e0202c0043dcab8340ca7b 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -296,65 +296,9 @@ class CommonConnection:
         if not obj.message and obj.chatstate is None and obj.form_node is None:
             return
 
-        if obj.keyID and self.USE_GPG:
-            self._encrypt_message(obj)
-            return
-
         self._build_message_stanza(obj)
 
-    def _encrypt_message(self, obj):
-        obj.xhtml = None
-        if obj.keyID == 'UNKNOWN':
-            error = _('Neither the remote presence is signed, nor a key was '
-                      'assigned.')
-        elif obj.keyID.endswith('MISMATCH'):
-            error = _('The contact\'s key (%s) does not match the key assigned '
-                      'in Gajim.' % obj.keyID[:8])
-        else:
-            myKeyID = gajim.config.get_per('accounts', self.name, 'keyid')
-            key_list = [obj.keyID, myKeyID]
-            def _on_encrypted(output):
-                msgenc, error = output
-                if error.startswith('NOT_TRUSTED'):
-                    def _on_always_trust(answer):
-                        if answer:
-                            gajim.thread_interface(
-                                self.gpg.encrypt, [obj.message, key_list, True],
-                                _on_encrypted, [])
-                        else:
-                            self._finished_encrypt(obj, msgenc=msgenc,
-                                                   error=error)
-                    gajim.nec.push_incoming_event(GPGTrustKeyEvent(None,
-                        conn=self, keyID=error.split(' ')[-1],
-                        callback=_on_always_trust))
-                else:
-                    self._finished_encrypt(obj, msgenc=msgenc, error=error)
-            gajim.thread_interface(
-                self.gpg.encrypt, [obj.message, key_list, False],
-                _on_encrypted, [])
-            return
-        self._finished_encrypt(obj, error=error)
-
-    def _finished_encrypt(self, obj, msgenc=None, error=None):
-        if error:
-            gajim.nec.push_incoming_event(
-                MessageNotSentEvent(
-                    None, conn=self, jid=obj.jid, message=obj.message,
-                    error=error, time_=time.time(), session=obj.session))
-            return
-        self._build_message_stanza(obj, msgenc)
-
-    def _build_message_stanza(self, obj, msgenc=None):
-        if msgenc:
-            msgtxt = '[This message is *encrypted* (See :XEP:`27`]'
-            lang = os.getenv('LANG')
-            if lang is not None and not lang.startswith('en'):
-                # we're not english: one in locale and one en
-                msgtxt = _('[This message is *encrypted* (See :XEP:`27`]') + \
-                        ' (' + msgtxt + ')'
-        else:
-            msgtxt = obj.message
-
+    def _build_message_stanza(self, obj):
         if obj.jid == gajim.get_jid_from_account(self.name):
             fjid = obj.jid
         else:
@@ -368,44 +312,30 @@ class CommonConnection:
                 namespace=nbxmpp.NS_CORRECT)
             id2 = self.connection.getAnID()
             obj.correction_msg.setID(id2)
-            obj.correction_msg.setBody(msgtxt)
+            obj.correction_msg.setBody(obj.message)
             if obj.xhtml:
                 obj.correction_msg.setXHTML(obj.xhtml)
 
-            if msgenc:
-                encrypted_tag = obj.correction_msg.getTag(
-                    'x', namespace=nbxmpp.NS_ENCRYPTED)
-                obj.correction_msg.delChild(encrypted_tag)
-                obj.correction_msg.setTag(
-                    'x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc)
-
             if obj.session:
                 obj.session.last_send = time.time()
 
-                # XEP-0200
-                if obj.session.enable_encryption:
-                    obj.correction_msg = obj.session.encrypt_stanza(obj.correction_msg)
-
             self._push_stanza_message_outgoing(obj, obj.correction_msg)
             return
 
         if obj.type_ == 'chat':
-            msg_iq = nbxmpp.Message(body=msgtxt, typ=obj.type_,
+            msg_iq = nbxmpp.Message(body=obj.message, typ=obj.type_,
                     xhtml=obj.xhtml)
         else:
             if obj.subject:
-                msg_iq = nbxmpp.Message(body=msgtxt, typ='normal',
+                msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
                         subject=obj.subject, xhtml=obj.xhtml)
             else:
-                msg_iq = nbxmpp.Message(body=msgtxt, typ='normal',
+                msg_iq = nbxmpp.Message(body=obj.message, typ='normal',
                         xhtml=obj.xhtml)
 
         if obj.msg_id:
             msg_iq.setID(obj.msg_id)
 
-        if msgenc:
-            msg_iq.setTag('x', namespace=nbxmpp.NS_ENCRYPTED).setData(msgenc)
-
         if obj.form_node:
             msg_iq.addChild(node=obj.form_node)
         if obj.label:
@@ -460,7 +390,7 @@ class CommonConnection:
             if obj.chatstate and contact and contact.supports(nbxmpp.NS_CHATSTATES):
                 msg_iq.setTag(obj.chatstate, namespace=nbxmpp.NS_CHATSTATES)
                 only_chatste = False
-                if not msgtxt:
+                if not obj.message:
                     only_chatste = True
                 if only_chatste and not obj.session.enable_encryption:
                     msg_iq.setTag('no-store',
@@ -468,8 +398,9 @@ class CommonConnection:
 
             # XEP-0184
             if obj.jid != gajim.get_jid_from_account(self.name):
-                if msgtxt and gajim.config.get_per('accounts', self.name,
-                'request_receipt'):
+                request = gajim.config.get_per('accounts', self.name,
+                                               'request_receipt')
+                if obj.message and request:
                     msg_iq.setTag('request', namespace=nbxmpp.NS_RECEIPTS)
 
             if obj.forward_from:
@@ -483,23 +414,9 @@ class CommonConnection:
                 obj.session.last_send = time.time()
                 msg_iq.setThread(obj.session.thread_id)
 
-                # XEP-0200
-                if obj.session.enable_encryption:
-                    msg_iq = obj.session.encrypt_stanza(msg_iq)
-                    if self.carbons_enabled:
-                        msg_iq.addChild(name='private',
-                                        namespace=nbxmpp.NS_CARBONS)
-                    msg_iq.addChild(name='no-permanent-store',
-                                    namespace=nbxmpp.NS_MSG_HINTS)
-                    msg_iq.addChild(name='no-copy',
-                                    namespace=nbxmpp.NS_MSG_HINTS)
-                    if only_chatste:
-                        msg_iq.addChild(name='no-store',
-                                        namespace=nbxmpp.NS_MSG_HINTS)
-
         self._push_stanza_message_outgoing(obj, msg_iq)
 
-    def _push_stanza_message_outgoing(self, obj, msg_iq):    
+    def _push_stanza_message_outgoing(self, obj, msg_iq):
         obj.conn = self
         if isinstance(msg_iq, list):
             for iq in msg_iq:
@@ -2136,6 +2053,14 @@ class Connection(CommonConnection, ConnectionHandlers):
     def _nec_stanza_message_outgoing(self, obj):
         if obj.conn.name != self.name:
             return
+        encryption = gajim.config.get_per('contacts', obj.jid, 'encryption')
+        if encryption != 'disabled':
+            gajim.plugin_manager.gui_extension_point(
+                'encrypt' + encryption, self, obj, self.send_message)
+        else:
+            self.send_message(obj)
+
+    def send_message(self, obj):
         obj.msg_id = self.connection.send(obj.msg_iq, now=obj.now)
 
         gajim.nec.push_incoming_event(MessageSentEvent(
@@ -2747,6 +2672,14 @@ class Connection(CommonConnection, ConnectionHandlers):
     def _nec_gc_stanza_message_outgoing(self, obj):
         if obj.conn.name != self.name:
             return
+        encryption = gajim.config.get_per('contacts', obj.jid, 'encryption')
+        if encryption != 'disabled':
+            gajim.plugin_manager.gui_extension_point(
+                'gc_encrypt' + encryption, self, obj, self.send_gc_message)
+        else:
+            self.send_gc_message(obj)
+
+    def send_gc_message(self, obj):
         if obj.correction_msg:
             obj.msg_id = self.connection.send(obj.correction_msg)
         else:
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 7252f90467a9166fff6ea749949e13f3ec60b5cc..80f4cf19de28273917b0b380c0bc6301d870e7db 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -897,7 +897,7 @@ class ConnectionHandlersBase:
         gajim.ged.register_event_handler('message-received', ged.CORE,
             self._nec_message_received)
         gajim.ged.register_event_handler('mam-message-received', ged.CORE,
-            self._nec_mam_message_received)
+            self._nec_message_received)
         gajim.ged.register_event_handler('decrypted-message-received', ged.CORE,
             self._nec_decrypted_message_received)
 
@@ -911,7 +911,7 @@ class ConnectionHandlersBase:
         gajim.ged.remove_event_handler('message-received', ged.CORE,
             self._nec_message_received)
         gajim.ged.remove_event_handler('mam-message-received', ged.CORE,
-            self._nec_mam_message_received)
+            self._nec_message_received)
         gajim.ged.remove_event_handler('decrypted-message-received', ged.CORE,
             self._nec_decrypted_message_received)
 
@@ -1080,78 +1080,22 @@ class ConnectionHandlersBase:
             if sess.enable_encryption:
                 sess.terminate_e2e()
 
-    def decrypt_thread(self, encmsg, keyID, obj):
-        decmsg = self.gpg.decrypt(encmsg, keyID)
-        decmsg = self.connection.Dispatcher.replace_non_character(decmsg)
-        # \x00 chars are not allowed in C (so in GTK)
-        obj.msgtxt = decmsg.replace('\x00', '')
-        obj.encrypted = 'xep27'
-        self.gpg_messages_to_decrypt.remove([encmsg, keyID, obj])
-
     def _nec_message_received(self, obj):
         if obj.conn.name != self.name:
             return
-        if obj.encrypted == 'xep200':
-            try:
-                obj.stanza = obj.session.decrypt_stanza(obj.stanza)
-                obj.msgtxt = obj.stanza.getBody()
-            except Exception:
-                gajim.nec.push_incoming_event(FailedDecryptEvent(None,
-                    conn=self, msg_obj=obj))
-                return
 
-        if obj.enc_tag and self.USE_GPG:
-            encmsg = obj.enc_tag.getData()
-
-            keyID = gajim.config.get_per('accounts', self.name, 'keyid')
-            if keyID:
-                self.gpg_messages_to_decrypt.append([encmsg, keyID, obj])
-                if len(self.gpg_messages_to_decrypt) == 1:
-                    gajim.thread_interface(self.decrypt_thread, [encmsg, keyID,
-                        obj], self._on_message_decrypted, [obj])
-                return
-        gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None,
-            conn=self, msg_obj=obj))
+        gajim.plugin_manager.gui_extension_point(
+            'decrypt', self, obj, self._on_message_received)
+        if not obj.encrypted:
+            self._on_message_received(obj)
 
-    def _nec_mam_message_received(self, obj):
-        if obj.conn.name != self.name:
-            return
-        if obj.enc_tag and self.USE_GPG:
-            encmsg = obj.enc_tag.getData()
-
-            keyID = gajim.config.get_per('accounts', self.name, 'keyid')
-            if keyID:
-                self.gpg_messages_to_decrypt.append([encmsg, keyID, obj])
-                if len(self.gpg_messages_to_decrypt) == 1:
-                    gajim.thread_interface(self.decrypt_thread, [encmsg, keyID,
-                        obj], self._on_mam_message_decrypted, [obj])
-                return
-        gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None,
-            conn=self, msg_obj=obj))
-
-    def _on_message_decrypted(self, output, obj):
-        if len(self.gpg_messages_to_decrypt):
-            encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0]
-            if type(obj2) == MessageReceivedEvent:
-                cb = self._on_message_decrypted
-            else:
-                cb = self._on_mam_message_decrypted
-            gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2],
-                cb, [obj2])
-        gajim.nec.push_incoming_event(DecryptedMessageReceivedEvent(None,
-            conn=self, msg_obj=obj))
-
-    def _on_mam_message_decrypted(self, output, obj):
-        if len(self.gpg_messages_to_decrypt):
-            encmsg, keyID, obj2 = self.gpg_messages_to_decrypt[0]
-            if type(obj2) == MessageReceivedEvent:
-                cb = self._on_message_decrypted
-            else:
-                cb = self._on_mam_message_decrypted
-            gajim.thread_interface(self.decrypt_thread, [encmsg, keyID, obj2],
-                cb, [obj2])
-        gajim.nec.push_incoming_event(MamDecryptedMessageReceivedEvent(None,
-            conn=self, msg_obj=obj))
+    def _on_message_received(self, obj):
+        if isinstance(obj, MessageReceivedEvent):
+            gajim.nec.push_incoming_event(
+                DecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
+        else:
+            gajim.nec.push_incoming_event(
+                MamDecryptedMessageReceivedEvent(None, conn=self, msg_obj=obj))
 
     def _nec_decrypted_message_received(self, obj):
         if obj.conn.name != self.name:
diff --git a/src/common/connection_handlers_events.py b/src/common/connection_handlers_events.py
index 286f935fb7598b107c1682749fd4ab7b5f7b3ad1..ed2a9dd414eca40988f4787b0c26f27d94296ee4 100644
--- a/src/common/connection_handlers_events.py
+++ b/src/common/connection_handlers_events.py
@@ -1035,6 +1035,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
 
     def init(self):
         self.additional_data = {}
+        self.encrypted = False
     
     def generate(self):
         if not self.stanza:
@@ -1067,7 +1068,6 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
             self.with_ = to_
             self.direction = 'to'
             self.resource = gajim.get_resource_from_jid(self.msg_.getAttr('to'))
-        self.enc_tag = self.msg_.getTag('x', namespace=nbxmpp.NS_ENCRYPTED)
         return True
 
 class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
@@ -1122,6 +1122,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
         self.get_id()
         self.forwarded = False
         self.sent = False
+        self.encrypted = False
         account = self.conn.name
 
         our_full_jid = gajim.get_jid_from_account(account, full=True)
@@ -1216,33 +1217,31 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
                 conn=self.conn, stanza=forwarded))
             return
 
-        self.enc_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_ENCRYPTED)
-        if not self.enc_tag:
-            # Mediated invitation?
-            muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
-            if muc_user:
-                if muc_user.getTag('decline'):
-                    gajim.nec.push_incoming_event(
-                        GcDeclineReceivedEvent(
-                            None, conn=self.conn,
-                            room_jid=self.fjid, stanza=muc_user))
-                    return
-                if muc_user.getTag('invite'):
-                    gajim.nec.push_incoming_event(
-                        GcInvitationReceivedEvent(
-                            None, conn=self.conn, jid_from=self.fjid,
-                            mediated=True, stanza=muc_user))
-                    return
-            else:
-                # Direct invitation?
-                direct = self.stanza.getTag(
-                    'x', namespace=nbxmpp.NS_CONFERENCE)
-                if direct:
-                    gajim.nec.push_incoming_event(
-                        GcInvitationReceivedEvent(
-                            None, conn=self.conn, jid_from=self.fjid,
-                            mediated=False, stanza=direct))
-                    return
+        # Mediated invitation?
+        muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
+        if muc_user:
+            if muc_user.getTag('decline'):
+                gajim.nec.push_incoming_event(
+                    GcDeclineReceivedEvent(
+                        None, conn=self.conn,
+                        room_jid=self.fjid, stanza=muc_user))
+                return
+            if muc_user.getTag('invite'):
+                gajim.nec.push_incoming_event(
+                    GcInvitationReceivedEvent(
+                        None, conn=self.conn, jid_from=self.fjid,
+                        mediated=True, stanza=muc_user))
+                return
+        else:
+            # Direct invitation?
+            direct = self.stanza.getTag(
+                'x', namespace=nbxmpp.NS_CONFERENCE)
+            if direct:
+                gajim.nec.push_incoming_event(
+                    GcInvitationReceivedEvent(
+                        None, conn=self.conn, jid_from=self.fjid,
+                        mediated=False, stanza=direct))
+                return
 
         self.thread_id = self.stanza.getThread()
         self.mtype = self.stanza.getType()
@@ -1283,52 +1282,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
 
             self.session.last_receive = time_time()
 
-        # check if the message is a XEP-0020 feature negotiation request
-        if not self.forwarded and self.stanza.getTag('feature',
-                                                     namespace=nbxmpp.NS_FEATURE):
-            if gajim.HAVE_PYCRYPTO:
-                feature = self.stanza.getTag(name='feature',
-                                             namespace=nbxmpp.NS_FEATURE)
-                form = nbxmpp.DataForm(node=feature.getTag('x'))
-                if not form:
-                    return
-
-                if not form.getField('FORM_TYPE'):
-                    return
-
-                if form['FORM_TYPE'] == 'urn:xmpp:ssn':
-                    self.session.handle_negotiation(form)
-                else:
-                    reply = self.stanza.buildReply()
-                    reply.setType('error')
-                    reply.addChild(feature)
-                    err = nbxmpp.ErrorNode('service-unavailable', typ='cancel')
-                    reply.addChild(node=err)
-                    self.conn.connection.send(reply)
-            return
-
-        if not self.forwarded and self.stanza.getTag('init',
-                                                     namespace=nbxmpp.NS_ESESSION_INIT):
-            init = self.stanza.getTag(name='init',
-                                      namespace=nbxmpp.NS_ESESSION_INIT)
-            form = nbxmpp.DataForm(node=init.getTag('x'))
-
-            self.session.handle_negotiation(form)
-
-            return
-
         self._generate_timestamp(self.stanza.getTimestamp())
 
-
-        self.encrypted = False
-        xep_200_encrypted = self.stanza.getTag('c',
-                                               namespace=nbxmpp.NS_STANZA_CRYPTO)
-        if xep_200_encrypted:
-            if self.forwarded:
-                # Ignore E2E forwarded encrypted messages
-                return False
-            self.encrypted = 'xep200'
-
         return True
 
 class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
@@ -2796,6 +2751,7 @@ class MessageOutgoingEvent(nec.NetworkOutgoingEvent):
         self.attention = False
         self.correction_msg = None
         self.automatic_message = True
+        self.encryption = ''
 
     def get_full_jid(self):
         if self.resource:
diff --git a/src/dialogs.py b/src/dialogs.py
index 549bb05e21b8155f5967b68e0734f18a24327851..48203b566e0b7647af6f4190f4be82fff3c2421c 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -5315,140 +5315,6 @@ class DataFormWindow(Dialog):
             self.df_response_ok(form)
         self.destroy()
 
-class ESessionInfoWindow:
-    """
-    Class for displaying information about a XEP-0116 encrypted session
-    """
-    def __init__(self, session, transient_for=None):
-        self.session = session
-
-        self.xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
-        self.xml.connect_signals(self)
-
-        self.security_image = self.xml.get_object('security_image')
-        self.verify_now_button = self.xml.get_object('verify_now_button')
-        self.button_label = self.xml.get_object('verification_status_label')
-        self.window = self.xml.get_object('esession_info_window')
-        self.update_info()
-        self.window.set_transient_for(transient_for)
-
-        self.window.show_all()
-
-    def update_info(self):
-        labeltext = _('''Your chat session with <b>%(jid)s</b> is encrypted.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
-
-        if self.session.verified_identity:
-            labeltext += '\n\n' + _('''You have already verified this contact's identity.''')
-            security_image = 'security-high'
-            if self.session.control:
-                self.session.control._show_lock_image(True, 'E2E', True,
-                    self.session.is_loggable(), True)
-
-            verification_status = _('''Contact's identity verified''')
-            self.window.set_title(verification_status)
-            self.xml.get_object('verification_status_label').set_markup(
-                    '<b><span size="x-large">%s</span></b>' % verification_status)
-
-            self.xml.get_object('dialog-action_area1').set_no_show_all(True)
-            self.button_label.set_text(_('Verify again…'))
-        else:
-            if self.session.control:
-                self.session.control._show_lock_image(True, 'E2E', True,
-                     self.session.is_loggable(), False)
-            labeltext += '\n\n' + _('''To be certain that <b>only</b> the expected person can read your messages or send you messages, you need to verify their identity by clicking the button below.''')
-            security_image = 'security-low'
-
-            verification_status = _('''Contact's identity NOT verified''')
-            self.window.set_title(verification_status)
-            self.xml.get_object('verification_status_label').set_markup(
-                '<b><span size="x-large">%s</span></b>' % verification_status)
-
-            self.button_label.set_text(_('Verify…'))
-
-        path = gtkgui_helpers.get_icon_path(security_image, 32)
-        self.security_image.set_from_file(path)
-
-        self.xml.get_object('info_display').set_markup(labeltext)
-
-    def on_close_button_clicked(self, widget):
-        self.window.destroy()
-
-    def on_verify_now_button_clicked(self, widget):
-        pritext = _('''Have you verified the contact's identity?''')
-        sectext = _('''To prevent talking to an unknown person, you should speak to <b>%(jid)s</b> directly (in person or on the phone) and verify that they see the same Short Authentication String (SAS) as you.\n\nThis session's Short Authentication String is <b>%(sas)s</b>.''') % {'jid': self.session.jid, 'sas': self.session.sas}
-        sectext += '\n\n' + _('Did you talk to the remote contact and verify the SAS?')
-
-        def on_yes(checked):
-            self.session._verified_srs_cb()
-            self.session.verified_identity = True
-            self.update_info()
-
-        def on_no():
-            self.session._unverified_srs_cb()
-            self.session.verified_identity = False
-            self.update_info()
-
-        YesNoDialog(pritext, sectext, on_response_yes=on_yes,
-            on_response_no=on_no, transient_for=self.window)
-
-class GPGInfoWindow:
-    """
-    Class for displaying information about a XEP-0116 encrypted session
-    """
-    def __init__(self, control, transient_for=None):
-        xml = gtkgui_helpers.get_gtk_builder('esession_info_window.ui')
-        security_image = xml.get_object('security_image')
-        status_label = xml.get_object('verification_status_label')
-        info_label = xml.get_object('info_display')
-        verify_now_button = xml.get_object('verify_now_button')
-        self.window = xml.get_object('esession_info_window')
-        account = control.account
-        keyID = control.contact.keyID
-        error = None
-
-        verify_now_button.set_no_show_all(True)
-        verify_now_button.hide()
-
-        if keyID.endswith('MISMATCH'):
-            verification_status = _('''Contact's identity NOT verified''')
-            info = _('The contact\'s key (%s) <b>does not match</b> the key '
-                'assigned in Gajim.') % keyID[:8]
-            image = 'security-low'
-        elif not keyID:
-            # No key assigned nor a key is used by remote contact
-            verification_status = _('No OpenPGP key assigned')
-            info = _('No OpenPGP key is assigned to this contact. So you cannot'
-                ' encrypt messages.')
-            image = 'security-low'
-        else:
-            error = gajim.connections[account].gpg.encrypt('test', [keyID])[1]
-            if error:
-                verification_status = _('''Contact's identity NOT verified''')
-                info = _('OpenPGP key is assigned to this contact, but <b>you '
-                    'do not trust their key</b>, so message <b>cannot</b> be '
-                    'encrypted. Use your OpenPGP client to trust their key.')
-                image = 'security-low'
-            else:
-                verification_status = _('''Contact's identity verified''')
-                info = _('OpenPGP Key is assigned to this contact, and you '
-                    'trust their key, so messages will be encrypted.')
-                image = 'security-high'
-
-        status_label.set_markup('<b><span size="x-large">%s</span></b>' % \
-            verification_status)
-        info_label.set_markup(info)
-
-        path = gtkgui_helpers.get_icon_path(image, 32)
-        security_image.set_from_file(path)
-
-        self.window.set_transient_for(transient_for)
-        xml.connect_signals(self)
-        self.window.show_all()
-
-    def on_close_button_clicked(self, widget):
-        self.window.destroy()
-
-
 
 class ResourceConflictDialog(TimeoutDialog, InputDialog):
     def __init__(self, title, text, resource, ok_handler):
diff --git a/src/groupchat_control.py b/src/groupchat_control.py
index facef7eceb5f55aafa4d9a5fff99736dfee4692c..17f30a3c0d11a492db40967de710b47cbd974ce9 100644
--- a/src/groupchat_control.py
+++ b/src/groupchat_control.py
@@ -257,9 +257,8 @@ class PrivateChatControl(ChatControl):
     def prepare_context_menu(self, hide_buttonbar_items=False):
         """
         Set compact view menuitem active state sets active and sensitivity state
-        for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
-        tranasports) and file_transfer_menuitem and hide()/show() for
-        add_to_roster_menuitem
+        for history_menuitem (False for tranasports) and file_transfer_menuitem 
+        and hide()/show() for add_to_roster_menuitem
         """
         menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
             use_multiple_contacts=False, show_start_chat=False,
@@ -482,6 +481,19 @@ class GroupchatControl(ChatControlBase):
 
         self.form_widget = None
 
+        # Encryption
+        self.lock_image = self.xml.get_object('lock_image')
+        self.authentication_button = self.xml.get_object(
+            'authentication_button')
+        id_ = self.authentication_button.connect('clicked',
+            self._on_authentication_button_clicked)
+        self.handlers[id_] = self.authentication_button
+        self.set_lock_image()
+
+        self.encryption_menu = self.xml.get_object('encryption_menu')
+        self.encryption_menu.set_menu_model(
+            gui_menu_builder.get_encryption_menu(self.contact, self.type_id))
+
         gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
             self._nec_gc_presence_received)
         gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
@@ -509,6 +521,11 @@ class GroupchatControl(ChatControlBase):
         # instance object
         gajim.plugin_manager.gui_extension_point('groupchat_control', self)
 
+    def on_groupchat_maximize(self):
+        self.set_tooltip()
+        self.add_window_actions()
+        self.set_lock_image()
+
     def set_tooltip(self):
         widget = self.xml.get_object('list_treeview')
         if widget.get_tooltip_window():
@@ -752,6 +769,42 @@ class GroupchatControl(ChatControlBase):
         for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
             self.draw_contact(nick)
 
+    def set_lock_image(self):
+        visible = self.encryption != 'disabled'
+
+        encryption_state = {'visible': visible,
+                            'enc_type': self.encryption,
+                            'authenticated': False}
+
+        gajim.plugin_manager.gui_extension_point(
+            'encryption_state' + self.encryption, self, encryption_state)
+
+        self._show_lock_image(**encryption_state)
+
+    def _show_lock_image(self, visible, enc_type='',
+                         authenticated=False):
+        """
+        Set lock icon visibility and create tooltip
+        """
+        if authenticated:
+            authenticated_string = _('and authenticated')
+            img_path = gtkgui_helpers.get_icon_path('security-high')
+        else:
+            authenticated_string = _('and NOT authenticated')
+            img_path = gtkgui_helpers.get_icon_path('security-low')
+        self.lock_image.set_from_file(img_path)
+
+        tooltip = _('%(type)s encryption is active %(authenticated)s.') % {
+            'type': enc_type, 'authenticated': authenticated_string}
+
+        self.authentication_button.set_tooltip_text(tooltip)
+        self.widget_set_visible(self.authentication_button, not visible)
+        self.lock_image.set_sensitive(visible)
+
+    def _on_authentication_button_clicked(self, widget):
+        gajim.plugin_manager.gui_extension_point(
+            'encryption_dialog' + self.encryption, self)
+
     def _change_style(self, model, path, iter_, option):
         model[iter_][Column.NICK] = model[iter_][Column.NICK]
 
@@ -1967,6 +2020,13 @@ class GroupchatControl(ChatControlBase):
         if not message:
             return
 
+        if self.encryption:
+            self.sendmessage = True
+            gajim.plugin_manager.gui_extension_point(
+                    'send_message' + self.encryption, self)
+            if not self.sendmessage:
+                return
+
         if process_commands and self.process_as_command(message):
             return
 
diff --git a/src/gui_menu_builder.py b/src/gui_menu_builder.py
index 835b785782b94da7a040ec94386944c7d157a1d7..58f8925339658fc4a506abeb7cfd6bf0c1014893 100644
--- a/src/gui_menu_builder.py
+++ b/src/gui_menu_builder.py
@@ -245,9 +245,6 @@ control=None, gc_contact=None, is_anonymous=True):
             'remove_from_roster_menuitem')
     manage_contact_menuitem = xml.get_object('manage_contact')
     convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem')
-    encryption_separator = xml.get_object('encryption_separator')
-    toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem')
-    toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem')
     last_separator = xml.get_object('last_separator')
 
     items_to_hide = []
@@ -322,36 +319,6 @@ control=None, gc_contact=None, is_anonymous=True):
     if not show_start_chat:
         items_to_hide.append(start_chat_menuitem)
 
-    if not show_encryption or not control:
-        items_to_hide += [encryption_separator, toggle_gpg_menuitem,
-                toggle_e2e_menuitem]
-    else:
-        e2e_is_active = control.session is not None and \
-                control.session.enable_encryption
-
-        # check if we support and use gpg
-        if not gajim.config.get_per('accounts', account, 'keyid') or \
-        not gajim.connections[account].USE_GPG or gajim.jid_is_transport(
-        contact.jid):
-            toggle_gpg_menuitem.set_sensitive(False)
-        else:
-            toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \
-                    not e2e_is_active)
-            toggle_gpg_menuitem.set_active(control.gpg_is_active)
-            toggle_gpg_menuitem.connect('activate',
-                    control._on_toggle_gpg_menuitem_activate)
-
-        # disable esessions if we or the other client don't support them
-        if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \
-        not gajim.config.get_per('accounts', account, 'enable_esessions'):
-            toggle_e2e_menuitem.set_sensitive(False)
-        else:
-            toggle_e2e_menuitem.set_active(e2e_is_active)
-            toggle_e2e_menuitem.set_sensitive(e2e_is_active or \
-                    not control.gpg_is_active)
-            toggle_e2e_menuitem.connect('activate',
-                    control._on_toggle_e2e_menuitem_activate)
-
     if not show_buttonbar_items:
         items_to_hide += [history_menuitem, send_file_menuitem,
                 information_menuitem, convert_to_gc_menuitem, last_separator]
@@ -781,3 +748,23 @@ def build_bookmark_menu(account):
     label = menu.get_item_attribute_value(1, 'label').get_string()
     menu.remove(1)
     menu.insert_submenu(1, label, bookmark_menu)
+
+
+def get_encryption_menu(contact, type_id):
+    menu = Gio.Menu()
+    menu.append(
+        'Disabled', 'win.{}-encryptiongroup::{}'.format(contact.jid,
+                                                        'disabled'))
+    for name, plugin in gajim.plugin_manager.encryption_plugins.items():
+        if type_id == 'gc':
+            if not hasattr(plugin, 'allow_groupchat'):
+                continue
+        if type_id == 'pm':
+            if not hasattr(plugin, 'allow_privatchat'):
+                continue
+        menu_action = 'win.{}-encryptiongroup::{}'.format(
+            contact.jid, name)
+        menu.append(name, menu_action)
+    if menu.get_n_items() == 1:
+        return None
+    return menu
diff --git a/src/plugins/gajimplugin.py b/src/plugins/gajimplugin.py
index 2a78e497733208ec102082017401ed3fc2753511..18a8a75b01287a470f32d52b30e09156f2e2dcd9 100644
--- a/src/plugins/gajimplugin.py
+++ b/src/plugins/gajimplugin.py
@@ -58,6 +58,16 @@ class GajimPlugin(object):
 
     :todo: decide whether we really need this one, because class name (with
             module name) can act as such short name
+    '''
+    encryption_name = ''
+    '''
+    Name of the encryption scheme.
+
+    The name that Gajim displays in the encryption menu.
+    Leave empty if the plugin is not an encryption plugin.
+
+    :type: str
+
     '''
     version = ''
     '''
diff --git a/src/plugins/pluginmanager.py b/src/plugins/pluginmanager.py
index eeeafcb04ef4eca947fd9211826fa02b24ceb587..dc7e867eb6dcb59af87034c32863d4578dccf6f7 100644
--- a/src/plugins/pluginmanager.py
+++ b/src/plugins/pluginmanager.py
@@ -101,6 +101,12 @@ class PluginManager(metaclass=Singleton):
         '''
         Registered handlers of GUI extension points.
         '''
+
+        self.encryption_plugins = {}
+        '''
+        Registered names with instances of encryption Plugins.
+        '''
+
         for path in [gajim.PLUGINS_DIRS[1], gajim.PLUGINS_DIRS[0]]:
             pc = PluginManager.scan_dir_for_plugins(path)
             self.add_plugins(pc)
@@ -291,6 +297,10 @@ class PluginManager(metaclass=Singleton):
             elif issubclass(event_class, nec.NetworkOutgoingEvent):
                 gajim.nec.unregister_outgoing_event(event_class)
 
+    def _remove_name_from_encryption_plugins(self, plugin):
+        if plugin.encryption_name:
+            del self.encryption_plugins[plugin.encryption_name]
+
     @log_calls('PluginManager')
     def activate_plugin(self, plugin):
         '''
@@ -300,6 +310,7 @@ class PluginManager(metaclass=Singleton):
         if not plugin.active and plugin.activatable:
 
             self._add_gui_extension_points_handlers_from_plugin(plugin)
+            self._add_encryption_name_from_plugin(plugin)
             self._handle_all_gui_extension_points_with_plugin(plugin)
             self._register_events_handlers_in_ged(plugin)
             self._register_network_events_in_nec(plugin)
@@ -339,6 +350,7 @@ class PluginManager(metaclass=Singleton):
 
         self._remove_events_handler_from_ged(plugin)
         self._remove_network_events_from_nec(plugin)
+        self._remove_name_from_encryption_plugins(plugin)
 
         # removing plug-in from active plug-ins list
         plugin.deactivate()
@@ -357,6 +369,10 @@ class PluginManager(metaclass=Singleton):
             self.gui_extension_points_handlers.setdefault(gui_extpoint_name,
                 []).append(gui_extpoint_handlers)
 
+    def _add_encryption_name_from_plugin(self, plugin):
+        if plugin.encryption_name:
+            self.encryption_plugins[plugin.encryption_name] = plugin
+
     @log_calls('PluginManager')
     def _handle_all_gui_extension_points_with_plugin(self, plugin):
         for gui_extpoint_name, gui_extpoint_handlers in \
diff --git a/src/roster_window.py b/src/roster_window.py
index 81766ef943a26a792f20b534b26cb0ab95c82389..7d33908e8a1352b082e7f2b6fd9ef2a6a8c56631 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -3220,7 +3220,7 @@ class RosterWindow:
                 ctrl._on_window_motion_notify)
             ctrl.handlers[id_] = mw.window
         ctrl.parent_win = mw
-        ctrl.set_tooltip()
+        ctrl.on_groupchat_maximize()
         mw.new_tab(ctrl)
         mw.set_active_tab(ctrl)
         mw.window.get_window().focus(Gtk.get_current_event_time())