diff --git a/federation/protocols/diaspora/protocol.py b/federation/protocols/diaspora/protocol.py
index 83c7045..8113b90 100644
--- a/federation/protocols/diaspora/protocol.py
+++ b/federation/protocols/diaspora/protocol.py
@@ -1,7 +1,7 @@
+import json
 import logging
 import warnings
 from base64 import b64decode, urlsafe_b64decode, b64encode, urlsafe_b64encode
-from json import loads, dumps
 from urllib.parse import unquote_plus
 
 from Crypto.Cipher import AES, PKCS1_v1_5
@@ -20,14 +20,35 @@ logger = logging.getLogger("federation")
 
 PROTOCOL_NAME = "diaspora"
 PROTOCOL_NS = "https://joindiaspora.com/protocol"
+MAGIC_ENV_TAG = "{http://salmon-protocol.org/ns/magic-env}env"
 
 
 def identify_payload(payload):
+    """Try to identify whether this is a Diaspora payload.
+
+    Try first public message. Then private message. The check if this is a legacy payload.
+    """
+    # Private encrypted JSON payload
+    try:
+        data = json.loads(payload)
+        if "encrypted_magic_envelope" in data:
+            return True
+    except Exception:
+        pass
+    # Public XML payload
+    try:
+        xml = etree.fromstring(bytes(payload, encoding="utf-8"))
+        if xml.tag == MAGIC_ENV_TAG:
+            return True
+    except Exception:
+        pass
+    # Legacy XML payload
     try:
         xml = unquote_plus(payload)
         return xml.find('xmlns="%s"' % PROTOCOL_NS) > -1
     except Exception:
-        return False
+        pass
+    return False
 
 
 class Protocol(BaseProtocol):
@@ -151,7 +172,7 @@ class Protocol(BaseProtocol):
         key and hence the passphrase for the key.
         """
         decoded_json = b64decode(b64data.encode("ascii"))
-        rep = loads(decoded_json.decode("ascii"))
+        rep = json.loads(decoded_json.decode("ascii"))
         outer_key_details = self.decrypt_outer_aes_key_bundle(
             rep["aes_key"], key)
         header = self.get_decrypted_header(
@@ -173,7 +194,7 @@ class Protocol(BaseProtocol):
             b64decode(data.encode("ascii")),
             sentinel=None
         )
-        return loads(decoded_json.decode("ascii"))
+        return json.loads(decoded_json.decode("ascii"))
 
     def get_decrypted_header(self, ciphertext, key, iv):
         """
@@ -273,7 +294,7 @@ class Protocol(BaseProtocol):
         """
         Record the information on the key used to encrypt the header.
         """
-        d = dumps({
+        d = json.dumps({
             "iv": b64encode(self.outer_iv).decode("ascii"),
             "key": b64encode(self.outer_key).decode("ascii")
         })
@@ -297,7 +318,7 @@ class Protocol(BaseProtocol):
             public_key)).decode("ascii")
         ciphertext = b64encode(self.create_ciphertext()).decode("ascii")
 
-        d = dumps({
+        d = json.dumps({
             "aes_key": aes_key,
             "ciphertext": ciphertext
         })

Don't try to identify Diaspora protocol
Diaspora will drop the whole XML wrapper and just deliver a Magic Envelope (for public posts). Private posts will be delivered wrapped in a JSON structure. Diaspora 0.6 understands this new message, 0.7 will maybe send it out.

For public posts, this:

```
<?xml version="1.0" encoding="UTF-8"?>
<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
    <header>
        <author_id>user@domain.tld</author_id>
    </header>
    <me:env>
        ....
    </me:env>
</diaspora>
```

becomes this:

```
<me:env>
        ....
</me:env>
```

Check for `me:env` tags, then just try and open it - if it doesn't open it's something else. Same for public messages but looking at the JSON.

Old structures should be supported.
diff --git a/federation/tests/fixtures/payloads.py b/federation/tests/fixtures/payloads.py
index 97f88e6..b58c95e 100644
--- a/federation/tests/fixtures/payloads.py
+++ b/federation/tests/fixtures/payloads.py
@@ -1,4 +1,4 @@
-ENCRYPTED_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
+ENCRYPTED_LEGACY_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
             <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
                 <encrypted_header>{encrypted_header}</encrypted_header>
                 <me:env>
@@ -11,7 +11,7 @@ ENCRYPTED_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
         """
 
 
-UNENCRYPTED_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
+UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
             <diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env">
                 <header>
                     <author_id>bob@example.com</author_id>
@@ -26,6 +26,26 @@ UNENCRYPTED_DIASPORA_PAYLOAD = """<?xml version='1.0'?>
         """
 
 
+DIASPORA_PUBLIC_PAYLOAD = """<?xml version='1.0' encoding='UTF-8'?>
+<me:env xmlns:me="http://salmon-protocol.org/ns/magic-env">
+    <me:encoding>base64url</me:encoding>
+    <me:alg>RSA-SHA256</me:alg>
+    <me:data type="application/xml">PHN0YXR1c19tZXNzYWdlPjxmb28-YmFyPC9mb28-PC9zdGF0dXNfbWVzc2FnZT4=</me:data>
+    <me:sig key_id="Zm9vYmFyQGV4YW1wbGUuY29t">Cmk08MR4Tp8r9eVybD1hORcR_8NLRVxAu0biOfJbkI1xLx1c480zJ720cpVyKaF9""" \
+    """CxVjW3lvlvRz5YbswMv0izPzfHpXoWTXH-4UPrXaGYyJnrNvqEB2UWn4iHKJ2Rerto8sJY2b95qbXD6Nq75EoBNub5P7DYc16ENhp3""" \
+    """8YwBRnrBEvNOewddpOpEBVobyNB7no_QR8c_xkXie-hUDFNwI0z7vax9HkaBFbvEmzFPMZAAdWyjxeGiWiqY0t2ZdZRCPTezy66X6Q0""" \
+    """qc4I8kfT-Mt1ctjGmNMoJ4Lgu-PrO5hSRT4QBAVyxaog5w-B0PIPuC-mUW5SZLsnX3_ZuwJww==</me:sig>
+</me:env>
+"""
+
+
+DIASPORA_ENCRYPTED_PAYLOAD = """{
+  "aes_key": "...",
+  "encrypted_magic_envelope": "..."
+}
+"""
+
+
 DIASPORA_POST_LEGACY = """<XML>
       <post>
         <status_message>
diff --git a/federation/tests/protocols/diaspora/test_diaspora.py b/federation/tests/protocols/diaspora/test_protocol.py
similarity index 81%
rename from federation/tests/protocols/diaspora/test_diaspora.py
rename to federation/tests/protocols/diaspora/test_protocol.py
index 3226e19..fa4359e 100644
--- a/federation/tests/protocols/diaspora/test_diaspora.py
+++ b/federation/tests/protocols/diaspora/test_protocol.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from base64 import urlsafe_b64decode
 from unittest.mock import Mock, patch
 from xml.etree.ElementTree import ElementTree
@@ -8,10 +7,14 @@ import pytest
 
 from federation.exceptions import EncryptedMessageError, NoSenderKeyFoundError, NoHeaderInMessageError
 from federation.protocols.diaspora.protocol import Protocol, identify_payload
-from federation.tests.fixtures.payloads import ENCRYPTED_DIASPORA_PAYLOAD, UNENCRYPTED_DIASPORA_PAYLOAD
+from federation.tests.fixtures.payloads import (
+    ENCRYPTED_LEGACY_DIASPORA_PAYLOAD, UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD,
+    DIASPORA_PUBLIC_PAYLOAD,
+    DIASPORA_ENCRYPTED_PAYLOAD,
+)
 
 
-class MockUser(object):
+class MockUser():
     private_key = "foobar"
 
     def __init__(self, nokey=False):
@@ -27,15 +30,15 @@ def mock_not_found_get_contact_key(contact):
     return None
 
 
-class DiasporaTestBase(object):
+class DiasporaTestBase():
     def init_protocol(self):
         return Protocol()
 
     def get_unencrypted_doc(self):
-        return etree.fromstring(UNENCRYPTED_DIASPORA_PAYLOAD)
+        return etree.fromstring(UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD)
 
     def get_encrypted_doc(self):
-        return etree.fromstring(ENCRYPTED_DIASPORA_PAYLOAD)
+        return etree.fromstring(ENCRYPTED_LEGACY_DIASPORA_PAYLOAD)
 
     def get_mock_user(self, nokey=False):
         return MockUser(nokey)
@@ -68,7 +71,7 @@ class TestDiasporaProtocol(DiasporaTestBase):
         protocol = self.init_protocol()
         user = self.get_mock_user()
         protocol.get_message_content = self.mock_get_message_content
-        sender, content = protocol.receive(UNENCRYPTED_DIASPORA_PAYLOAD, user, mock_get_contact_key,
+        sender, content = protocol.receive(UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD, user, mock_get_contact_key,
                                            skip_author_verification=True)
         assert sender == "bob@example.com"
         assert content == "<content />"
@@ -80,7 +83,7 @@ class TestDiasporaProtocol(DiasporaTestBase):
             return_value="<content><diaspora_handle>bob@example.com</diaspora_handle></content>"
         )
         protocol.parse_header = Mock(return_value="foobar")
-        sender, content = protocol.receive(ENCRYPTED_DIASPORA_PAYLOAD, user, mock_get_contact_key,
+        sender, content = protocol.receive(ENCRYPTED_LEGACY_DIASPORA_PAYLOAD, user, mock_get_contact_key,
                                            skip_author_verification=True)
         assert sender == "bob@example.com"
         assert content == "<content><diaspora_handle>bob@example.com</diaspora_handle></content>"
@@ -88,19 +91,19 @@ class TestDiasporaProtocol(DiasporaTestBase):
     def test_receive_raises_on_encrypted_message_and_no_user(self):
         protocol = self.init_protocol()
         with pytest.raises(EncryptedMessageError):
-            protocol.receive(ENCRYPTED_DIASPORA_PAYLOAD)
+            protocol.receive(ENCRYPTED_LEGACY_DIASPORA_PAYLOAD)
 
     def test_receive_raises_on_encrypted_message_and_no_user_key(self):
         protocol = self.init_protocol()
         user = self.get_mock_user(nokey=True)
         with pytest.raises(EncryptedMessageError):
-            protocol.receive(ENCRYPTED_DIASPORA_PAYLOAD, user)
+            protocol.receive(ENCRYPTED_LEGACY_DIASPORA_PAYLOAD, user)
 
     def test_receive_raises_if_sender_key_cannot_be_found(self):
         protocol = self.init_protocol()
         user = self.get_mock_user()
         with pytest.raises(NoSenderKeyFoundError):
-            protocol.receive(UNENCRYPTED_DIASPORA_PAYLOAD, user, mock_not_found_get_contact_key)
+            protocol.receive(UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD, user, mock_not_found_get_contact_key)
 
     def test_find_header_raises_if_header_cannot_be_found(self):
         protocol = self.init_protocol()
@@ -115,8 +118,14 @@ class TestDiasporaProtocol(DiasporaTestBase):
         body = protocol.get_message_content()
         assert body == urlsafe_b64decode("{data}".encode("ascii"))
 
-    def test_identify_payload_with_diaspora_payload(self):
-        assert identify_payload(UNENCRYPTED_DIASPORA_PAYLOAD) == True
+    def test_identify_payload_with_legacy_diaspora_payload(self):
+        assert identify_payload(UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD) == True
+
+    def test_identify_payload_with_diaspora_public_payload(self):
+        assert identify_payload(DIASPORA_PUBLIC_PAYLOAD) == True
+
+    def test_identify_payload_with_diaspora_encrypted_payload(self):
+        assert identify_payload(DIASPORA_ENCRYPTED_PAYLOAD) == True
 
     def test_identify_payload_with_other_payload(self):
         assert identify_payload("foobar not a diaspora protocol") == False
diff --git a/federation/tests/test_inbound.py b/federation/tests/test_inbound.py
index 879e7d1..0975808 100644
--- a/federation/tests/test_inbound.py
+++ b/federation/tests/test_inbound.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 from unittest.mock import patch
 
 import pytest
@@ -6,12 +5,12 @@ import pytest
 from federation.exceptions import NoSuitableProtocolFoundError
 from federation.inbound import handle_receive
 from federation.protocols.diaspora.protocol import Protocol
-from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
+from federation.tests.fixtures.payloads import UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD
 
 
-class TestHandleReceiveProtocolIdentification(object):
+class TestHandleReceiveProtocolIdentification():
     def test_handle_receive_routes_to_identified_protocol(self):
-        payload = UNENCRYPTED_DIASPORA_PAYLOAD
+        payload = UNENCRYPTED_LEGACY_DIASPORA_PAYLOAD
         with patch.object(
                     Protocol,
                     'receive',

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc libxml2-dev libxslt1-dev
3.6
dev-requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
arrow==1.2.3
attrs==22.2.0
Babel==2.11.0
certifi==2021.5.30
charset-normalizer==2.0.12
codecov==2.1.13
colorama==0.4.5
commonmark==0.9.1
coverage==6.2
cssselect==1.1.0
dirty-validators==0.5.4
docutils==0.18.1
factory-boy==3.2.1
Faker==14.2.1
-e git+https://github.com/jaywink/federation.git@273b7ba47a08d4fe8f85db9620c19ce7c79f83be#egg=federation
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
iniconfig==1.1.1
isodate==0.6.1
Jinja2==3.0.3
jsonschema==3.2.0
livereload==2.6.3
lxml==5.3.1
MarkupSafe==2.0.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
pycrypto==2.6.1
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
pytest-warnings==0.3.1
python-dateutil==2.9.0.post0
python-xrd==0.1
pytz==2025.2
recommonmark==0.7.1
requests==2.27.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==5.3.0
sphinx-autobuild==2021.3.14
sphinxcontrib-applehelp==1.0.2
sphinxcontrib-devhelp==1.0.2
sphinxcontrib-htmlhelp==2.0.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.3
sphinxcontrib-serializinghtml==1.1.5
tomli==1.2.3
tornado==6.1
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: federation
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - arrow==1.2.3
      - attrs==22.2.0
      - babel==2.11.0
      - charset-normalizer==2.0.12
      - codecov==2.1.13
      - colorama==0.4.5
      - commonmark==0.9.1
      - coverage==6.2
      - cssselect==1.1.0
      - dirty-validators==0.5.4
      - docutils==0.18.1
      - factory-boy==3.2.1
      - faker==14.2.1
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - isodate==0.6.1
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - livereload==2.6.3
      - lxml==5.3.1
      - markupsafe==2.0.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pycrypto==2.6.1
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - pytest-warnings==0.3.1
      - python-dateutil==2.9.0.post0
      - python-xrd==0.1
      - pytz==2025.2
      - recommonmark==0.7.1
      - requests==2.27.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==5.3.0
      - sphinx-autobuild==2021.3.14
      - sphinxcontrib-applehelp==1.0.2
      - sphinxcontrib-devhelp==1.0.2
      - sphinxcontrib-htmlhelp==2.0.0
      - sphinxcontrib-jsmath==1.0.1
      - sphinxcontrib-qthelp==1.0.3
      - sphinxcontrib-serializinghtml==1.1.5
      - tomli==1.2.3
      - tornado==6.1
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/federation

federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_identify_payload_with_diaspora_public_payload federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_identify_payload_with_diaspora_encrypted_payload
federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_find_unencrypted_header federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_find_encrypted_header federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_receive_unencrypted_returns_sender_and_content federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_receive_encrypted_returns_sender_and_content federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_receive_raises_on_encrypted_message_and_no_user federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_receive_raises_on_encrypted_message_and_no_user_key federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_receive_raises_if_sender_key_cannot_be_found federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_find_header_raises_if_header_cannot_be_found federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_get_message_content federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_identify_payload_with_legacy_diaspora_payload federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_identify_payload_with_other_payload federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_get_sender_returns_sender_in_header federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_get_sender_returns_sender_in_content federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_get_sender_returns_none_if_no_sender_found federation/tests/protocols/diaspora/test_protocol.py::TestDiasporaProtocol::test_build_send federation/tests/test_inbound.py::TestHandleReceiveProtocolIdentification::test_handle_receive_routes_to_identified_protocol federation/tests/test_inbound.py::TestHandleReceiveProtocolIdentification::test_handle_receive_raises_on_unidentified_protocol
federation/protocols/diaspora/protocol.py
federation/protocols/diaspora/protocol.py