diff --git a/PKG-INFO b/PKG-INFO
index f1120edf560986917c20ffbade9e6640ca056336..bba27476db25720b2956fe2b08858b12a4dd091e 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.1.1
+Version: 0.2.0
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO
index f1120edf560986917c20ffbade9e6640ca056336..bba27476db25720b2956fe2b08858b12a4dd091e 100644
--- a/swh.model.egg-info/PKG-INFO
+++ b/swh.model.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: swh.model
-Version: 0.1.1
+Version: 0.2.0
 Summary: Software Heritage data model
 Home-page: https://forge.softwareheritage.org/diffusion/DMOD/
 Author: Software Heritage developers
diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py
index 448487a8ea2e1e2bc79a51529c64402f17cccdd6..bc9d58c46c2ce73fd153efbd65db2b389a8da84a 100644
--- a/swh/model/hypothesis_strategies.py
+++ b/swh/model/hypothesis_strategies.py
@@ -93,10 +93,13 @@ def urls(draw):
     return "%s://%s" % (protocol, domain)
 
 
-def persons_d():
-    return builds(
-        dict, fullname=binary(), email=optional(binary()), name=optional(binary()),
-    )
+@composite
+def persons_d(draw):
+    fullname = draw(binary())
+    email = draw(optional(binary()))
+    name = draw(optional(binary()))
+    assume(not (len(fullname) == 32 and email is None and name is None))
+    return dict(fullname=fullname, name=name, email=email)
 
 
 def persons():
diff --git a/swh/model/model.py b/swh/model/model.py
index 74702cf435d3dfb60b2d39b247531389c2d349c9..7db255cc5600c46f7a35dcc1b5857b0c9fc10775 100644
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -7,7 +7,8 @@ import datetime
 
 from abc import ABCMeta, abstractmethod
 from enum import Enum
-from typing import Dict, List, Optional, Union
+from hashlib import sha256
+from typing import Dict, List, Optional, TypeVar, Union
 
 import attr
 from attrs_strict import type_validator
@@ -51,6 +52,9 @@ def dictify(value):
         return value
 
 
+ModelType = TypeVar("ModelType", bound="BaseModel")
+
+
 class BaseModel:
     """Base class for SWH model classes.
 
@@ -68,6 +72,13 @@ class BaseModel:
         recursively builds the corresponding objects."""
         return cls(**d)
 
+    def anonymize(self: ModelType) -> Optional[ModelType]:
+        """Returns an anonymized version of the object, if needed.
+
+        If the object model does not need/support anonymization, returns None.
+        """
+        return None
+
 
 class HashableObject(metaclass=ABCMeta):
     """Mixin to automatically compute object identifier hash when
@@ -129,6 +140,14 @@ class Person(BaseModel):
 
         return Person(name=name or None, email=email or None, fullname=fullname,)
 
+    def anonymize(self) -> "Person":
+        """Returns an anonymized version of the Person object.
+
+        Anonymization is simply a Person which fullname is the hashed, with unset name
+        or email.
+        """
+        return Person(fullname=sha256(self.fullname).digest(), name=None, email=None,)
+
 
 @attr.s(frozen=True)
 class Timestamp(BaseModel):
@@ -369,6 +388,14 @@ class Release(BaseModel, HashableObject):
             d["date"] = TimestampWithTimezone.from_dict(d["date"])
         return cls(target_type=ObjectType(d.pop("target_type")), **d)
 
+    def anonymize(self) -> "Release":
+        """Returns an anonymized version of the Release object.
+
+        Anonymization consists in replacing the author with an anonymized Person object.
+        """
+        author = self.author and self.author.anonymize()
+        return attr.evolve(self, author=author)
+
 
 class RevisionType(Enum):
     GIT = "git"
@@ -422,6 +449,16 @@ class Revision(BaseModel, HashableObject):
             **d,
         )
 
+    def anonymize(self) -> "Revision":
+        """Returns an anonymized version of the Revision object.
+
+        Anonymization consists in replacing the author and committer with an anonymized
+        Person object.
+        """
+        return attr.evolve(
+            self, author=self.author.anonymize(), committer=self.committer.anonymize()
+        )
+
 
 @attr.s(frozen=True)
 class DirectoryEntry(BaseModel):
diff --git a/swh/model/tests/test_hypothesis_strategies.py b/swh/model/tests/test_hypothesis_strategies.py
index 1622b3c71ba47542f4cec845d3a2094f3f152dfd..2be35a0575a04bf2f787ce4f33a406dc190a377f 100644
--- a/swh/model/tests/test_hypothesis_strategies.py
+++ b/swh/model/tests/test_hypothesis_strategies.py
@@ -18,6 +18,7 @@ from swh.model.hypothesis_strategies import (
     skipped_contents,
     snapshots,
     origin_visits,
+    persons,
 )
 from swh.model.model import TargetType
 
@@ -196,3 +197,10 @@ def test_snapshots_strategy_fixed_size(snapshot):
 @given(origin_visits())
 def test_origin_visit_aware_datetime(visit):
     assert visit.date.tzinfo is not None
+
+
+@given(persons())
+def test_person_do_not_look_like_anonimized(person):
+    assert not (
+        len(person.fullname) == 32 and person.name is None and person.email is None
+    )
diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py
index 6027cc2735bc12bd8cb6dc16852dd16a1a4e3535..e126ca571e8d631486242b231df6dd3f88c09e8a 100644
--- a/swh/model/tests/test_model.py
+++ b/swh/model/tests/test_model.py
@@ -61,6 +61,37 @@ def test_todict_inverse_fromdict(objtype_and_obj):
     assert obj_as_dict == type(obj).from_dict(obj_as_dict).to_dict()
 
 
+# Anonymization
+
+
+@given(strategies.objects())
+def test_anonymization(objtype_and_obj):
+    (obj_type, obj) = objtype_and_obj
+
+    def check_person(p):
+        if p is not None:
+            assert p.name is None
+            assert p.email is None
+            assert len(p.fullname) == 32
+
+    anon_obj = obj.anonymize()
+    if obj_type == "person":
+        assert anon_obj is not None
+        check_person(anon_obj)
+    elif obj_type == "release":
+        assert anon_obj is not None
+        check_person(anon_obj.author)
+    elif obj_type == "revision":
+        assert anon_obj is not None
+        check_person(anon_obj.author)
+        check_person(anon_obj.committer)
+    else:
+        assert anon_obj is None
+
+
+# Origin, OriginVisit
+
+
 @given(strategies.origins())
 def test_todict_origins(origin):
     obj = origin.to_dict()
diff --git a/version.txt b/version.txt
index 8019930dbc7fb6e043f89728e2f895f10b09ad3b..461646da2e67bbb6ae7b2b6799e5334afaaee225 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-v0.1.1-0-g091498e
\ No newline at end of file
+v0.2.0-0-g29312df
\ No newline at end of file