1414# KIND, either express or implied. See the License for the
1515# specific language governing permissions and limitations
1616# under the License.
17- """Key management client interface for table encryption, and an in-memory implementation ."""
17+ """Key management client interface for table encryption."""
1818
1919from __future__ import annotations
2020
2121from abc import ABC , abstractmethod
2222from dataclasses import dataclass , field
2323
24- from pyiceberg .encryption . ciphers import AesGcmCipher , AesKeySize , SecureKey
24+ from pyiceberg .typedef import EMPTY_DICT , Properties
2525
2626
2727@dataclass (frozen = True )
@@ -36,8 +36,16 @@ class KeyManagementClient(ABC):
3636 """A base class for key management service implementations.
3737
3838 Wraps and unwraps table encryption keys using master keys that the service holds.
39+
40+ Implementations are loaded by name from the catalog properties, so a subclass must keep
41+ this constructor signature, as `FileIO` does.
3942 """
4043
44+ properties : Properties
45+
46+ def __init__ (self , properties : Properties = EMPTY_DICT ) -> None :
47+ self .properties = properties
48+
4149 @abstractmethod
4250 def wrap_key (self , key : bytes , wrapping_key_id : str ) -> bytes :
4351 """Wrap a key using the master key identified by `wrapping_key_id`.
@@ -67,48 +75,3 @@ def generate_key(self, wrapping_key_id: str) -> GeneratedKey:
6775 wrapping_key_id (str): Identifies the master key held by the service.
6876 """
6977 raise NotImplementedError (f"{ type (self ).__name__ } does not support key generation" )
70-
71-
72- class MemoryKeyManagementClient (KeyManagementClient ):
73- """A key management service that holds its master keys in memory, for testing and demonstration.
74-
75- Master keys live only in this process, with no durability or access control, so this is
76- not for production use. Mirrors Java's `MemoryMockKMS` and iceberg-rust's
77- `MemoryKeyManagementClient`.
78- """
79-
80- def __init__ (self , master_key_size : AesKeySize = AesKeySize .BITS_128 ) -> None :
81- self ._master_key_size = master_key_size
82- self ._master_keys : dict [str , SecureKey ] = {}
83-
84- def __repr__ (self ) -> str :
85- """Return a representation that counts the master keys without exposing them."""
86- return f"MemoryKeyManagementClient(master_key_size={ self ._master_key_size !r} , key_count={ len (self ._master_keys )} )"
87-
88- def add_master_key (self , wrapping_key_id : str , key : SecureKey | None = None ) -> SecureKey :
89- """Register a master key under `wrapping_key_id`, generating one when `key` is omitted.
90-
91- Args:
92- wrapping_key_id (str): The id to register the master key under.
93- key (SecureKey | None): Known key material, for tests that share it with another client.
94- """
95- if wrapping_key_id in self ._master_keys :
96- raise ValueError (f"Master key already exists: { wrapping_key_id } " )
97-
98- master_key = SecureKey .generate (self ._master_key_size ) if key is None else key
99- self ._master_keys [wrapping_key_id ] = master_key
100- return master_key
101-
102- def _cipher (self , wrapping_key_id : str ) -> AesGcmCipher :
103- if (master_key := self ._master_keys .get (wrapping_key_id )) is None :
104- raise ValueError (f"Master key not found: { wrapping_key_id } " )
105-
106- return AesGcmCipher (master_key )
107-
108- def wrap_key (self , key : bytes , wrapping_key_id : str ) -> bytes :
109- """Wrap a key with the registered master key, without AAD, as Java and iceberg-rust do."""
110- return self ._cipher (wrapping_key_id ).encrypt (key )
111-
112- def unwrap_key (self , wrapped_key : bytes , wrapping_key_id : str ) -> bytes :
113- """Unwrap a key wrapped by `wrap_key`."""
114- return self ._cipher (wrapping_key_id ).decrypt (wrapped_key )
0 commit comments