diff --git a/cybox/objects/artifact_object.py b/cybox/objects/artifact_object.py
index 1c65723..92561b6 100644
--- a/cybox/objects/artifact_object.py
+++ b/cybox/objects/artifact_object.py
@@ -12,6 +12,10 @@ from mixbox.compat import xor
 import cybox.bindings.artifact_object as artifact_binding
 from cybox.common import ObjectProperties, String, HashList
 
+_COMPRESSION_EXT_MAP = {}   # Maps compression_mechanism property to implementation/extension classes
+_ENCRYPTION_EXT_MAP = {}    # Maps encryption_mechanism property to implementation/extension classes
+_ENCODING_EXT_MAP = {}      # Maps algorithm property to implementation/extension classes
+
 
 def validate_artifact_type(instance, value):
     if value is None:
@@ -55,6 +59,7 @@ class Compression(entities.Entity):
     _namespace = 'http://cybox.mitre.org/objects#ArtifactObject-2'
     _binding = artifact_binding
     _binding_class = _binding.CompressionType
+    _COMPRESSION_TYPE = None  # overridden by subclasses
 
     compression_mechanism = fields.TypedField("compression_mechanism")
     compression_mechanism_ref = fields.TypedField("compression_mechanism_ref")
@@ -72,11 +77,6 @@ class Compression(entities.Entity):
         """This should accept byte data and return byte data"""
         raise NotImplementedError()
 
-    def to_dict(self):
-        dict_ = super(Compression, self).to_dict()
-        dict_['packaging_type'] = 'compression'
-        return dict_
-
 
 class Encryption(entities.Entity):
     """
@@ -85,6 +85,7 @@ class Encryption(entities.Entity):
     _namespace = 'http://cybox.mitre.org/objects#ArtifactObject-2'
     _binding = artifact_binding
     _binding_class = _binding.EncryptionType
+    _ENCRYPTION_TYPE = None  # overridden by subclasses
 
     encryption_mechanism = fields.TypedField("encryption_mechanism")
     encryption_mechanism_ref = fields.TypedField("encryption_mechanism_ref")
@@ -107,11 +108,6 @@ class Encryption(entities.Entity):
         """This should accept byte data and return byte data"""
         raise NotImplementedError()
 
-    def to_dict(self):
-        dict_ = super(Encryption, self).to_dict()
-        dict_['packaging_type'] = 'encryption'
-        return dict_
-
 
 class Encoding(entities.Entity):
     """
@@ -121,6 +117,7 @@ class Encoding(entities.Entity):
     """
     _binding = artifact_binding
     _binding_class = _binding.EncodingType
+    _ENCODING_TYPE = None  # overridden by subclasses
 
     algorithm = fields.TypedField("algorithm")
     character_set = fields.TypedField("character_set")
@@ -140,13 +137,68 @@ class Encoding(entities.Entity):
         """This should accept byte data and return byte data"""
         raise NotImplementedError()
 
-    def to_dict(self):
-        dict_ = super(Encoding, self).to_dict()
-        dict_['packaging_type'] = 'encoding'
-        return dict_
 
+class EncryptionFactory(entities.EntityFactory):
+    @classmethod
+    def entity_class(cls, key):
+        return _ENCRYPTION_EXT_MAP.get(key, Encryption)
+
+    @classmethod
+    def dictkey(cls, mapping):
+        return mapping.get("encryption_mechanism")
+
+    @classmethod
+    def objkey(cls, obj):
+        return obj.encryption_mechanism
+
+    @staticmethod
+    def register_extension(cls):
+        _ENCRYPTION_EXT_MAP[cls._ENCRYPTION_TYPE] = cls
+        return cls
+
+
+class CompressionFactory(entities.EntityFactory):
+    @classmethod
+    def entity_class(cls, key):
+        return _COMPRESSION_EXT_MAP.get(key, Compression)
+
+    @classmethod
+    def dictkey(cls, mapping):
+        return mapping.get("compression_mechanism")
+
+    @classmethod
+    def objkey(cls, obj):
+        return obj.compression_mechanism
+
+    @staticmethod
+    def register_extension(cls):
+        _COMPRESSION_EXT_MAP[cls._COMPRESSION_TYPE] = cls
+        return cls
+
+
+class EncodingFactory(entities.EntityFactory):
+    @classmethod
+    def entity_class(cls, key):
+        return _ENCODING_EXT_MAP.get(key, Encoding)
+
+    @classmethod
+    def dictkey(cls, mapping):
+        return mapping.get("algorithm", "Base64")  # default is Base64
+
+    @classmethod
+    def objkey(cls, obj):
+        return getattr(obj, "algorithm", "Base64")  # default is Base64
+
+    @staticmethod
+    def register_extension(cls):
+        _ENCODING_EXT_MAP[cls._ENCODING_TYPE] = cls
+        return cls
 
+
+@CompressionFactory.register_extension
 class ZlibCompression(Compression):
+    _COMPRESSION_TYPE = "zlib"
+
     def __init__(self):
         super(ZlibCompression, self).__init__(compression_mechanism="zlib")
 
@@ -157,7 +209,9 @@ class ZlibCompression(Compression):
         return zlib.decompress(packed_data)
 
 
+@CompressionFactory.register_extension
 class Bz2Compression(Compression):
+    _COMPRESSION_TYPE = "bz2"
 
     def __init__(self):
         super(Bz2Compression, self).__init__(compression_mechanism="bz2")
@@ -169,7 +223,9 @@ class Bz2Compression(Compression):
         return bz2.decompress(packed_data)
 
 
+@EncryptionFactory.register_extension
 class XOREncryption(Encryption):
+    _ENCRYPTION_TYPE = "xor"
 
     def __init__(self, key=None):
         super(XOREncryption, self).__init__(
@@ -184,7 +240,10 @@ class XOREncryption(Encryption):
         return xor(packed_data, self.encryption_key)
 
 
+@EncryptionFactory.register_extension
 class PasswordProtectedZipEncryption(Encryption):
+    _ENCRYPTION_TYPE = "PasswordProtected"
+
     def __init__(self, key=None):
         super(PasswordProtectedZipEncryption, self).__init__(
             encryption_mechanism="PasswordProtected",
@@ -206,7 +265,10 @@ class PasswordProtectedZipEncryption(Encryption):
         return data
 
 
+@EncodingFactory.register_extension
 class Base64Encoding(Encoding):
+    _ENCODING_TYPE = "Base64"
+
     def __init__(self):
         super(Base64Encoding, self).__init__(algorithm="Base64")
 
@@ -217,61 +279,6 @@ class Base64Encoding(Encoding):
         return base64.b64decode(packed_data)
 
 
-class EncryptionFactory(entities.EntityFactory):
-    @classmethod
-    def entity_class(cls, key):
-        if key == "xor":
-            return XOREncryption
-        elif key == "PasswordProtected":
-            return PasswordProtectedZipEncryption
-        else:
-            raise ValueError("Unsupported encryption mechanism: %s" % key)
-
-    @classmethod
-    def dictkey(cls, mapping):
-        return mapping.get("encryption_mechanism")
-
-    @classmethod
-    def objkey(cls, obj):
-        return obj.encryption_mechanism
-
-
-class CompressionFactory(entities.EntityFactory):
-    @classmethod
-    def entity_class(cls, key):
-        if key == "zlib":
-            return ZlibCompression
-        elif key == "bz2":
-            return Bz2Compression
-        else:
-            raise ValueError("Unsupported compression mechanism: %s" % key)
-
-    @classmethod
-    def dictkey(cls, mapping):
-        return mapping.get("compression_mechanism")
-
-    @classmethod
-    def objkey(cls, obj):
-        return obj.compression_mechanism
-
-
-class EncodingFactory(entities.EntityFactory):
-    @classmethod
-    def entity_class(cls, key):
-        if key == "Base64":
-            return Base64Encoding
-        else:
-            raise ValueError("Unsupported encoding algorithm: %s" % key)
-
-    @classmethod
-    def dictkey(cls, mapping):
-        return mapping.get("algorithm", "Base64")  # default is Base64
-
-    @classmethod
-    def objkey(cls, obj):
-        return getattr(obj, "algorithm", "Base64")  # default is Base64
-
-
 class Packaging(entities.Entity):
     """An individual packaging layer."""
     _namespace = 'http://cybox.mitre.org/objects#ArtifactObject-2'

Parsing fails if algorithm, compression_mechanism, or encryption_mechanism are not present
For Encoding, Encryption and Compression API classes we have a Factory that currently is limited in its extensibility. If the class is not found in the if/else clause it will fail hard. Propose a way to register new API classes, as a fallback it will create the an instance of the default class. Yet, keep in mind that trying to access the Articfact.packed_data or Artifact.data properties may return in a NotImplementedError because pack() and unpack() methods are not defined for base classes.

This fix would allow to interact with the parsed object even if a valid API class is not available.
diff --git a/cybox/test/objects/artifact_test.py b/cybox/test/objects/artifact_test.py
index 86d6326..c9a8df4 100644
--- a/cybox/test/objects/artifact_test.py
+++ b/cybox/test/objects/artifact_test.py
@@ -1,7 +1,7 @@
 # Copyright (c) 2017, The MITRE Corporation. All rights reserved.
 # See LICENSE.txt for complete terms.
 
-from base64 import b64encode
+import base64
 import unittest
 from zlib import compress
 
@@ -9,7 +9,8 @@ from mixbox.vendor import six
 from mixbox.vendor.six import u
 
 from cybox.objects.artifact_object import (Artifact, Base64Encoding,
-                                           Bz2Compression, Packaging, RawArtifact, XOREncryption, ZlibCompression)
+                                           Bz2Compression, Encoding, EncodingFactory, Packaging, RawArtifact,
+                                           XOREncryption, ZlibCompression)
 from cybox.test import round_trip
 from cybox.test.objects import ObjectTestCase
 
@@ -115,7 +116,7 @@ class TestArtifact(ObjectTestCase, unittest.TestCase):
         a2 = round_trip(a, Artifact)
         self.assertEqual(self.binary_data, a2.data)
 
-        expected = b64encode(self.binary_data).decode('ascii')
+        expected = base64.b64encode(self.binary_data).decode('ascii')
         self.assertEqual(expected, a2.packed_data)
 
     def test_zlib_base64_encoding(self):
@@ -126,7 +127,7 @@ class TestArtifact(ObjectTestCase, unittest.TestCase):
         a2 = round_trip(a, Artifact)
         self.assertEqual(self.binary_data, a2.data)
 
-        expected = b64encode(compress(self.binary_data)).decode('ascii')
+        expected = base64.b64encode(compress(self.binary_data)).decode('ascii')
         self.assertEqual(expected, a2.packed_data)
 
     def test_encryption(self):
@@ -148,6 +149,29 @@ class TestArtifact(ObjectTestCase, unittest.TestCase):
 
         self.assertEqual(self.binary_data, a2.data)
 
+    def test_custom_encoding(self):
+        @EncodingFactory.register_extension
+        class Base32Encoding(Encoding):
+            _ENCODING_TYPE = "Base32"
+
+            def __init__(self):
+                super(Base32Encoding, self).__init__(algorithm="Base32")
+
+            def pack(self, data):
+                return base64.b32encode(data)
+
+            def unpack(self, packed_data):
+                return base64.b32decode(packed_data)
+
+        a = Artifact(self.binary_data)
+        a.packaging = Packaging()
+        a.packaging.compression.append(Bz2Compression())
+        a.packaging.encryption.append(XOREncryption(0x4a))
+        a.packaging.encoding.append(Base32Encoding())
+        a2 = round_trip(a, Artifact)
+
+        self.assertEqual(self.binary_data, a2.data)
+
 
 class TestArtifactInstance(ObjectTestCase, unittest.TestCase):
     object_type = "ArtifactObjectType"
@@ -159,7 +183,6 @@ class TestArtifactInstance(ObjectTestCase, unittest.TestCase):
             "is_compressed": False,
             "encoding": [
                 {
-                    "packaging_type": "encoding",
                     "algorithm": "Base64"
                 }
             ]

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest nose sphinx sphinx_rtd_theme tox
apt-get update apt-get install -y python3-dev libxml2-dev libxslt1-dev zlib1g-dev
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
babel==2.17.0
cachetools==5.5.2
chardet==5.2.0
colorama==0.4.6
-e git+https://github.com/CybOXProject/python-cybox.git@ee82c7da40ca4638e3ca8d70766150c0dace1b55#egg=cybox
distlib==0.3.9
docutils==0.21.2
exceptiongroup==1.2.2
filelock==3.18.0
iniconfig==2.1.0
Jinja2==3.1.6
lxml==5.3.1
MarkupSafe==3.0.2
mixbox==1.0.5
nose==1.3.7
ordered-set==4.1.0
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
py==1.11.0
Pygments==2.19.1
pyproject-api==1.9.0
pytest==8.3.5
python-dateutil==2.9.0.post0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.3.6
sphinx-rtd-theme==0.2.4
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
virtualenv==20.29.3
weakrefmethod==1.0.3

name: python-cybox
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
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - 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=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - babel==2.17.0
      - cachetools==5.5.2
      - chardet==5.2.0
      - colorama==0.4.6
      - distlib==0.3.9
      - docutils==0.21.2
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - iniconfig==2.1.0
      - jinja2==3.1.6
      - lxml==5.3.1
      - markupsafe==3.0.2
      - mixbox==1.0.5
      - nose==1.3.7
      - ordered-set==4.1.0
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - py==1.11.0
      - pygments==2.19.1
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.3.6
      - sphinx-rtd-theme==0.2.4
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - virtualenv==20.29.3
      - weakrefmethod==1.0.3
prefix: /opt/conda/envs/python-cybox

cybox/test/objects/artifact_test.py::TestArtifact::test_custom_encoding cybox/test/objects/artifact_test.py::TestArtifactInstance::test_round_trip cybox/test/objects/artifact_test.py::TestArtifactInstance::test_round_trip_dict
cybox/test/objects/artifact_test.py::TestRawArtifact::test_xml_output cybox/test/objects/artifact_test.py::TestArtifactEncoding::test_cannot_create_artifact_from_unicode_data cybox/test/objects/artifact_test.py::TestArtifactEncoding::test_cannot_set_nonascii_artifact_packed_data cybox/test/objects/artifact_test.py::TestArtifactEncoding::test_cannot_set_nonascii_data_with_no_packaging cybox/test/objects/artifact_test.py::TestArtifactEncoding::test_setting_ascii_artifact_data_no_packaging cybox/test/objects/artifact_test.py::TestArtifactEncoding::test_setting_ascii_artifact_packed_data_no_packaging cybox/test/objects/artifact_test.py::TestArtifact::test_base64_encoding cybox/test/objects/artifact_test.py::TestArtifact::test_compression cybox/test/objects/artifact_test.py::TestArtifact::test_encryption cybox/test/objects/artifact_test.py::TestArtifact::test_non_ascii_round_trip_raises_error cybox/test/objects/artifact_test.py::TestArtifact::test_object_reference cybox/test/objects/artifact_test.py::TestArtifact::test_round_trip cybox/test/objects/artifact_test.py::TestArtifact::test_round_trip_dict cybox/test/objects/artifact_test.py::TestArtifact::test_round_trip_entity cybox/test/objects/artifact_test.py::TestArtifact::test_round_trip_observable cybox/test/objects/artifact_test.py::TestArtifact::test_set_data_and_packed_data cybox/test/objects/artifact_test.py::TestArtifact::test_type_exists cybox/test/objects/artifact_test.py::TestArtifact::test_zlib_base64_encoding cybox/test/objects/artifact_test.py::TestArtifactInstance::test_object_reference cybox/test/objects/artifact_test.py::TestArtifactInstance::test_round_trip_entity cybox/test/objects/artifact_test.py::TestArtifactInstance::test_round_trip_observable cybox/test/objects/artifact_test.py::TestArtifactInstance::test_type_exists cybox/test/objects/artifact_test.py::TestArtifactPattern::test_object_reference cybox/test/objects/artifact_test.py::TestArtifactPattern::test_round_trip cybox/test/objects/artifact_test.py::TestArtifactPattern::test_round_trip_dict cybox/test/objects/artifact_test.py::TestArtifactPattern::test_round_trip_entity cybox/test/objects/artifact_test.py::TestArtifactPattern::test_round_trip_observable cybox/test/objects/artifact_test.py::TestArtifactPattern::test_type_exists
cybox/objects/artifact_object.py
cybox/objects/artifact_object.py