Skip to content

Commit 6b6d239

Browse files
authored
Merge pull request #1527 from serengil/feat-task-1511-encrypt-embeddings-feature
encrypt module added
2 parents 54e696f + 0308c9c commit 6b6d239

File tree

8 files changed

+348
-24
lines changed

8 files changed

+348
-24
lines changed

README.md

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,10 @@ from lightphe import LightPHE
324324
# build an additively homomorphic cryptosystem (e.g. Paillier) on-prem
325325
cs = LightPHE(algorithm_name = "Paillier", precision = 19)
326326

327-
# define plain vectors for source and target
328-
alpha = DeepFace.represent("img1.jpg")[0]["embedding"]
327+
# define encrypted and plain vectors
328+
encrypted_alpha = DeepFace.represent("source.jpg", cryptosystem=cs)[0]["encrypted_embedding"]
329329
beta = DeepFace.represent("target.jpg")[0]["embedding"]
330330

331-
# encrypt source embedding on-prem - private key not required
332-
encrypted_alpha = cs.encrypt(alpha)
333-
334331
# dot product of encrypted & plain embedding in cloud - private key not required
335332
encrypted_cosine_similarity = encrypted_alpha @ beta
336333

deepface/DeepFace.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import numpy as np
1414
import pandas as pd
1515
import tensorflow as tf
16+
from lightphe import LightPHE
1617

1718
# package dependencies
1819
from deepface.commons import package_utils, folder_utils
@@ -398,6 +399,7 @@ def represent(
398399
max_faces: Optional[int] = None,
399400
l2_normalize: bool = False,
400401
minmax_normalize: bool = False,
402+
cryptosystem: Optional[LightPHE] = None,
401403
) -> Union[List[Dict[str, Any]], List[List[Dict[str, Any]]]]:
402404
"""
403405
Represent facial images as multi-dimensional vector embeddings.
@@ -441,6 +443,20 @@ def represent(
441443
minmax_normalize (bool): Flag to enable min-max normalization of the output embeddings
442444
to the range [0, 1].
443445
446+
cryptosystem (LightPHE): An instance of a partially homomorphic encryption system
447+
to encrypt the output embeddings. If provided, the embeddings will be encrypted
448+
using the specified cryptosystem. Then, you will be able to perform homomorphic
449+
operations on the encrypted embeddings without decrypting them first.
450+
Check out these papers to find out more:
451+
452+
[1] Serengil, S. I. & Ozpinar, A. (2025). "LightPHE: Integrating Partially
453+
Homomorphic Encryption into Python with Extensive Cloud Environment Evaluations",
454+
https://arxiv.org/abs/2408.05219.
455+
456+
[2] Serengil, S. I. & Ozpinar, A. (2025). "Encrypted Vector Similarity Computations
457+
Using Partially Homomorphic Encryption: Applications and Performance Analysis",
458+
https://arxiv.org/abs/2503.05850.
459+
444460
Returns:
445461
results (List[Dict[str, Any]] or List[Dict[str, Any]]): A list of dictionaries.
446462
Result type becomes List of List of Dict if batch input passed.
@@ -457,6 +473,9 @@ def represent(
457473
458474
- face_confidence (float): Confidence score of face detection. If `detector_backend` is set
459475
to 'skip', the confidence will be 0 and is nonsensical.
476+
477+
- encrypted_embedding (List[Any]): Encrypted multidimensional vector representing
478+
facial features. This field is included only if a `cryptosystem` is provided.
460479
"""
461480
return representation.represent(
462481
img_path=img_path,
@@ -470,6 +489,7 @@ def represent(
470489
max_faces=max_faces,
471490
l2_normalize=l2_normalize,
472491
minmax_normalize=minmax_normalize,
492+
cryptosystem=cryptosystem,
473493
)
474494

475495

deepface/commons/embed_utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from typing import List, Union
2+
3+
4+
def is_flat_embedding(x: Union[List[float], List[List[float]]]) -> bool:
5+
"""
6+
Check if the embeddings represent a single flat list of floats
7+
rather than a list of list of float.
8+
Args:
9+
x (List[float] or List[List[float]]): Embeddings to check.
10+
Returns:
11+
bool: True if x is a flat list of floats, False otherwise.
12+
"""
13+
return isinstance(x, list) and all(isinstance(i, (int, float)) for i in x)

deepface/modules/encryption.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# built-in dependencies
2+
from typing import List, Union, Optional, cast
3+
4+
# third-party dependencies
5+
from lightphe import LightPHE
6+
from lightphe.models.Tensor import EncryptedTensor
7+
import numpy as np
8+
9+
# project dependencies
10+
from deepface.commons.embed_utils import is_flat_embedding
11+
from deepface.commons.logger import Logger
12+
13+
logger = Logger()
14+
15+
16+
# pylint: disable=no-else-return
17+
def encrypt_embeddings(
18+
embeddings: Union[List[float], List[List[float]]], cryptosystem: Optional[LightPHE] = None
19+
) -> Union[EncryptedTensor, List[EncryptedTensor], None]:
20+
"""
21+
Encrypt embeddings using a provided cryptosystem.
22+
Args:
23+
embeddings (List[float] or List[List[float]]): Embeddings to encrypt.
24+
cryptosystem (LightPHE): Cryptosystem to use for encryption.
25+
Returns:
26+
EncryptedTensor or List[EncryptedTensor] or None: Encrypted embeddings or None
27+
if no cryptosystem is provided.
28+
"""
29+
if cryptosystem is None:
30+
return None
31+
32+
if is_flat_embedding(embeddings):
33+
embedding = cast(List[float], embeddings) # let type checker know
34+
encrypted_embedding = encrypt_embedding(embedding, cryptosystem)
35+
return encrypted_embedding
36+
else:
37+
encrypted_embeddings: List[EncryptedTensor] = []
38+
for embedding in embeddings:
39+
embedding = cast(List[float], embedding) # let type checker know
40+
encrypted_embedding = encrypt_embedding(embedding, cryptosystem)
41+
encrypted_embeddings.append(encrypted_embedding)
42+
43+
if all(item is None for item in encrypted_embeddings):
44+
return None
45+
46+
return encrypted_embeddings
47+
48+
49+
def encrypt_embedding(embeddings: List[float], cryptosystem: LightPHE) -> Optional[EncryptedTensor]:
50+
"""
51+
Encrypt an embedding using a provided cryptosystem.
52+
Args:
53+
embeddings (List[float]): Embedding to encrypt.
54+
cryptosystem (LightPHE): Cryptosystem to use for encryption.
55+
Returns:
56+
EncryptedTensor or None: Encrypted embedding or None if encryption is skipped.
57+
"""
58+
if any(x < 0 for x in embeddings):
59+
logger.warn(
60+
"Skipping encryption because it contains negative values."
61+
"Consider to set minmax_normalize=True in DeepFace.represent method."
62+
)
63+
return None
64+
65+
norm = np.linalg.norm(embeddings)
66+
if not np.isclose(norm, 1.0):
67+
logger.warn(
68+
"Skipping encryption because given embedding is not l_2 normalized."
69+
"Consider to set l2_normalize=True in DeepFace.represent method."
70+
)
71+
return None
72+
73+
encrypted_embeddings = cryptosystem.encrypt(embeddings, silent=True)
74+
return encrypted_embeddings

deepface/modules/normalization.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,7 @@
66

77
# project dependencies
88
from deepface.config.minmax import get_minmax_values
9-
10-
11-
def is_flat_embedding(x: Union[List[float], List[List[float]]]) -> bool:
12-
"""
13-
Check if the embeddings represent a single flat list of floats
14-
rather than a list of list of float.
15-
Args:
16-
x (List[float] or List[List[float]]): Embeddings to check.
17-
Returns:
18-
bool: True if x is a flat list of floats, False otherwise.
19-
"""
20-
return isinstance(x, list) and all(isinstance(i, (int, float)) for i in x)
9+
from deepface.commons.embed_utils import is_flat_embedding
2110

2211

2312
def normalize_embedding_minmax(

deepface/modules/representation.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44

55
# 3rd party dependencies
66
import numpy as np
7+
from lightphe import LightPHE
78

89
# project dependencies
910
from deepface.commons import image_utils
1011
from deepface.modules import modeling, detection, preprocessing
1112
from deepface.models.FacialRecognition import FacialRecognition
1213
from deepface.modules.normalization import normalize_embedding_l2, normalize_embedding_minmax
14+
from deepface.modules.encryption import encrypt_embeddings
15+
from deepface.commons.logger import Logger
16+
17+
logger = Logger()
1318

1419

1520
def represent(
@@ -24,6 +29,7 @@ def represent(
2429
max_faces: Optional[int] = None,
2530
l2_normalize: bool = False,
2631
minmax_normalize: bool = False,
32+
cryptosystem: Optional[LightPHE] = None,
2733
) -> Union[List[Dict[str, Any]], List[List[Dict[str, Any]]]]:
2834
"""
2935
Represent facial images as multi-dimensional vector embeddings.
@@ -63,6 +69,10 @@ def represent(
6369
minmax_normalize (bool): Flag to enable min-max normalization of the output embeddings
6470
to the range [0, 1].
6571
72+
cryptosystem (LightPHE): An instance of a partially homomorphic encryption system
73+
to encrypt the output embeddings. If provided, the embeddings will be encrypted
74+
using the specified cryptosystem.
75+
6676
Returns:
6777
results (List[Dict[str, Any]] or List[Dict[str, Any]]): A list of dictionaries.
6878
Result type becomes List of List of Dict if batch input passed.
@@ -77,6 +87,8 @@ def represent(
7787
the full image area and is nonsensical.
7888
- face_confidence (float): Confidence score of face detection. If `detector_backend` is set
7989
to 'skip', the confidence will be 0 and is nonsensical.
90+
- encrypted_embedding (List[Any]): Encrypted multidimensional vector representing
91+
facial features. This field is included only if a `cryptosystem` is provided.
8092
"""
8193
resp_objs = []
8294

@@ -178,15 +190,20 @@ def represent(
178190
if l2_normalize:
179191
embeddings = normalize_embedding_l2(embeddings)
180192

193+
encrypted_embeddings = encrypt_embeddings(embeddings, cryptosystem)
194+
181195
resp_objs_dict = defaultdict(list)
182196
for idy, batch_index in enumerate(batch_indexes):
183-
resp_objs_dict[batch_index].append(
184-
{
185-
"embedding": embeddings if len(batch_images) == 1 else embeddings[idy],
186-
"facial_area": batch_regions[idy],
187-
"face_confidence": batch_confidences[idy],
188-
}
189-
)
197+
resp_obj = {
198+
"embedding": embeddings if len(batch_images) == 1 else embeddings[idy],
199+
"facial_area": batch_regions[idy],
200+
"face_confidence": batch_confidences[idy],
201+
}
202+
if cryptosystem is not None and encrypted_embeddings is not None:
203+
resp_obj["encrypted_embedding"] = (
204+
encrypted_embeddings if len(batch_images) == 1 else encrypted_embeddings[idy]
205+
)
206+
resp_objs_dict[batch_index].append(resp_obj)
190207

191208
resp_objs = [resp_objs_dict[idx] for idx in range(len(images))]
192209

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ mtcnn>=0.1.0
1313
retina-face>=0.0.14
1414
fire>=0.4.0
1515
gunicorn>=20.1.0
16+
lightphe>=0.0.15

0 commit comments

Comments
 (0)