├── .deepsource.toml ├── .github └── workflows │ ├── codeql-analysis.yml │ ├── jekyll-gh-pages.yml │ └── python-app.yml ├── .idea ├── .gitignore ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── libsse.iml ├── misc.xml ├── modules.xml ├── vcs.xml └── workspace.xml ├── Dockerfile ├── LICENSE ├── README.md ├── data_persistence ├── __init__.py ├── bytes_shelf.py ├── interfaces.py ├── persistent_array.py └── persistent_dict.py ├── docker-compose.yml ├── example_db.json ├── frontend ├── README.md ├── __init__.py ├── client │ ├── __init__.py │ ├── commands.py │ └── services │ │ ├── __init__.py │ │ ├── file_manager.py │ │ ├── service.py │ │ └── service_name_handler.py ├── common │ ├── __init__.py │ ├── constants.py │ └── utils.py ├── constants.py └── server │ ├── __init__.py │ ├── connector.py │ └── services │ ├── __init__.py │ ├── comm.py │ ├── file_manager.py │ ├── service.py │ └── services_manager.py ├── global_config.py ├── requirements.txt ├── run_client.py ├── run_server.py ├── schemes ├── ANSS16 │ ├── Scheme3 │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ └── __init__.py ├── CGKO06 │ ├── SSE1 │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ ├── SSE2 │ │ ├── __init__.py │ │ ├── config.json │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ └── __init__.py ├── CJJ14 │ ├── Pi2Lev │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ ├── PiBas │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ ├── PiPack │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ ├── PiPtr │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ └── __init__.py ├── CT14 │ ├── Pi │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ └── __init__.py ├── DP17 │ ├── Pi │ │ ├── __init__.py │ │ ├── config.py │ │ ├── construction.py │ │ └── structures.py │ └── __init__.py ├── __init__.py └── interface │ ├── __init__.py │ ├── config.py │ ├── inverted_index_sse.py │ ├── module_loader.py │ ├── objects.py │ └── structures.py ├── test ├── __init__.py ├── test_bits.py ├── test_database_utils.py ├── test_fpe.py ├── test_persistent_array.py ├── test_persistent_dict.py ├── test_sse_schemes │ ├── __init__.py │ ├── test_ANSS16_Scheme3.py │ ├── test_CGKO06_SSE1.py │ ├── test_CGKO06_SSE2.py │ ├── test_CJJ14_Pi2Lev.py │ ├── test_CJJ14_PiBas.py │ ├── test_CJJ14_PiPack.py │ ├── test_CJJ14_PiPtr.py │ ├── test_CT14_Pi.py │ └── test_DP17_Pi.py └── tools │ ├── __init__.py │ ├── faker.py │ └── slice_test_tools.py └── toolkit ├── __init__.py ├── bits.py ├── bits_utils.py ├── bytes_utils.py ├── config_manager.py ├── constants.py ├── data_structures ├── __init__.py └── extended_bytes.py ├── database_utils.py ├── hash.py ├── list_utils.py ├── logger ├── __init__.py └── logger.py ├── prf ├── __init__.py ├── abstraction.py └── hmac_prf.py ├── prp ├── __init__.py ├── abstraction.py ├── bitwise_fpe_prp.py ├── hmac_luby_rackoff_prp.py └── luby_rackoff_prp.py ├── symmetric_encryption ├── __init__.py ├── abstraction.py ├── aes.py └── fpe.py └── symmetric_padding.py /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '35 11 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'python' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages.yml: -------------------------------------------------------------------------------- 1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages 2 | name: Deploy Jekyll with GitHub Pages dependencies preinstalled 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Build job 26 | build: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - name: Setup Pages 32 | uses: actions/configure-pages@v4 33 | - name: Build with Jekyll 34 | uses: actions/jekyll-build-pages@v1 35 | with: 36 | source: ./ 37 | destination: ./_site 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | 41 | # Deployment job 42 | deploy: 43 | environment: 44 | name: github-pages 45 | url: ${{ steps.deployment.outputs.page_url }} 46 | runs-on: ubuntu-latest 47 | needs: build 48 | steps: 49 | - name: Deploy to GitHub Pages 50 | id: deployment 51 | uses: actions/deploy-pages@v4 52 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.10 20 | uses: actions/setup-python@v2 21 | with: 22 | python-version: "3.10" 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | pip install flake8 pytest 27 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 28 | - name: Lint with flake8 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Test with pytest 35 | run: | 36 | pytest 37 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/libsse.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Image 2 | FROM python:3.8-alpine 3 | EXPOSE 8001 4 | # RUN apk add --update bash curl git && rm -rf /var/cache/apk/* 5 | 6 | COPY . /usr/src/app 7 | WORKDIR /usr/src/app 8 | 9 | RUN apk add build-base 10 | RUN apk add libffi-dev 11 | RUN apk add openssl 12 | RUN pip install -r requirements.txt 13 | CMD python3 run_server.py start -------------------------------------------------------------------------------- /data_persistence/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py 7 | @time: 2022/05/08 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # For local development 2 | version: "3" 3 | services: 4 | web: 5 | build: . 6 | ports: 7 | - "8001:8001" 8 | -------------------------------------------------------------------------------- /example_db.json: -------------------------------------------------------------------------------- 1 | { 2 | "China": [ 3 | "3A4B1ACC12AA1B2D", 4 | "2DDD1FFF1122BBCC", 5 | "1122AA4B101A2812", 6 | "C2C2C2C21010AACC" 7 | ], 8 | "Github": [ 9 | "1A1ADD2C2320A1CC", 10 | "2222CC1F1421A22A" 11 | ], 12 | "Chen": [ 13 | "1BB2BB2B1010112A", 14 | "233278781010212C", 15 | "88771ABB101AA02B" 16 | ] 17 | } -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Plan (Chinese) 2 | 3 | ## Prototype 0.1 4 | 5 | ### 功能概览 6 | - 服务端将加密索引和配置文件作为文件存储在磁盘中(文件夹使用服务ID标识), 当和客户端建立起连接后,再从中读取至内存中。 7 | - 客户端将配置文件的Hash值作为服务ID (Service ID, sid), 8 | 如果sid已存在,则服务器根据sid找到配置文件和数据库文件,读取至内存中,并根据配置初始化SSE程序; 9 | 否则,则需要客户端传输配置文件至服务端中,进行数据库的初始化操作。 10 | - 该版本不支持文档本身的加解密,支持加密索引功能。所以配置文件目前也是写死的, 11 | 不用文件扫描功能来配置参数。 12 | - 使用websockets进行客户端和服务端的通讯 13 | - 对于单个服务(sid),服务器有以下几个状态: 14 | - `0`: 未创建/未配置(不保存任何信息) 15 | - `1`: 已配置但没有上传加密数据库(只保存配置文件) 16 | - `2`: 已完全配置状态(保存配置文件和数据库),可以进行搜索 17 | - `0`: 删除状态 --> 删除sid对应的目录,重新进入未配置状态 18 | - 对于单个服务(sid),客户端有以下几个状态:(bit标记) 19 | - 第1个bit: 表示配置文件创建了没有 20 | - 第2个bit: 表示配置文件上传了没有 21 | - 第3个bit: 表示密钥创建了没有 22 | - 第4个bit: 表示数据库加密了没有 23 | - 第5个bit: 表示数据库上传了没有 24 | - 配置文件应包含的字段: 25 | - `scheme`: 选用的方案 26 | - `salt`: 客户端选用的盐值, 用于区别相同配置的不同服务 27 | - 用户的配置信息 28 | - 对于每个服务,服务器应该需要维护以下状态信息(服务全局状态): 29 | - `status`: 当前服务状态 30 | - 对于每个服务,客户端需要维护以下全局状态: 31 | - `status`: 当前服务状态 32 | - `key`: 密钥信息: 需要用口令加密 33 | 34 | 35 | ### websockets字段说明 36 | 37 | - `sid`: `str`类型,服务ID, 38 | - `type`: `str`类型,信息类型 --> 便于websockets路由 39 | - `init`: 首次连接,以开启服务。服务器进入服务状态。如果服务器发现`sid`不存在,则提醒客户端上传配置文件。 40 | - `upload_config`: 传输配置文件,需要有`config`字段传输配置文件信息(json格式)。 41 | 如果服务器发现`sid`已经存在,拒绝配置,说明该`sid`已经配置好了。 42 | 否则,配置该服务,创建`sid`对应目录,然后写入到该目录内,并进入已配置但没有上传加密数据库状态; 43 | - `upload_db`: 传输加密数据库,需要有`db`字段传输配置文件信息(二进制形式)。 44 | 如果服务器发现该`sid`已经存在数据库 or 未配置,拒绝接收。否则,写入加密数据库到该目录内,并进入已完全配置状态; 45 | - `search`: 搜索请求,需要有`token`字段传输关键字信息(二进制形式)。 46 | 如果服务器发现该`sid`未完全配置,拒绝搜索。否则,进行搜索; 47 | - `result`: 搜索结果,需要有`result`字段传输结果(二进制形式)。 48 | - `delete`: 删除服务 49 | 如果服务器发现`sid`不存在,则返回错误信息,否则,删除该服务 50 | - `error`: 错误信息,需要使用`msg`传输错误信息。 51 | 52 | ### 目前发现的问题 53 | 54 | - 断开连接后, 数据库也断开了,应该需要维持一段时间(目前的方案也只能单点连接,如果多客户端场景会有并发的可能) 55 | -------------------------------------------------------------------------------- /frontend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezaChen/SSEPy/33ba96ac72d5c6ab565abaafd2b4a52ce25c51d1/frontend/__init__.py -------------------------------------------------------------------------------- /frontend/client/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/14 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /frontend/client/services/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/17 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /frontend/client/services/file_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: file_manager.py 7 | @time: 2022/03/15 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import json 14 | import pathlib 15 | import pickle 16 | import shutil 17 | 18 | _PROGRAM_DIR_PATH = pathlib.Path.home().joinpath(".sse/client/") 19 | _PROGRAM_PATH = pathlib.Path(_PROGRAM_DIR_PATH) 20 | if not _PROGRAM_PATH.exists(): 21 | _PROGRAM_PATH.mkdir(exist_ok=True, parents=True) 22 | 23 | 24 | def check_sid_local_file_valid(sid: str): 25 | return _PROGRAM_PATH.joinpath(sid).exists() \ 26 | and _PROGRAM_PATH.joinpath(sid).joinpath("config.json").exists() \ 27 | and _PROGRAM_PATH.joinpath(sid).joinpath("service_meta").exists() 28 | 29 | 30 | def create_sid_folder(sid: str): 31 | _PROGRAM_PATH.joinpath(sid).mkdir() 32 | 33 | 34 | def delete_sid_folder(sid: str): 35 | shutil.rmtree(_PROGRAM_PATH.joinpath(sid)) 36 | 37 | 38 | def read_service_config(sid: str) -> dict: 39 | return json.loads(_PROGRAM_PATH.joinpath(sid).joinpath("config.json").read_text(encoding="utf8")) 40 | 41 | 42 | def write_service_config(sid: str, config: dict): 43 | with open(_PROGRAM_PATH.joinpath(sid).joinpath("config.json"), "w") as f: 44 | json.dump(config, f) 45 | 46 | 47 | def read_service_meta(sid: str) -> dict: 48 | return pickle.loads(_PROGRAM_PATH.joinpath(sid).joinpath("service_meta").read_bytes()) 49 | 50 | 51 | def write_service_meta(sid: str, meta: dict): 52 | with open(_PROGRAM_PATH.joinpath(sid).joinpath("service_meta"), "wb") as f: 53 | pickle.dump(meta, f) 54 | 55 | 56 | def read_encrypted_database(sid: str) -> bytes: 57 | edb_bytes = _PROGRAM_PATH.joinpath(sid).joinpath("edb").read_bytes() 58 | return edb_bytes 59 | 60 | 61 | def write_encrypted_database(sid: str, edb_bytes: bytes): 62 | with open(_PROGRAM_PATH.joinpath(sid).joinpath("edb"), "wb") as f: 63 | f.write(edb_bytes) 64 | 65 | 66 | def delete_encrypted_database(sid: str): 67 | edb_path = _PROGRAM_PATH.joinpath(sid).joinpath("edb") 68 | edb_path.unlink(missing_ok=True) 69 | 70 | 71 | def write_key(sid: str, key_bytes: bytes): 72 | with open(_PROGRAM_PATH.joinpath(sid).joinpath("key"), "wb") as f: 73 | f.write(key_bytes) 74 | 75 | 76 | def read_key(sid: str) -> bytes: 77 | key_bytes = _PROGRAM_PATH.joinpath(sid).joinpath("key").read_bytes() 78 | return key_bytes 79 | -------------------------------------------------------------------------------- /frontend/client/services/service_name_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: service_name_handler.py 7 | @time: 2022/03/23 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | Since the length of the sid is too long, 13 | making the sid not easy to be remembered, 14 | each service should have an alias (service name) to make it easy for people to remember. 15 | 16 | The module records the mapping of service alias to sid 17 | and handles the corresponding configuration reads and writes. 18 | """ 19 | import json 20 | import pathlib 21 | from typing import Optional, Dict, Any 22 | 23 | _PROGRAM_DIR_PATH = pathlib.Path.home().joinpath(".sse/client/") 24 | SERVICE_MAPPING_PATH = pathlib.Path(_PROGRAM_DIR_PATH).joinpath("service_mapping.json") 25 | 26 | 27 | def _get_service_mapping_read_and_write_function(): 28 | __service_name_mapping = None 29 | 30 | def _read_service_mapping() -> Optional[Dict[Any, Any]]: 31 | nonlocal __service_name_mapping 32 | 33 | if __service_name_mapping is not None: 34 | return __service_name_mapping 35 | try: 36 | with open(SERVICE_MAPPING_PATH) as f: 37 | __service_name_mapping = json.load(f) 38 | return __service_name_mapping 39 | except (FileNotFoundError, json.decoder.JSONDecodeError): 40 | return {} 41 | 42 | def _write_service_mapping(new_mapping: dict): 43 | nonlocal __service_name_mapping 44 | __service_name_mapping = new_mapping 45 | if not _PROGRAM_DIR_PATH.exists(): 46 | _PROGRAM_DIR_PATH.mkdir(parents=True) 47 | 48 | with open(SERVICE_MAPPING_PATH, "w") as f: 49 | json.dump(new_mapping, f) 50 | 51 | return _read_service_mapping, _write_service_mapping 52 | 53 | 54 | read_service_mapping, write_service_mapping = _get_service_mapping_read_and_write_function() 55 | 56 | 57 | def get_service_id_by_sname(sname: str) -> str: 58 | mapping = read_service_mapping() 59 | try: 60 | return mapping[sname] 61 | except KeyError: 62 | raise KeyError(f"The service id corresponding to sname {sname} not found.") 63 | 64 | 65 | def record_sname_id_pair(sname: str, sid: str): 66 | mapping = read_service_mapping() 67 | if sname in mapping: 68 | raise KeyError(f"The service name {sname} already exists.") 69 | mapping[sname] = sid 70 | write_service_mapping(mapping) 71 | -------------------------------------------------------------------------------- /frontend/common/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: services_manager.py 7 | @time: 2023/12/17 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: common constants and functions for client and server endpoints. 12 | """ 13 | -------------------------------------------------------------------------------- /frontend/common/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: services_manager.py 7 | @time: 2023/12/17 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: constants shared by clients and servers 12 | """ 13 | 14 | 15 | # Types of messages that can be sent between the client and server 16 | class MsgType: 17 | # init echo 18 | INIT = "init" 19 | # service config 20 | CONFIG = "config" 21 | # upload encrypted databases 22 | UPLOAD_DB = "upload_edb" 23 | # for search request 24 | TOKEN = "token" 25 | RESULT = "result" 26 | # for debug 27 | CONTROL = "control" 28 | -------------------------------------------------------------------------------- /frontend/common/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: services_manager.py 7 | @time: 2024/1/6 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: utility functions for frontend module 12 | """ 13 | 14 | __all__ = [ 15 | 'shorten_sid', 16 | ] 17 | 18 | 19 | def shorten_sid(sid: str) -> str: 20 | """ 21 | shorten sid for display 22 | :param sid: Service ID 23 | :return: 24 | """ 25 | return sid[:8] 26 | -------------------------------------------------------------------------------- /frontend/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: constants.py 7 | @time: 2022/03/15 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | KEY_TYPE = "type" 15 | KEY_SID = "sid" 16 | 17 | TYPE_INIT = "init" 18 | -------------------------------------------------------------------------------- /frontend/server/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/14 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /frontend/server/connector.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: connector.py 7 | @time: 2022/03/14 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import asyncio 15 | import pickle 16 | 17 | import websockets 18 | 19 | from frontend.constants import KEY_SID, KEY_TYPE, TYPE_INIT 20 | from frontend.server.services.services_manager import ServicesManager 21 | from global_config import ServerConfig 22 | 23 | _sse_service_manager = ServicesManager() 24 | 25 | 26 | async def handler(websocket, path): 27 | """ 28 | Handle a connection and dispatch it according to who is connecting. 29 | 30 | """ 31 | message = await websocket.recv() 32 | event = pickle.loads(message) 33 | assert KEY_SID in event and KEY_TYPE in event 34 | assert event[KEY_TYPE] == TYPE_INIT 35 | sid = event[KEY_SID] 36 | 37 | await _sse_service_manager.create_service(sid, websocket) 38 | 39 | 40 | async def run_server(host, port): 41 | async with websockets.serve(handler, host, port, max_size=None): 42 | await asyncio.Future() # run forever 43 | 44 | 45 | if __name__ == "__main__": 46 | asyncio.run(run_server(ServerConfig.HOST, ServerConfig.PORT)) 47 | -------------------------------------------------------------------------------- /frontend/server/services/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/15 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /frontend/server/services/comm.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: services_manager.py 7 | @time: 2023/12/17 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import asyncio 14 | import pickle 15 | import typing 16 | 17 | if typing.TYPE_CHECKING: 18 | from websockets import WebSocketServerProtocol 19 | 20 | __all__ = [ 21 | 'send_message', 22 | ] 23 | 24 | 25 | def send_message( 26 | websocket: 'WebSocketServerProtocol', 27 | sid: str, 28 | msg_type: str, content: bytes, **additional_field 29 | ) -> asyncio.Task: 30 | msg_dict = { 31 | 'type': msg_type, 32 | 'sid': sid, 33 | 'content': content 34 | } 35 | msg_dict.update(additional_field) 36 | return asyncio.create_task(websocket.send(pickle.dumps(msg_dict))) 37 | -------------------------------------------------------------------------------- /frontend/server/services/file_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: file_manager.py 7 | @time: 2022/03/15 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import json 14 | import pathlib 15 | import pickle 16 | import shutil 17 | 18 | _PROGRAM_DIR_PATH = pathlib.Path.home().joinpath(".sse") 19 | _PROGRAM_PATH = pathlib.Path(_PROGRAM_DIR_PATH) 20 | if not _PROGRAM_PATH.exists(): 21 | _PROGRAM_PATH.mkdir(exist_ok=True) 22 | 23 | 24 | def check_sid_folder_exist(sid: str): 25 | return _PROGRAM_PATH.joinpath(sid).exists() 26 | 27 | 28 | def create_sid_folder(sid: str): 29 | _PROGRAM_PATH.joinpath(sid).mkdir() 30 | 31 | 32 | def delete_sid_folder(sid: str): 33 | shutil.rmtree(_PROGRAM_PATH.joinpath(sid)) 34 | 35 | 36 | def read_service_config(sid: str) -> dict: 37 | return json.loads(_PROGRAM_PATH.joinpath(sid).joinpath("config.json").read_text(encoding="utf8")) 38 | 39 | 40 | def write_service_config(sid: str, config: dict): 41 | service_dir_path = _PROGRAM_PATH.joinpath(sid) 42 | if not service_dir_path.exists(): 43 | return 44 | 45 | with open(service_dir_path.joinpath("config.json"), "w") as f: 46 | json.dump(config, f) 47 | 48 | 49 | def read_service_meta(sid: str) -> dict: 50 | return pickle.loads(_PROGRAM_PATH.joinpath(sid).joinpath("service_meta").read_bytes()) 51 | 52 | 53 | def write_service_meta(sid: str, meta: dict): 54 | service_dir_path = _PROGRAM_PATH.joinpath(sid) 55 | if not service_dir_path.exists(): 56 | return 57 | 58 | with open(service_dir_path.joinpath("service_meta"), "wb") as f: 59 | pickle.dump(meta, f) 60 | 61 | 62 | def read_encrypted_database(sid: str) -> bytes: 63 | edb_bytes = _PROGRAM_PATH.joinpath(sid).joinpath("edb").read_bytes() 64 | return edb_bytes 65 | 66 | 67 | def write_encrypted_database(sid: str, edb_bytes: bytes): 68 | service_dir_path = _PROGRAM_PATH.joinpath(sid) 69 | if not service_dir_path.exists(): 70 | return 71 | 72 | with open(service_dir_path.joinpath("edb"), "wb") as f: 73 | f.write(edb_bytes) 74 | -------------------------------------------------------------------------------- /frontend/server/services/services_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: services_manager.py 7 | @time: 2022/03/15 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import asyncio 14 | 15 | from websockets.legacy.server import WebSocketServerProtocol 16 | 17 | from frontend.common.constants import MsgType 18 | from frontend.common.utils import shorten_sid 19 | from frontend.server.services.comm import send_message 20 | from frontend.server.services.service import Service 21 | from toolkit.logger.logger import getSSELogger 22 | 23 | logger = getSSELogger("sse_server") 24 | 25 | 26 | class ServicesManager: 27 | def __init__(self): 28 | # The access to the service dictionary may have competition, 29 | # so we need to introduce a lock to ensure the access to the dictionary is concurrent safe. 30 | self._access_dict_lock = asyncio.Lock() 31 | self._service_dict = {} 32 | 33 | async def create_service(self, sid: str, websocket: WebSocketServerProtocol): 34 | short_sid = shorten_sid(sid) # shorten sid for display and log 35 | logger.info(f"A new request for service {short_sid} found, creating...") 36 | # initialize a service first to send control message when the previous connection is not closed 37 | # a new service created with the same sid just to send init or control messages will not affect the database. 38 | service = Service(sid, websocket) 39 | 40 | if sid in self._service_dict: 41 | prev_server = self._service_dict[sid] 42 | reason = f"Service {short_sid} is already running, we need to wait for the previous connection to close..." 43 | logger.warning(reason) 44 | # In the previous practice, if the previous connection was not closed, 45 | # the later connection was closed, which resulted in a anomalous behavior of the client. 46 | # So we need to send a control message to the client to tell it 47 | # to wait for the previous connection to close. 48 | service.send_message(MsgType.CONTROL, reason.encode('utf8')) 49 | await prev_server.wait_closed() # wait for the previous socket to close 50 | 51 | async with self._access_dict_lock: 52 | self._service_dict[sid] = service 53 | clean_task = asyncio.create_task(self.clean_service_when_close_connection(sid, websocket)) 54 | await service.start() # run forever! do not use asyncio.create_task 55 | await clean_task 56 | 57 | async def clean_service_when_close_connection(self, sid: str, websocket: WebSocketServerProtocol): 58 | await websocket.wait_closed() 59 | async with self._access_dict_lock: 60 | await asyncio.sleep(1) 61 | self._service_dict[sid].close_service() 62 | del self._service_dict[sid] 63 | logger.info(f"Clean service {shorten_sid(sid)} successfully.") 64 | -------------------------------------------------------------------------------- /global_config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | # FOR CLIENT 5 | class ClientConfig: 6 | SERVER_URI = "ws://localhost:8001" 7 | CONSOLE_LOG_LEVEL = logging.WARNING 8 | FILE_LOG_LEVEL = logging.INFO 9 | 10 | 11 | # FOR SERVER 12 | class ServerConfig: 13 | HOST = "" 14 | PORT = 8001 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cryptography~=41.0.7 2 | websockets~=10.2 3 | asyncclick~=8.0.3.2 4 | anyio~=3.7.0 5 | -------------------------------------------------------------------------------- /run_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: run_client.py 7 | @time: 2022/03/18 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import asyncclick as click 15 | 16 | import frontend.client.commands as client_commands 17 | 18 | 19 | @click.group() 20 | async def cli(): 21 | pass 22 | 23 | 24 | @cli.command() 25 | @click.option("--scheme", help='name of SSE scheme') 26 | @click.option("--save-path", help='save file path') 27 | async def generate_config(scheme, save_path): 28 | if scheme is None or save_path is None: 29 | click.echo(f'Incomplete options') 30 | return 31 | client_commands.generate_default_config(scheme, save_path) 32 | 33 | 34 | @cli.command() 35 | @click.option("--config", help='file path of config') 36 | @click.option("--sname", help='service name') 37 | async def create_service(config, sname): 38 | if config is None or sname is None: 39 | click.echo(f'Incomplete options') 40 | return 41 | 42 | client_commands.create_service(config_path=config, sname=sname) 43 | 44 | 45 | @cli.command() 46 | @click.option("--sid", help='service id', default='') 47 | @click.option("--sname", help='service name', default='') 48 | async def upload_config(sid, sname): 49 | if not sid and not sname: 50 | click.echo(f'One of the two options --sid or --sname must be assigned') 51 | return 52 | 53 | await client_commands.upload_config(sid=sid, sname=sname) 54 | 55 | 56 | @cli.command() 57 | @click.option("--sid", help='service id', default='') 58 | @click.option("--sname", help='service name', default='') 59 | async def generate_key(sid, sname): 60 | if not sid and not sname: 61 | click.echo(f'One of the two options --sid or --sname must be assigned') 62 | return 63 | 64 | client_commands.generate_key(sid=sid, sname=sname) 65 | 66 | 67 | @cli.command() 68 | @click.option("--sid", help='service id', default='') 69 | @click.option("--sname", help='service name', default='') 70 | @click.option("--db-path", help='database path') 71 | async def encrypt_database(sid, sname, db_path): 72 | if db_path is None: 73 | click.echo(f'Incomplete options: --db-path') 74 | return 75 | 76 | if not sid and not sname: 77 | click.echo(f'One of the two options --sid or --sname must be assigned') 78 | return 79 | 80 | client_commands.encrypt_database(db_path, sid=sid, sname=sname) 81 | 82 | 83 | @cli.command() 84 | @click.option("--sid", help='service id', default='') 85 | @click.option("--sname", help='service name', default='') 86 | async def upload_encrypted_database(sid, sname): 87 | if not sid and not sname: 88 | click.echo(f'One of the two options --sid or --sname must be assigned') 89 | return 90 | await client_commands.upload_encrypted_database(sid=sid, sname=sname) 91 | 92 | 93 | @cli.command() 94 | @click.option("--sid", help='service id', default='') 95 | @click.option("--sname", help='service name', default='') 96 | @click.option("--keyword", help='keyword to search') 97 | @click.option("--output-format", 98 | help='Specify the output format, which currently supports ' 99 | 'int, hex, raw and utf8, where utf8 format output must require that' 100 | ' the byte string of the file identifier must be converted from a utf8 string', 101 | default="raw") 102 | async def search(sid, sname, keyword, output_format): 103 | if keyword is None: 104 | click.echo(f'Incomplete options: --keyword') 105 | return 106 | if not sid and not sname: 107 | click.echo(f'One of the two options --sid or --sname must be assigned') 108 | return 109 | 110 | await client_commands.search(keyword, output_format, sid=sid, sname=sname) 111 | 112 | 113 | if __name__ == '__main__': 114 | cli(_anyio_backend="asyncio") 115 | -------------------------------------------------------------------------------- /run_server.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: run_server.py 7 | @time: 2022/03/18 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import asyncclick as click 14 | 15 | import frontend.server.connector 16 | from global_config import ServerConfig 17 | 18 | 19 | @click.group() 20 | async def cli(): 21 | pass 22 | 23 | 24 | @cli.command() 25 | @click.option("--host", default=ServerConfig.HOST, help='server host') 26 | @click.option("--port", default=ServerConfig.PORT, help='port to bind', type=int) 27 | async def start(host, port): 28 | if host is None or port is None: 29 | click.echo(f'Incomplete options') 30 | await frontend.server.connector.run_server(host, port) 31 | 32 | 33 | if __name__ == '__main__': 34 | cli(_anyio_backend="asyncio") -------------------------------------------------------------------------------- /schemes/ANSS16/Scheme3/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "Pi" 18 | _module_name = "ANSS16.Scheme3" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/ANSS16/Scheme3/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_HEADER = b"\x93\x94Asharov2014Scheme3" 21 | 22 | # If the param of the specified length is not suffixed, the default is in bytes 23 | # The length parameter of the bit suffix is the length in bits 24 | 25 | DEFAULT_CONFIG = { 26 | "scheme": "ANSS16.Scheme3", 27 | "param_lambda": 32, 28 | "param_k": 32, # key space K 29 | "param_k_prime": 32, # key space K' 30 | "param_l": 32, 31 | "param_l_prime": 32, 32 | # "param_t": -1, # need to scan, N = 2 ^ t, N is the total size of database 33 | "param_identifier_size": 4, 34 | 35 | "prf": "HmacPRF", 36 | "ske": "AES-CBC" 37 | } 38 | 39 | 40 | class PiConfig(SSEConfig): 41 | __slots__ = [ 42 | "param_lambda", 43 | "param_k", 44 | "param_k_prime", 45 | "param_l", 46 | "param_l_prime", 47 | "param_identifier_size", 48 | 49 | "prf", 50 | "ske" 51 | ] 52 | 53 | DEFAULT_CONFIG = DEFAULT_CONFIG 54 | 55 | def __init__(self, config_dict: dict): 56 | super(PiConfig, self).__init__(config_dict) 57 | self._parse_config(config_dict) 58 | 59 | def _parse_config(self, config_dict: dict): 60 | SSEConfig.check_param_exist(["param_lambda", 61 | "param_k", 62 | "param_k_prime", 63 | "param_l", 64 | "param_l_prime", 65 | "param_identifier_size", 66 | 67 | "prf", 68 | "ske"], 69 | config_dict) 70 | 71 | self.param_lambda = config_dict.get("param_lambda") 72 | self.param_k = config_dict.get("param_k") 73 | self.param_k_prime = config_dict.get("param_k_prime") 74 | self.param_l = config_dict.get("param_l") 75 | self.param_l_prime = config_dict.get("param_l_prime") 76 | # self.param_t = config_dict.get("param_t") 77 | 78 | self.param_identifier_size = config_dict.get("param_identifier_size") 79 | 80 | self.prf = toolkit.prf.get_prf_implementation(config_dict.get("prf", ""))( 81 | output_length=self.param_k + self.param_k_prime + self.param_l + self.param_l_prime) 82 | 83 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 84 | key_length=self.param_k 85 | ) 86 | -------------------------------------------------------------------------------- /schemes/ANSS16/Scheme3/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.ANSS16.Scheme3.config import PiConfig, PI_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | from toolkit.bytes_utils import split_bytes_given_slice_len 18 | 19 | 20 | class PiKey(SSEKey): 21 | __slots__ = ["K"] 22 | 23 | def __init__(self, K: bytes, config: PiConfig = None): 24 | super(PiKey, self).__init__(config) 25 | self.K = K 26 | 27 | def serialize(self) -> bytes: 28 | return self.K 29 | 30 | @classmethod 31 | def deserialize(cls, xbytes: bytes, config: PiConfig): 32 | if len(xbytes) != config.param_k: 33 | raise ValueError("The length of xbytes must be the same as the length of the parameter param_lambda.") 34 | 35 | return cls(xbytes) 36 | 37 | def __eq__(self, other): 38 | if not isinstance(other, PiKey): 39 | return False 40 | return self.K == other.K 41 | 42 | 43 | class PiEncryptedDatabase(SSEEncryptedDatabase): 44 | __slots__ = ["HT_S", "HT_L_list"] # dict D 45 | 46 | def __init__(self, HT_S: dict, HT_list: list, config: PiConfig = None): 47 | super(PiEncryptedDatabase, self).__init__(config) 48 | self.HT_S = HT_S 49 | self.HT_L_list = HT_list 50 | 51 | @classmethod 52 | def create_hash_table(cls, kv_pairs: list): 53 | kv_pairs.sort(key=lambda pair: pair[0]) 54 | D = {key: value for key, value in kv_pairs} 55 | return D 56 | 57 | def serialize(self) -> bytes: 58 | data = PI_HEADER + pickle.dumps((self.HT_S, self.HT_L_list)) 59 | return data 60 | 61 | @classmethod 62 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 63 | if xbytes[:len(PI_HEADER)] != PI_HEADER: 64 | raise ValueError("Parse header error.") 65 | 66 | data_bytes = xbytes[len(PI_HEADER):] 67 | HT_S, HT_list = pickle.loads(data_bytes) 68 | return cls(HT_S, HT_list, config) 69 | 70 | def __eq__(self, other): 71 | if not isinstance(other, PiEncryptedDatabase): 72 | return False 73 | return self.HT_S == other.HT_S and self.HT_L_list == other.HT_L_list 74 | 75 | 76 | class PiToken(SSEToken): 77 | __slots__ = ["li", "Ki", "li_prime", "Ki_prime"] # τ 78 | 79 | def __init__(self, li: bytes, Ki: bytes, li_prime: bytes, Ki_prime: bytes, config: PiConfig = None): 80 | super(PiToken, self).__init__(config) 81 | self.li = li 82 | self.Ki = Ki 83 | self.li_prime = li_prime 84 | self.Ki_prime = Ki_prime 85 | 86 | def serialize(self) -> bytes: 87 | return self.li + self.Ki + self.li_prime + self.Ki_prime 88 | 89 | @classmethod 90 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 91 | if len(xbytes) != config.param_k + config.param_k_prime + config.param_l + config.param_l_prime: 92 | raise ValueError("The length of xbytes must be matched with config.") 93 | 94 | li, Ki, li_prime, Ki_prime = split_bytes_given_slice_len(xbytes, [config.param_l, 95 | config.param_k, 96 | config.param_l_prime, 97 | config.param_k_prime]) 98 | 99 | return cls(li, Ki, li_prime, Ki_prime, config) 100 | 101 | def __eq__(self, other): 102 | if not isinstance(other, PiToken): 103 | return False 104 | return self.li == other.li \ 105 | and self.Ki == other.Ki \ 106 | and self.li_prime == other.li_prime \ 107 | and self.Ki_prime == other.Ki_prime 108 | 109 | 110 | class PiResult(SSEResult): 111 | __slots__ = ["result"] 112 | 113 | def __init__(self, result: list, config: PiConfig = None): 114 | super(PiResult, self).__init__(config) 115 | self.result = result 116 | 117 | def serialize(self) -> bytes: 118 | return pickle.dumps(self.result) 119 | 120 | @classmethod 121 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 122 | result = pickle.loads(xbytes) 123 | if not isinstance(result, list): 124 | return ValueError("The data contained in xbytes is not a list.") 125 | 126 | return cls(result, config) 127 | 128 | def __str__(self): 129 | return self.result.__str__() 130 | 131 | def __eq__(self, other): 132 | if not isinstance(other, PiResult): 133 | return False 134 | return self.result == other.result 135 | 136 | def get_result_list(self) -> list: 137 | return self.result 138 | -------------------------------------------------------------------------------- /schemes/ANSS16/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/13 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE1/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import schemes.interface.module_loader 15 | 16 | 17 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 18 | _sse_name = "SSE1" 19 | _module_name = "CGKO06.SSE1" 20 | 21 | 22 | # __init__.py in every SSE module must have sse_module_class_loader attribute 23 | sse_module_class_loader = ModuleClassLoader() 24 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE1/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/12 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import math 14 | 15 | import toolkit.config_manager 16 | import toolkit.prf 17 | import toolkit.prp 18 | import toolkit.symmetric_encryption 19 | from schemes.interface.config import SSEConfig 20 | 21 | SSE1_HEADER = b"\x93\x94Curtomola2006SSE1" 22 | 23 | # If the param of the specified length is not suffixed, the default is in bytes 24 | # The length parameter of the bit suffix is the length in bits 25 | 26 | DEFAULT_CONFIG = { 27 | "scheme": "CGKO06.SSE1", 28 | "param_k": 24, # key size (bytes) 29 | "param_l": 32, # max keyword size (bytes) 30 | "param_s": 2 ** 16, # size of array A 31 | "param_dictionary_size": 2 ** 16, # size of dictionary |∆| 32 | "param_identifier_size": 8, # fixed file identifier size (bytes) 33 | 34 | "prf_f": "HmacPRF", 35 | "prp_pi": "BitwiseFPEPRP", # todo 最好按照prp的格式识别使用哪个参数(bit version or byte version) 36 | "prp_psi": "BitwiseFPEPRP", 37 | "ske1": "AES-CBC", 38 | "ske2": "AES-CBC", 39 | } 40 | 41 | 42 | class SSE1Config(SSEConfig): 43 | __slots__ = [ 44 | "param_k", 45 | "param_l", 46 | "param_s", 47 | 48 | "param_k_bits", 49 | "param_l_bits", 50 | "param_log2_s", 51 | "param_log2_s_bytes", 52 | 53 | "param_dictionary_size", 54 | "param_identifier_size", 55 | 56 | "prp_pi", 57 | "prp_psi", 58 | "prf_f", 59 | "ske1", 60 | "ske2" 61 | ] 62 | 63 | DEFAULT_CONFIG = DEFAULT_CONFIG 64 | 65 | def __init__(self, config_dict: dict): 66 | super(SSE1Config, self).__init__(config_dict) 67 | self._parse_config(config_dict) 68 | 69 | def _parse_config(self, config_dict: dict): 70 | SSEConfig.check_param_exist(["param_k", "param_l", "param_s", 71 | "param_dictionary_size", "param_identifier_size", 72 | "prp_pi", "prp_psi", "prf_f", "ske1", "ske2"], 73 | config_dict) 74 | 75 | self.param_k = config_dict.get("param_k") 76 | self.param_l = config_dict.get("param_l") 77 | self.param_s = config_dict.get("param_s") 78 | 79 | self.param_identifier_size = config_dict.get("param_identifier_size") 80 | self.param_dictionary_size = config_dict.get("param_dictionary_size") 81 | 82 | self.param_k_bits = self.param_k * 8 83 | self.param_l_bits = self.param_l * 8 84 | self.param_log2_s = math.ceil(math.log2(self.param_s)) 85 | self.param_log2_s_bytes = math.ceil(self.param_log2_s / 8) 86 | 87 | self.prp_pi = toolkit.prp.get_prp_implementation(config_dict.get("prp_pi", ""))( 88 | key_bit_length=self.param_k_bits, 89 | message_bit_length=self.param_l_bits 90 | ) 91 | self.prp_psi = toolkit.prp.get_prp_implementation(config_dict.get("prp_psi", ""))( 92 | key_bit_length=self.param_k_bits, 93 | message_bit_length=self.param_log2_s 94 | ) 95 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 96 | key_length=self.param_k, 97 | message_length=self.param_l, 98 | output_length=self.param_k + self.param_log2_s_bytes) 99 | self.ske1 = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske1", ""))( 100 | key_length=self.param_k 101 | ) 102 | self.ske2 = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske2", ""))( 103 | key_length=self.param_k 104 | ) 105 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE1/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CGKO06.SSE1.config import SSE1Config, SSE1_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class SSE1Key(SSEKey): 20 | __slots__ = ["K1", "K2", "K3", "K4"] 21 | 22 | def __init__(self, K1: bytes, K2: bytes, K3: bytes, K4: bytes, config: SSE1Config = None): 23 | super(SSE1Key, self).__init__(config) 24 | self.K1, self.K2, self.K3, self.K4 = K1, K2, K3, K4 25 | 26 | def serialize(self) -> bytes: 27 | return b''.join([self.K1, self.K2, self.K3, self.K4]) 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: SSE1Config): 31 | if len(xbytes) != 4 * config.param_k: 32 | raise ValueError("The length of xbytes must be four times the length of the parameter param_k.") 33 | 34 | key_list = [xbytes[i: i + config.param_k] for i in range(0, len(xbytes), config.param_k)] 35 | return cls(*key_list) 36 | 37 | def __eq__(self, other): 38 | if not isinstance(other, SSE1Key): 39 | return False 40 | return self.K1 == other.K1 and self.K2 == other.K2 and self.K3 == other.K3 and self.K4 == other.K4 41 | 42 | 43 | class SSE1EncryptedDatabase(SSEEncryptedDatabase): 44 | __slots__ = ["A", "T"] # array A and look-up table T 45 | 46 | def __init__(self, A: list, T: dict, config: SSE1Config = None): 47 | super(SSE1EncryptedDatabase, self).__init__(config) 48 | self.A, self.T = A, T 49 | 50 | def serialize(self) -> bytes: 51 | data = SSE1_HEADER + pickle.dumps((self.A, self.T)) 52 | return data 53 | 54 | @classmethod 55 | def deserialize(cls, xbytes: bytes, config: SSE1Config = None): 56 | if xbytes[:len(SSE1_HEADER)] != SSE1_HEADER: 57 | raise ValueError("Parse header error.") 58 | 59 | data_bytes = xbytes[len(SSE1_HEADER):] 60 | A, T = pickle.loads(data_bytes) 61 | return cls(A, T) 62 | 63 | def __eq__(self, other): 64 | if not isinstance(other, SSE1EncryptedDatabase): 65 | return False 66 | return self.A == other.A and self.T == other.T 67 | 68 | 69 | class SSE1Token(SSEToken): 70 | __slots__ = ["gamma", "eta"] # array A and look-up table T 71 | 72 | def __init__(self, gamma: bytes, eta: bytes, config: SSE1Config = None): 73 | super(SSE1Token, self).__init__(config) 74 | self.gamma, self.eta = gamma, eta 75 | 76 | def serialize(self) -> bytes: 77 | return self.gamma + self.eta 78 | 79 | @classmethod 80 | def deserialize(cls, xbytes: bytes, config: SSE1Config = None): 81 | if len(xbytes) != config.param_l + config.param_k + config.param_log2_s_bytes: 82 | raise ValueError("The length of xbytes is wrong.") 83 | 84 | gamma, eta = xbytes[:config.param_l], xbytes[config.param_l:] 85 | return cls(gamma, eta) 86 | 87 | def __eq__(self, other): 88 | if not isinstance(other, SSE1Token): 89 | return False 90 | return self.gamma == other.gamma and self.eta == other.eta 91 | 92 | 93 | class SSE1Result(SSEResult): 94 | __slots__ = ["result"] # array A and look-up table T 95 | 96 | def __init__(self, result: list, config: SSE1Config = None): 97 | super(SSE1Result, self).__init__(config) 98 | 99 | self.result = result 100 | 101 | def serialize(self) -> bytes: 102 | return pickle.dumps(self.result) 103 | 104 | @classmethod 105 | def deserialize(cls, xbytes: bytes, config: SSE1Config = None): 106 | result = pickle.loads(xbytes) 107 | if not isinstance(result, list): 108 | return ValueError("The data contained in xbytes is not a list.") 109 | 110 | return cls(result) 111 | 112 | def __str__(self): 113 | return self.result.__str__() 114 | 115 | def __eq__(self, other): 116 | if not isinstance(other, SSE1Result): 117 | return False 118 | return self.result == other.result 119 | 120 | def get_result_list(self) -> list: 121 | return self.result 122 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE2/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import schemes.interface.module_loader 15 | 16 | 17 | class PiBasModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 18 | _sse_name = "SSE2" 19 | _module_name = "CGKO06.SSE2" 20 | 21 | 22 | # __init__.py in every SSE module must have sse_module_class_loader attribute 23 | sse_module_class_loader = PiBasModuleClassLoader() 24 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE2/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "param_k": 24, 3 | "param_l": 32, 4 | "param_n": -1, 5 | "param_max": -1, 6 | "param_dictionary_size": 65536, 7 | "param_identifier_size": 8, 8 | 9 | "prf_f": "HmacPRF", 10 | "prp_pi": "HmacLubyRackoffPRP", 11 | "prp_psi": "HmacLubyRackoffPRP", 12 | "ske": "AES-CBC" 13 | } -------------------------------------------------------------------------------- /schemes/CGKO06/SSE2/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import math 14 | 15 | import toolkit.config_manager 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | from toolkit.database_utils import get_distinct_file_count 20 | 21 | 22 | def determine_param_n(db: dict) -> int: 23 | return get_distinct_file_count(db) 24 | 25 | 26 | def determine_param_max(max_document_size: int): 27 | """ 28 | Determine the parameter max in SSE-2, by the max document size 29 | The essence is to find out how many keywords at most can make up that document of max size. 30 | Start with single-byte keywords and keep adding up the number of keywords 31 | :param max_document_size (bytes) 32 | """ 33 | result = 0 34 | curr_keyword_size = 1 35 | curr_document_size = 0 36 | 37 | while True: 38 | if curr_document_size + 2 ** (curr_keyword_size * 8) * curr_keyword_size > max_document_size: 39 | result += (max_document_size - curr_document_size) // curr_keyword_size 40 | break 41 | result += 2 ** (curr_keyword_size * 8) 42 | curr_document_size += 2 ** (curr_keyword_size * 8) * curr_keyword_size 43 | curr_keyword_size += 1 44 | 45 | return result 46 | 47 | 48 | SSE2_HEADER = b"\x93\x94Curtomola2006SSE2" 49 | 50 | # If the param of the specified length is not suffixed, the default is in bytes 51 | # The length parameter of the bit suffix is the length in bits 52 | 53 | DEFAULT_CONFIG = { 54 | "scheme": "CGKO06.SSE2", 55 | "param_k": 24, # key size (bytes) 56 | "param_l": 32, # max keyword size (bytes) 57 | "param_n": -1, # number of files 58 | "param_max": -1, # parameter max, need to be determined at first # todo need document scan 59 | "param_dictionary_size": 65536, 60 | "param_identifier_size": 8, 61 | "param_max_file_size": 1024 * 1024, 62 | 63 | "prp_pi": "BitwiseFPEPRP", 64 | "ske": "AES-CBC" 65 | } 66 | 67 | 68 | class SSE2Config(SSEConfig): 69 | __slots__ = [ 70 | "param_k", 71 | "param_l", 72 | "param_n", 73 | "param_max", 74 | "param_s", 75 | "param_k_bits", 76 | "param_l_bits", 77 | "param_log2_n", 78 | "param_log2_n_bytes", 79 | "param_log2_n_plus_max", 80 | "param_log2_n_plus_max_bytes", 81 | "param_dictionary_size", 82 | "param_identifier_size", 83 | "param_max_file_size", # todo need to scan 84 | "prp_pi", 85 | "ske" 86 | ] 87 | 88 | DEFAULT_CONFIG = DEFAULT_CONFIG 89 | 90 | def __init__(self, config_dict: dict): 91 | super(SSE2Config, self).__init__(config_dict) 92 | self._parse_config(config_dict) 93 | 94 | def _parse_config(self, config_dict: dict): 95 | SSEConfig.check_param_exist(["param_k", 96 | "param_l", 97 | "param_n", 98 | "param_max_file_size"], 99 | config_dict) 100 | 101 | self.param_k = config_dict.get("param_k") 102 | self.param_l = config_dict.get("param_l") 103 | self.param_n = config_dict.get("param_n") 104 | 105 | self.param_k_bits = self.param_k * 8 106 | self.param_l_bits = self.param_l * 8 107 | 108 | self.param_max = determine_param_max(config_dict.get("param_max_file_size")) 109 | 110 | self.param_log2_n = math.ceil(math.log2(self.param_n)) 111 | self.param_log2_n_bytes = math.ceil(self.param_log2_n / 8) 112 | 113 | self.param_log2_n_plus_max = math.ceil(math.log2(self.param_n + self.param_max)) 114 | self.param_log2_n_plus_max_bytes = math.ceil(self.param_log2_n_plus_max / 8) 115 | 116 | self.param_s = self.param_max * self.param_n 117 | 118 | self.prp_pi = toolkit.prp.get_prp_implementation(config_dict.get("prp_pi", ""))( 119 | key_bit_length=self.param_k_bits, 120 | message_bit_length=self.param_l_bits + self.param_log2_n_plus_max 121 | ) 122 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 123 | key_length=self.param_k 124 | ) 125 | 126 | 127 | def scan_database_and_update_config_dict(config_dict: dict, database: dict): 128 | config_dict["param_n"] = determine_param_n(database) 129 | 130 | 131 | if __name__ == '__main__': 132 | determine_param_max(1000 * 1000) 133 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE2/construction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: construction.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | @note: Here PRP is bitwise PRP 13 | """ 14 | import os 15 | 16 | import schemes.interface.inverted_index_sse 17 | from schemes.CGKO06.SSE2.config import DEFAULT_CONFIG, SSE2Config 18 | from schemes.CGKO06.SSE2.structures import SSE2Key, SSE2Token, SSE2EncryptedDatabase, SSE2Result 19 | from toolkit.bits import Bitset 20 | from toolkit.bytes_utils import int_to_bytes 21 | 22 | 23 | class SSE2(schemes.interface.inverted_index_sse.InvertedIndexSSE): 24 | """SSE-2 Construction described by Curtomola et al. [CGKO06]""" 25 | 26 | def __init__(self, config: dict = DEFAULT_CONFIG): 27 | super(SSE2, self).__init__() 28 | self.config = SSE2Config(config) 29 | pass 30 | 31 | def _Gen(self) -> SSE2Key: 32 | """ 33 | Generate Key 34 | K2 is not used here now. 35 | """ 36 | key_tuple = tuple(os.urandom(self.config.param_k) for _ in range(2)) 37 | return SSE2Key(*key_tuple) 38 | 39 | def _Enc(self, K: SSE2Key, database: dict) -> SSE2EncryptedDatabase: 40 | """Encrypted the given database under the key""" 41 | K1, K2 = K.K1, K.K2 42 | I = {} 43 | 44 | s_prime = 0 # total_size 45 | document_count_dict = {} # the number of entries in I that already contain id(Di) 46 | 47 | for keyword in database: 48 | s_prime += len(database[keyword]) 49 | for j, identifier in enumerate(database[keyword], start=1): 50 | I[int( 51 | self.config.prp_pi(Bitset(K1, 52 | length=self.config.param_k_bits), 53 | Bitset(keyword, length=self.config.param_l_bits) + 54 | Bitset(int_to_bytes(j, self.config.param_log2_n_plus_max_bytes), 55 | length=self.config.param_log2_n_plus_max), 56 | ) 57 | )] = identifier 58 | 59 | document_count_dict[identifier] = document_count_dict.get(identifier, 0) + 1 60 | 61 | n = self.config.param_n 62 | 63 | if s_prime < self.config.param_s: 64 | for identifier in document_count_dict: 65 | for l in range(document_count_dict[identifier] - self.config.param_max): 66 | I[int( 67 | self.config.prp_pi(Bitset(K1, 68 | length=self.config.param_k_bits), 69 | Bitset(b"\x00" * self.config.param_l, 70 | length=self.config.param_l_bits) + 71 | Bitset(int_to_bytes(n + l, self.config.param_log2_n_plus_max_bytes), 72 | length=self.config.param_log2_n_plus_max) 73 | ))] = identifier 74 | n += document_count_dict[identifier] - self.config.param_max 75 | return SSE2EncryptedDatabase(I) 76 | 77 | def _Trap(self, K: SSE2Key, keyword: bytes) -> SSE2Token: 78 | """Trapdoor Generation Algorithm""" 79 | K1 = K.K1 80 | t = [] 81 | for i in range(1, self.config.param_n + 1): 82 | t.append(int(self.config.prp_pi(Bitset(K1, 83 | length=self.config.param_k_bits), 84 | Bitset(keyword, length=self.config.param_l_bits) + 85 | Bitset(int_to_bytes(i, self.config.param_log2_n_plus_max_bytes), 86 | length=self.config.param_log2_n_plus_max)) 87 | )) 88 | return SSE2Token(t) 89 | 90 | def _Search(self, edb: SSE2EncryptedDatabase, tk: SSE2Token) -> SSE2Result: 91 | """Search Algorithm""" 92 | I = edb.I 93 | t_list = tk.t 94 | result = [] 95 | 96 | for ti in t_list: 97 | identifier = I.get(ti) 98 | if identifier is None: 99 | break 100 | result.append(identifier) 101 | 102 | return SSE2Result(result) 103 | 104 | def KeyGen(self) -> SSE2Key: 105 | key = self._Gen() 106 | return key 107 | 108 | def EDBSetup(self, 109 | key: SSE2Key, 110 | database: dict 111 | ) -> SSE2EncryptedDatabase: 112 | return self._Enc(key, database) 113 | 114 | def TokenGen(self, key: SSE2Key, keyword: bytes) -> SSE2Token: 115 | return self._Trap(key, keyword) 116 | 117 | def Search(self, 118 | edb: SSE2EncryptedDatabase, 119 | token: SSE2Token) -> SSE2Result: 120 | return self._Search(edb, token) 121 | -------------------------------------------------------------------------------- /schemes/CGKO06/SSE2/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CGKO06.SSE2.config import SSE2_HEADER, SSE2Config 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class SSE2Key(SSEKey): 20 | __slots__ = ["K1", "K2"] 21 | 22 | def __init__(self, K1: bytes, K2: bytes, config: SSE2Config = None): 23 | super(SSE2Key, self).__init__(config) 24 | self.K1, self.K2 = K1, K2 25 | 26 | def serialize(self) -> bytes: 27 | return b''.join([self.K1, self.K2]) 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: SSE2Config): 31 | if len(xbytes) != 2 * config.param_k: 32 | raise ValueError("The length of xbytes must be 2 times the length of the parameter param_k.") 33 | 34 | key_list = [xbytes[i: i + config.param_k] for i in range(0, len(xbytes), config.param_k)] 35 | return cls(*key_list) 36 | 37 | def __eq__(self, other): 38 | if not isinstance(other, SSE2Key): 39 | return False 40 | return self.K1 == other.K1 and self.K2 == other.K2 41 | 42 | 43 | class SSE2EncryptedDatabase(SSEEncryptedDatabase): 44 | __slots__ = ["I"] # dict I 45 | 46 | def __init__(self, I: dict, config: SSE2Config = None): 47 | super(SSE2EncryptedDatabase, self).__init__(config) 48 | self.I = I 49 | 50 | def serialize(self) -> bytes: 51 | data = SSE2_HEADER + pickle.dumps(self.I) 52 | return data 53 | 54 | @classmethod 55 | def deserialize(cls, xbytes: bytes, config: SSE2Config = None): 56 | if xbytes[:len(SSE2_HEADER)] != SSE2_HEADER: 57 | raise ValueError("Parse header error.") 58 | 59 | data_bytes = xbytes[len(SSE2_HEADER):] 60 | I = pickle.loads(data_bytes) 61 | return cls(I) 62 | 63 | def __eq__(self, other): 64 | if not isinstance(other, SSE2EncryptedDatabase): 65 | return False 66 | return self.I == other.I 67 | 68 | 69 | class SSE2Token(SSEToken): 70 | __slots__ = ["t"] # array t 71 | 72 | def __init__(self, t: list, config: SSE2Config = None): 73 | super(SSE2Token, self).__init__(config) 74 | self.t = t 75 | 76 | def serialize(self) -> bytes: 77 | return pickle.dumps(self.t) 78 | 79 | @classmethod 80 | def deserialize(cls, xbytes: bytes, config: SSE2Config = None): 81 | t = pickle.loads(xbytes) 82 | if not isinstance(t, list): 83 | return ValueError("The data contained in xbytes is not a list.") 84 | return cls(t, config) 85 | 86 | def __eq__(self, other): 87 | if not isinstance(other, SSE2Token): 88 | return False 89 | return self.t == other.t 90 | 91 | 92 | class SSE2Result(SSEResult): 93 | __slots__ = ["result"] 94 | 95 | def __init__(self, result: list, config: SSE2Config = None): 96 | super(SSE2Result, self).__init__(config) 97 | self.result = result 98 | 99 | def serialize(self) -> bytes: 100 | return pickle.dumps(self.result) 101 | 102 | @classmethod 103 | def deserialize(cls, xbytes: bytes, config: SSE2Config = None): 104 | result = pickle.loads(xbytes) 105 | if not isinstance(result, list): 106 | return ValueError("The data contained in xbytes is not a list.") 107 | 108 | return cls(result, config) 109 | 110 | def __str__(self): 111 | return self.result.__str__() 112 | 113 | def __eq__(self, other): 114 | if not isinstance(other, SSE2Result): 115 | return False 116 | return self.result == other.result 117 | 118 | def get_result_list(self) -> list: 119 | return self.result 120 | -------------------------------------------------------------------------------- /schemes/CGKO06/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/CJJ14/Pi2Lev/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "Pi2Lev" 18 | _module_name = "CJJ14.Pi2Lev" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/CJJ14/Pi2Lev/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_2LEV_HEADER = b"\x93\x94Cash2014Pi2Lev" 21 | 22 | LEVEL_FILE_IDENTIFIER = b"\x00" # current level is storing file identifiers 23 | LEVEL_POINTER_OF_ARRAY = b"\x01" # current level is storing pointers of array A 24 | 25 | # If the param of the specified length is not suffixed, the default is in bytes 26 | # The length parameter of the bit suffix is the length in bits 27 | 28 | DEFAULT_CONFIG = { 29 | "scheme": "CJJ14.Pi2Lev", 30 | "param_lambda": 32, # key size (bytes) 31 | "param_B": 64, # store identifiers in the array (in the medium and large cases), it packs up to B of them together 32 | "param_b": 64, # store identifiers in the dictionary (in the small case), it packs up to b of them together 33 | "param_B_prime": 64, # storing pointers 34 | "param_b_prime": 64, # storing pointers 35 | "param_identifier_size": 8, 36 | "prf_f_output_length": 32, 37 | 38 | "prf_f": "HmacPRF", 39 | "ske": "AES-CBC" 40 | } 41 | 42 | 43 | class Pi2LevConfig(SSEConfig): 44 | __slots__ = [ 45 | "param_lambda", 46 | "param_B", 47 | "param_B_prime", 48 | "param_b", 49 | "param_b_prime", 50 | "prf_f_output_length", 51 | "param_identifier_size", 52 | "param_index_size_of_A", 53 | "prf_f", 54 | "ske" 55 | ] 56 | 57 | DEFAULT_CONFIG = DEFAULT_CONFIG 58 | 59 | def __init__(self, config_dict: dict): 60 | super(Pi2LevConfig, self).__init__(config_dict) 61 | self._parse_config(config_dict) 62 | 63 | def _parse_config(self, config_dict: dict): 64 | SSEConfig.check_param_exist(["param_lambda", 65 | "param_B", 66 | "param_b", 67 | "param_B_prime", 68 | "param_b_prime", 69 | "prf_f_output_length", 70 | "param_identifier_size", 71 | "prf_f", 72 | "ske"], 73 | config_dict) 74 | 75 | self.param_lambda = config_dict.get("param_lambda") 76 | 77 | self.param_B = config_dict.get("param_B") 78 | self.param_b = config_dict.get("param_b") 79 | 80 | self.param_B_prime = config_dict.get("param_B_prime") 81 | self.param_b_prime = config_dict.get("param_b_prime") 82 | 83 | self.prf_f_output_length = config_dict.get("prf_f_output_length") 84 | self.param_identifier_size = config_dict.get("param_identifier_size") 85 | 86 | self.param_index_size_of_A = (self.param_B * self.param_identifier_size) // self.param_B_prime 87 | if (self.param_b * self.param_identifier_size) // self.param_b_prime != self.param_index_size_of_A: 88 | raise ValueError("guarantee (param_B * param_identifier_size) // param_B_prime == " 89 | "(param_b * param_identifier_size) // param_b_prime") 90 | 91 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 92 | key_length=self.param_lambda, 93 | output_length=self.prf_f_output_length) 94 | 95 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 96 | key_length=self.param_lambda 97 | ) 98 | -------------------------------------------------------------------------------- /schemes/CJJ14/Pi2Lev/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CJJ14.Pi2Lev.config import Pi2LevConfig, PI_2LEV_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class Pi2LevKey(SSEKey): 20 | __slots__ = ["K"] 21 | 22 | def __init__(self, K: bytes, config: Pi2LevConfig = None): 23 | super(Pi2LevKey, self).__init__(config) 24 | self.K = K 25 | 26 | def serialize(self) -> bytes: 27 | return self.K 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: Pi2LevConfig): 31 | if len(xbytes) != config.param_lambda: 32 | raise ValueError( 33 | "The length of xbytes must be the same as the length of the parameter param_k." 34 | ) 35 | 36 | return cls(xbytes) 37 | 38 | def __eq__(self, other): 39 | if not isinstance(other, Pi2LevKey): 40 | return False 41 | return self.K == other.K 42 | 43 | 44 | class Pi2LevEncryptedDatabase(SSEEncryptedDatabase): 45 | __slots__ = ["D", "A"] # dict D, array A 46 | 47 | def __init__(self, D: dict, A: list, config: Pi2LevConfig = None): 48 | super(Pi2LevEncryptedDatabase, self).__init__(config) 49 | self.D = D 50 | self.A = A 51 | 52 | @staticmethod 53 | def create_dictionary_from_list(kv_pairs: list) -> dict: 54 | kv_pairs.sort(key=lambda pair: pair[0]) 55 | D = {key: value for key, value in kv_pairs} 56 | return D 57 | 58 | def serialize(self) -> bytes: 59 | data = PI_2LEV_HEADER + pickle.dumps((self.D, self.A)) 60 | return data 61 | 62 | @classmethod 63 | def deserialize(cls, xbytes: bytes, config: Pi2LevConfig = None): 64 | if xbytes[:len(PI_2LEV_HEADER)] != PI_2LEV_HEADER: 65 | raise ValueError("Parse header error.") 66 | 67 | data_bytes = xbytes[len(PI_2LEV_HEADER):] 68 | D, A = pickle.loads(data_bytes) 69 | return cls(D, A) 70 | 71 | def __eq__(self, other): 72 | if not isinstance(other, Pi2LevEncryptedDatabase): 73 | return False 74 | return self.D == other.D and self.A == other.A 75 | 76 | 77 | class Pi2LevToken(SSEToken): 78 | __slots__ = ["K1", "K2"] # K1, K2 79 | 80 | def __init__(self, K1: bytes, K2: bytes, config: Pi2LevConfig = None): 81 | super(Pi2LevToken, self).__init__(config) 82 | self.K1 = K1 83 | self.K2 = K2 84 | 85 | def serialize(self) -> bytes: 86 | return self.K1 + self.K2 87 | 88 | @classmethod 89 | def deserialize(cls, xbytes: bytes, config: Pi2LevConfig = None): 90 | if len(xbytes) != 2 * config.param_lambda: 91 | raise ValueError( 92 | "The length of xbytes must be 2 times the length of the parameter param_k." 93 | ) 94 | 95 | K1, K2 = xbytes[:config.param_lambda], xbytes[config.param_lambda:] 96 | 97 | return cls(K1, K2, config) 98 | 99 | def __eq__(self, other): 100 | if not isinstance(other, Pi2LevToken): 101 | return False 102 | return self.K1 == other.K1 and self.K2 == other.K2 103 | 104 | 105 | class Pi2LevResult(SSEResult): 106 | __slots__ = ["result"] 107 | 108 | def __init__(self, result: list, config: Pi2LevConfig = None): 109 | super(Pi2LevResult, self).__init__(config) 110 | self.result = result 111 | 112 | def serialize(self) -> bytes: 113 | return pickle.dumps(self.result) 114 | 115 | @classmethod 116 | def deserialize(cls, xbytes: bytes, config: Pi2LevConfig = None): 117 | result = pickle.loads(xbytes) 118 | if not isinstance(result, list): 119 | return ValueError("The data contained in xbytes is not a list.") 120 | 121 | return cls(result, config) 122 | 123 | def __str__(self): 124 | return self.result.__str__() 125 | 126 | def __eq__(self, other): 127 | if not isinstance(other, Pi2LevResult): 128 | return False 129 | return self.result == other.result 130 | 131 | def get_result_list(self) -> list: 132 | return self.result 133 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiBas/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import schemes.interface.module_loader 15 | 16 | 17 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 18 | _sse_name = "PiBas" 19 | _module_name = "CJJ14.PiBas" 20 | 21 | 22 | # __init__.py in every SSE module must have sse_module_class_loader attribute 23 | sse_module_class_loader = ModuleClassLoader() 24 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiBas/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_BAS_HEADER = b"\x93\x94Cash2014PiBas" 21 | 22 | # If the param of the specified length is not suffixed, the default is in bytes 23 | # The length parameter of the bit suffix is the length in bits 24 | 25 | DEFAULT_CONFIG = { 26 | "scheme": "CJJ14.PiBas", 27 | "param_lambda": 32, # key size (bytes) 28 | "prf_f_output_length": 32, 29 | 30 | "prf_f": "HmacPRF", 31 | "ske": "AES-CBC" 32 | } 33 | 34 | 35 | class PiBasConfig(SSEConfig): 36 | __slots__ = [ 37 | "param_lambda", 38 | "prf_f_output_length", 39 | 40 | "prf_f", 41 | "ske" 42 | ] 43 | 44 | DEFAULT_CONFIG = DEFAULT_CONFIG 45 | 46 | def __init__(self, config_dict: dict): 47 | super(PiBasConfig, self).__init__(config_dict) 48 | self._parse_config(config_dict) 49 | 50 | def _parse_config(self, config_dict: dict): 51 | SSEConfig.check_param_exist(["param_lambda", 52 | "prf_f_output_length", 53 | "prf_f", 54 | "ske"], 55 | config_dict) 56 | 57 | self.param_lambda = config_dict.get("param_lambda") 58 | self.prf_f_output_length = config_dict.get("prf_f_output_length") 59 | 60 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 61 | key_length=self.param_lambda, 62 | output_length=self.prf_f_output_length) 63 | 64 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 65 | key_length=self.param_lambda 66 | ) 67 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiBas/construction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: construction.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: ΠBas Construction described by Cash et al. [CJJ+14] 12 | """ 13 | import os 14 | 15 | import schemes.interface.inverted_index_sse 16 | from schemes.CJJ14.PiBas.config import DEFAULT_CONFIG, PiBasConfig 17 | from schemes.CJJ14.PiBas.structures import PiBasKey, PiBasToken, PiBasEncryptedDatabase, PiBasResult 18 | from toolkit.bytes_utils import int_to_bytes 19 | 20 | 21 | class PiBas(schemes.interface.inverted_index_sse.InvertedIndexSSE): 22 | """PiBas Construction described by Cash et al. [CJJ+14]""" 23 | 24 | def __init__(self, config: dict = DEFAULT_CONFIG): 25 | super(PiBas, self).__init__() 26 | self.config = PiBasConfig(config) 27 | pass 28 | 29 | def _Gen(self) -> PiBasKey: 30 | """ 31 | Generate Key 32 | K2 is not used here now. 33 | """ 34 | K = os.urandom(self.config.param_lambda) 35 | return PiBasKey(K) 36 | 37 | def _Enc(self, K: PiBasKey, database: dict) -> PiBasEncryptedDatabase: 38 | """Encrypted the given database under the key""" 39 | K = K.K 40 | L = [] 41 | 42 | for keyword in database: 43 | K1 = self.config.prf_f(K, b'\x01' + keyword) 44 | K2 = self.config.prf_f(K, b'\x02' + keyword) 45 | for c, identifier in enumerate(database[keyword]): 46 | l = self.config.prf_f(K1, int_to_bytes(c)) 47 | d = self.config.ske.Encrypt(K2, identifier) 48 | L.append((l, d)) 49 | return PiBasEncryptedDatabase.build_from_list(L) 50 | 51 | def _Trap(self, K: PiBasKey, keyword: bytes) -> PiBasToken: 52 | """Trapdoor Generation Algorithm""" 53 | K = K.K 54 | K1 = self.config.prf_f(K, b'\x01' + keyword) 55 | K2 = self.config.prf_f(K, b'\x02' + keyword) 56 | return PiBasToken(K1, K2) 57 | 58 | def _Search(self, edb: PiBasEncryptedDatabase, tk: PiBasToken) -> PiBasResult: 59 | """Search Algorithm""" 60 | D = edb.D 61 | K1, K2 = tk.K1, tk.K2 62 | result = [] 63 | c = 0 64 | while True: 65 | addr = self.config.prf_f(K1, int_to_bytes(c)) 66 | cipher = D.get(addr) 67 | if cipher is None: 68 | break 69 | result.append(self.config.ske.Decrypt(K2, cipher)) 70 | c += 1 71 | 72 | return PiBasResult(result) 73 | 74 | def KeyGen(self) -> PiBasKey: 75 | key = self._Gen() 76 | return key 77 | 78 | def EDBSetup(self, 79 | key: PiBasKey, 80 | database: dict 81 | ) -> PiBasEncryptedDatabase: 82 | return self._Enc(key, database) 83 | 84 | def TokenGen(self, key: PiBasKey, keyword: bytes) -> PiBasToken: 85 | return self._Trap(key, keyword) 86 | 87 | def Search(self, 88 | edb: PiBasEncryptedDatabase, 89 | token: PiBasToken) -> PiBasResult: 90 | return self._Search(edb, token) 91 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiBas/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CJJ14.PiBas.config import PiBasConfig, PI_BAS_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class PiBasKey(SSEKey): 20 | __slots__ = ["K"] 21 | 22 | def __init__(self, K: bytes, config: PiBasConfig = None): 23 | super(PiBasKey, self).__init__(config) 24 | self.K = K 25 | 26 | def serialize(self) -> bytes: 27 | return self.K 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: PiBasConfig): 31 | if len(xbytes) != config.param_lambda: 32 | raise ValueError("The length of xbytes must be the same as the length of the parameter param_lambda.") 33 | 34 | return cls(xbytes) 35 | 36 | def __eq__(self, other): 37 | if not isinstance(other, PiBasKey): 38 | return False 39 | return self.K == other.K 40 | 41 | 42 | class PiBasEncryptedDatabase(SSEEncryptedDatabase): 43 | __slots__ = ["D"] # dict D 44 | 45 | def __init__(self, D: dict, config: PiBasConfig = None): 46 | super(PiBasEncryptedDatabase, self).__init__(config) 47 | self.D = D 48 | 49 | @classmethod 50 | def build_from_list(cls, kv_pairs: list, config: PiBasConfig = None): 51 | kv_pairs.sort(key=lambda pair: pair[0]) 52 | D = {key: value for key, value in kv_pairs} 53 | return cls(D, config) 54 | 55 | def serialize(self) -> bytes: 56 | data = PI_BAS_HEADER + pickle.dumps(self.D) 57 | return data 58 | 59 | @classmethod 60 | def deserialize(cls, xbytes: bytes, config: PiBasConfig = None): 61 | if xbytes[:len(PI_BAS_HEADER)] != PI_BAS_HEADER: 62 | raise ValueError("Parse header error.") 63 | 64 | data_bytes = xbytes[len(PI_BAS_HEADER):] 65 | D = pickle.loads(data_bytes) 66 | return cls(D) 67 | 68 | def __eq__(self, other): 69 | if not isinstance(other, PiBasEncryptedDatabase): 70 | return False 71 | return self.D == other.D 72 | 73 | 74 | class PiBasToken(SSEToken): 75 | __slots__ = ["K1", "K2"] # K1, K2 76 | 77 | def __init__(self, K1: bytes, K2: bytes, config: PiBasConfig = None): 78 | super(PiBasToken, self).__init__(config) 79 | self.K1 = K1 80 | self.K2 = K2 81 | 82 | def serialize(self) -> bytes: 83 | return self.K1 + self.K2 84 | 85 | @classmethod 86 | def deserialize(cls, xbytes: bytes, config: PiBasConfig = None): 87 | if len(xbytes) != 2 * config.param_lambda: 88 | raise ValueError("The length of xbytes must be 2 times the length of the parameter param_lambda.") 89 | 90 | K1, K2 = xbytes[:config.param_lambda], xbytes[config.param_lambda:] 91 | 92 | return cls(K1, K2, config) 93 | 94 | def __eq__(self, other): 95 | if not isinstance(other, PiBasToken): 96 | return False 97 | return self.K1 == other.K1 and self.K2 == other.K2 98 | 99 | 100 | class PiBasResult(SSEResult): 101 | __slots__ = ["result"] 102 | 103 | def __init__(self, result: list, config: PiBasConfig = None): 104 | super(PiBasResult, self).__init__(config) 105 | self.result = result 106 | 107 | def serialize(self) -> bytes: 108 | return pickle.dumps(self.result) 109 | 110 | @classmethod 111 | def deserialize(cls, xbytes: bytes, config: PiBasConfig = None): 112 | result = pickle.loads(xbytes) 113 | if not isinstance(result, list): 114 | return ValueError("The data contained in xbytes is not a list.") 115 | 116 | return cls(result, config) 117 | 118 | def __str__(self): 119 | return self.result.__str__() 120 | 121 | def __eq__(self, other): 122 | if not isinstance(other, PiBasResult): 123 | return False 124 | return self.result == other.result 125 | 126 | def get_result_list(self) -> list: 127 | return self.result 128 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPack/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "PiPack" 18 | _module_name = "CJJ14.PiPack" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPack/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_PACK_HEADER = b"\x93\x94Cash2014PiPack" 21 | 22 | # If the param of the specified length is not suffixed, the default is in bytes 23 | # The length parameter of the bit suffix is the length in bits 24 | 25 | DEFAULT_CONFIG = { 26 | "scheme": "CJJ14.PiPack", 27 | "param_lambda": 32, # key size (bytes) 28 | "param_B": 64, # a fixed block size, process B identifiers at a time and pack them into one ciphertext d 29 | "param_identifier_size": 8, 30 | "prf_f_output_length": 32, 31 | 32 | "prf_f": "HmacPRF", 33 | "ske": "AES-CBC" 34 | } 35 | 36 | 37 | class PiPackConfig(SSEConfig): 38 | __slots__ = [ 39 | "param_lambda", 40 | "param_B", 41 | "prf_f_output_length", 42 | "param_identifier_size", 43 | 44 | "prf_f", 45 | "ske" 46 | ] 47 | 48 | DEFAULT_CONFIG = DEFAULT_CONFIG 49 | 50 | def __init__(self, config_dict: dict): 51 | super(PiPackConfig, self).__init__(config_dict) 52 | self._parse_config(config_dict) 53 | 54 | def _parse_config(self, config_dict: dict): 55 | SSEConfig.check_param_exist(["param_lambda", 56 | "param_B", 57 | "prf_f_output_length", 58 | "param_identifier_size", 59 | "prf_f", 60 | "ske"], 61 | config_dict) 62 | 63 | self.param_lambda = config_dict.get("param_lambda") 64 | self.param_B = config_dict.get("param_B") 65 | self.prf_f_output_length = config_dict.get("prf_f_output_length") 66 | self.param_identifier_size = config_dict.get("param_identifier_size") 67 | 68 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 69 | key_length=self.param_lambda, 70 | output_length=self.prf_f_output_length) 71 | 72 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 73 | key_length=self.param_lambda 74 | ) 75 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPack/construction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: construction.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: ΠPack Construction described by Cash et al. [CJJ+14] 12 | 13 | @note: 14 | Here, we define the file identifier to start from 1, 15 | to eliminate the misunderstanding of the de-padding algorithm due to the misunderstanding of 0 as the padding value !!! 16 | """ 17 | import os 18 | 19 | import schemes.interface.inverted_index_sse 20 | from schemes.CJJ14.PiPack.config import DEFAULT_CONFIG, PiPackConfig 21 | from schemes.CJJ14.PiPack.structures import PiPackKey, PiPackToken, PiPackEncryptedDatabase, PiPackResult 22 | from toolkit.bytes_utils import int_to_bytes 23 | from toolkit.database_utils import partition_identifiers_to_blocks, parse_identifiers_from_block_given_identifier_size 24 | 25 | 26 | class PiPack(schemes.interface.inverted_index_sse.InvertedIndexSSE): 27 | """PiPack Construction described by Cash et al. [CJJ+14]""" 28 | 29 | def __init__(self, config: dict = DEFAULT_CONFIG): 30 | super(PiPack, self).__init__() 31 | self.config = PiPackConfig(config) 32 | pass 33 | 34 | def _Gen(self) -> PiPackKey: 35 | """ 36 | Generate Key 37 | K2 is not used here now. 38 | """ 39 | K = os.urandom(self.config.param_lambda) 40 | return PiPackKey(K) 41 | 42 | def _Enc(self, K: PiPackKey, database: dict) -> PiPackEncryptedDatabase: 43 | """Encrypted the given database under the key""" 44 | K = K.K 45 | L = [] 46 | 47 | for keyword in database: 48 | K1 = self.config.prf_f(K, b'\x01' + keyword) 49 | K2 = self.config.prf_f(K, b'\x02' + keyword) 50 | block_list = partition_identifiers_to_blocks(database[keyword], self.config.param_B, 51 | self.config.param_identifier_size) 52 | 53 | for c, block in enumerate(block_list): 54 | l = self.config.prf_f(K1, int_to_bytes(c)) 55 | d = self.config.ske.Encrypt(K2, block) 56 | L.append((l, d)) 57 | return PiPackEncryptedDatabase.build_from_list(L) 58 | 59 | def _Trap(self, K: PiPackKey, keyword: bytes) -> PiPackToken: 60 | """Trapdoor Generation Algorithm""" 61 | K = K.K 62 | K1 = self.config.prf_f(K, b'\x01' + keyword) 63 | K2 = self.config.prf_f(K, b'\x02' + keyword) 64 | return PiPackToken(K1, K2) 65 | 66 | def _Search(self, edb: PiPackEncryptedDatabase, tk: PiPackToken) -> PiPackResult: 67 | """Search Algorithm""" 68 | D = edb.D 69 | K1, K2 = tk.K1, tk.K2 70 | result = [] 71 | c = 0 72 | while True: 73 | addr = self.config.prf_f(K1, int_to_bytes(c)) 74 | cipher = D.get(addr) 75 | if cipher is None: 76 | break 77 | result.extend(parse_identifiers_from_block_given_identifier_size(self.config.ske.Decrypt(K2, cipher), 78 | self.config.param_identifier_size)) 79 | c += 1 80 | 81 | return PiPackResult(result) 82 | 83 | def KeyGen(self) -> PiPackKey: 84 | key = self._Gen() 85 | return key 86 | 87 | def EDBSetup(self, 88 | key: PiPackKey, 89 | database: dict 90 | ) -> PiPackEncryptedDatabase: 91 | return self._Enc(key, database) 92 | 93 | def TokenGen(self, key: PiPackKey, keyword: bytes) -> PiPackToken: 94 | return self._Trap(key, keyword) 95 | 96 | def Search(self, 97 | edb: PiPackEncryptedDatabase, 98 | token: PiPackToken) -> PiPackResult: 99 | return self._Search(edb, token) 100 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPack/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CJJ14.PiPack.config import PiPackConfig, PI_PACK_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class PiPackKey(SSEKey): 20 | __slots__ = ["K"] 21 | 22 | def __init__(self, K: bytes, config: PiPackConfig = None): 23 | super(PiPackKey, self).__init__(config) 24 | self.K = K 25 | 26 | def serialize(self) -> bytes: 27 | return self.K 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: PiPackConfig): 31 | if len(xbytes) != config.param_lambda: 32 | raise ValueError( 33 | "The length of xbytes must be the same as the length of the parameter param_k." 34 | ) 35 | 36 | return cls(xbytes) 37 | 38 | def __eq__(self, other): 39 | if not isinstance(other, PiPackKey): 40 | return False 41 | return self.K == other.K 42 | 43 | 44 | class PiPackEncryptedDatabase(SSEEncryptedDatabase): 45 | __slots__ = ["D"] # dict D 46 | 47 | def __init__(self, D: dict, config: PiPackConfig = None): 48 | super(PiPackEncryptedDatabase, self).__init__(config) 49 | self.D = D 50 | 51 | @classmethod 52 | def build_from_list(cls, kv_pairs: list, config: PiPackConfig = None): 53 | kv_pairs.sort(key=lambda pair: pair[0]) 54 | D = {key: value for key, value in kv_pairs} 55 | return cls(D, config) 56 | 57 | def serialize(self) -> bytes: 58 | data = PI_PACK_HEADER + pickle.dumps(self.D) 59 | return data 60 | 61 | @classmethod 62 | def deserialize(cls, xbytes: bytes, config: PiPackConfig = None): 63 | if xbytes[:len(PI_PACK_HEADER)] != PI_PACK_HEADER: 64 | raise ValueError("Parse header error.") 65 | 66 | data_bytes = xbytes[len(PI_PACK_HEADER):] 67 | D = pickle.loads(data_bytes) 68 | return cls(D) 69 | 70 | def __eq__(self, other): 71 | if not isinstance(other, PiPackEncryptedDatabase): 72 | return False 73 | return self.D == other.D 74 | 75 | 76 | class PiPackToken(SSEToken): 77 | __slots__ = ["K1", "K2"] # K1, K2 78 | 79 | def __init__(self, K1: bytes, K2: bytes, config: PiPackConfig = None): 80 | super(PiPackToken, self).__init__(config) 81 | self.K1 = K1 82 | self.K2 = K2 83 | 84 | def serialize(self) -> bytes: 85 | return self.K1 + self.K2 86 | 87 | @classmethod 88 | def deserialize(cls, xbytes: bytes, config: PiPackConfig = None): 89 | if len(xbytes) != 2 * config.param_lambda: 90 | raise ValueError( 91 | "The length of xbytes must be 2 times the length of the parameter param_lambda." 92 | ) 93 | 94 | K1, K2 = xbytes[:config.param_lambda], xbytes[config.param_lambda:] 95 | 96 | return cls(K1, K2, config) 97 | 98 | def __eq__(self, other): 99 | if not isinstance(other, PiPackToken): 100 | return False 101 | return self.K1 == other.K1 and self.K2 == other.K2 102 | 103 | 104 | class PiPackResult(SSEResult): 105 | __slots__ = ["result"] 106 | 107 | def __init__(self, result: list, config: PiPackConfig = None): 108 | super(PiPackResult, self).__init__(config) 109 | self.result = result 110 | 111 | def serialize(self) -> bytes: 112 | return pickle.dumps(self.result) 113 | 114 | @classmethod 115 | def deserialize(cls, xbytes: bytes, config: PiPackConfig = None): 116 | result = pickle.loads(xbytes) 117 | if not isinstance(result, list): 118 | return ValueError("The data contained in xbytes is not a list.") 119 | 120 | return cls(result, config) 121 | 122 | def __str__(self): 123 | return self.result.__str__() 124 | 125 | def __eq__(self, other): 126 | if not isinstance(other, PiPackResult): 127 | return False 128 | return self.result == other.result 129 | 130 | def get_result_list(self) -> list: 131 | return self.result 132 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPtr/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "PiPtr" 18 | _module_name = "CJJ14.PiPtr" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPtr/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_PTR_HEADER = b"\x93\x94Cash2014PiPtr" 21 | 22 | # If the param of the specified length is not suffixed, the default is in bytes 23 | # The length parameter of the bit suffix is the length in bits 24 | 25 | DEFAULT_CONFIG = { 26 | "scheme": "CJJ14.PiPtr", 27 | "param_lambda": 32, # key size (bytes) 28 | "param_B": 64, # a fixed block size, process B identifiers at a time and pack them into one ciphertext d 29 | "param_b": 64, # store encrypted blocks of b pointers to these encrypted blocks 30 | "param_identifier_size": 8, 31 | "prf_f_output_length": 32, 32 | 33 | "prf_f": "HmacPRF", 34 | "ske": "AES-CBC" 35 | } 36 | 37 | 38 | class PiPtrConfig(SSEConfig): 39 | __slots__ = [ 40 | "param_lambda", 41 | "param_B", 42 | "param_b", 43 | "prf_f_output_length", 44 | "param_identifier_size", 45 | 46 | "prf_f", 47 | "ske" 48 | ] 49 | 50 | DEFAULT_CONFIG = DEFAULT_CONFIG 51 | 52 | def __init__(self, config_dict: dict): 53 | super(PiPtrConfig, self).__init__(config_dict) 54 | self._parse_config(config_dict) 55 | 56 | def _parse_config(self, config_dict: dict): 57 | SSEConfig.check_param_exist(["param_lambda", 58 | "param_B", 59 | "param_b", 60 | "prf_f_output_length", 61 | "param_identifier_size", 62 | "prf_f", 63 | "ske"], 64 | config_dict) 65 | 66 | self.param_lambda = config_dict.get("param_lambda") 67 | self.param_B = config_dict.get("param_B") 68 | self.param_b = config_dict.get("param_b") 69 | self.prf_f_output_length = config_dict.get("prf_f_output_length") 70 | self.param_identifier_size = config_dict.get("param_identifier_size") 71 | 72 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 73 | key_length=self.param_lambda, 74 | output_length=self.prf_f_output_length) 75 | 76 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 77 | key_length=self.param_lambda 78 | ) 79 | -------------------------------------------------------------------------------- /schemes/CJJ14/PiPtr/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CJJ14.PiPtr.config import PiPtrConfig, PI_PTR_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class PiPtrKey(SSEKey): 20 | __slots__ = ["K"] 21 | 22 | def __init__(self, K: bytes, config: PiPtrConfig = None): 23 | super(PiPtrKey, self).__init__(config) 24 | self.K = K 25 | 26 | def serialize(self) -> bytes: 27 | return self.K 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: PiPtrConfig): 31 | if len(xbytes) != config.param_lambda: 32 | raise ValueError( 33 | "The length of xbytes must be the same as the length of the parameter param_k." 34 | ) 35 | 36 | return cls(xbytes) 37 | 38 | def __eq__(self, other): 39 | if not isinstance(other, PiPtrKey): 40 | return False 41 | return self.K == other.K 42 | 43 | 44 | class PiPtrEncryptedDatabase(SSEEncryptedDatabase): 45 | __slots__ = ["D", "A"] # dict D, array A 46 | 47 | def __init__(self, D: dict, A: list, config: PiPtrConfig = None): 48 | super(PiPtrEncryptedDatabase, self).__init__(config) 49 | self.D = D 50 | self.A = A 51 | 52 | @staticmethod 53 | def create_dictionary_from_list(kv_pairs: list) -> dict: 54 | kv_pairs.sort(key=lambda pair: pair[0]) 55 | D = {key: value for key, value in kv_pairs} 56 | return D 57 | 58 | def serialize(self) -> bytes: 59 | data = PI_PTR_HEADER + pickle.dumps((self.D, self.A)) 60 | return data 61 | 62 | @classmethod 63 | def deserialize(cls, xbytes: bytes, config: PiPtrConfig = None): 64 | if xbytes[:len(PI_PTR_HEADER)] != PI_PTR_HEADER: 65 | raise ValueError("Parse header error.") 66 | 67 | data_bytes = xbytes[len(PI_PTR_HEADER):] 68 | D, A = pickle.loads(data_bytes) 69 | return cls(D, A) 70 | 71 | def __eq__(self, other): 72 | if not isinstance(other, PiPtrEncryptedDatabase): 73 | return False 74 | return self.D == other.D and self.A == other.A 75 | 76 | 77 | class PiPtrToken(SSEToken): 78 | __slots__ = ["K1", "K2"] # K1, K2 79 | 80 | def __init__(self, K1: bytes, K2: bytes, config: PiPtrConfig = None): 81 | super(PiPtrToken, self).__init__(config) 82 | self.K1 = K1 83 | self.K2 = K2 84 | 85 | def serialize(self) -> bytes: 86 | return self.K1 + self.K2 87 | 88 | @classmethod 89 | def deserialize(cls, xbytes: bytes, config: PiPtrConfig = None): 90 | if len(xbytes) != 2 * config.param_lambda: 91 | raise ValueError( 92 | "The length of xbytes must be 2 times the length of the parameter param_lambda." 93 | ) 94 | 95 | K1, K2 = xbytes[:config.param_lambda], xbytes[config.param_lambda:] 96 | 97 | return cls(K1, K2, config) 98 | 99 | def __eq__(self, other): 100 | if not isinstance(other, PiPtrToken): 101 | return False 102 | return self.K1 == other.K1 and self.K2 == other.K2 103 | 104 | 105 | class PiPtrResult(SSEResult): 106 | __slots__ = ["result"] 107 | 108 | def __init__(self, result: list, config: PiPtrConfig = None): 109 | super(PiPtrResult, self).__init__(config) 110 | self.result = result 111 | 112 | def serialize(self) -> bytes: 113 | return pickle.dumps(self.result) 114 | 115 | @classmethod 116 | def deserialize(cls, xbytes: bytes, config: PiPtrConfig = None): 117 | result = pickle.loads(xbytes) 118 | if not isinstance(result, list): 119 | return ValueError("The data contained in xbytes is not a list.") 120 | 121 | return cls(result, config) 122 | 123 | def __str__(self): 124 | return self.result.__str__() 125 | 126 | def __eq__(self, other): 127 | if not isinstance(other, PiPtrResult): 128 | return False 129 | return self.result == other.result 130 | 131 | def get_result_list(self) -> list: 132 | return self.result 133 | -------------------------------------------------------------------------------- /schemes/CJJ14/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/12 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/CT14/Pi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "Pi" 18 | _module_name = "CT14.Pi" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/CT14/Pi/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.prf 16 | import toolkit.prp 17 | import toolkit.symmetric_encryption 18 | from schemes.interface.config import SSEConfig 19 | 20 | PI_HEADER = b"\x93\x94Cash2014LocalityPi" 21 | 22 | # If the param of the specified length is not suffixed, the default is in bytes 23 | # The length parameter of the bit suffix is the length in bits 24 | 25 | DEFAULT_CONFIG = { 26 | "scheme": "CT14.Pi", 27 | "param_k": 32, # key space K 28 | "param_k_prime": 32, # key space K' 29 | "param_l": 32, # key size (bytes) 30 | # "param_t": -1, # need to scan, N = 2 ^ t, N is the total size of database 31 | "param_identifier_size": 4, 32 | 33 | "prf_f": "HmacPRF", 34 | "prf_f_prime": "HmacPRF", 35 | "ske": "AES-CBC" 36 | } 37 | 38 | 39 | class PiConfig(SSEConfig): 40 | __slots__ = [ 41 | "param_k", 42 | "param_k_prime", 43 | "param_l", 44 | "param_identifier_size", 45 | 46 | "prf_f", 47 | "prf_f_prime", 48 | "ske" 49 | ] 50 | 51 | DEFAULT_CONFIG = DEFAULT_CONFIG 52 | 53 | def __init__(self, config_dict: dict): 54 | super(PiConfig, self).__init__(config_dict) 55 | self._parse_config(config_dict) 56 | 57 | def _parse_config(self, config_dict: dict): 58 | SSEConfig.check_param_exist(["param_k", 59 | "param_k_prime", 60 | "param_l", 61 | "param_identifier_size", 62 | "prf_f", 63 | "prf_f_prime", 64 | "ske"], 65 | config_dict) 66 | 67 | self.param_k = config_dict.get("param_k") 68 | self.param_k_prime = config_dict.get("param_k_prime") 69 | self.param_l = config_dict.get("param_l") 70 | # self.param_t = config_dict.get("param_t") 71 | 72 | self.param_identifier_size = config_dict.get("param_identifier_size") 73 | 74 | self.prf_f = toolkit.prf.get_prf_implementation(config_dict.get("prf_f", ""))( 75 | key_length=self.param_k, 76 | output_length=self.param_k + self.param_k_prime) 77 | 78 | self.prf_f_prime = toolkit.prf.get_prf_implementation(config_dict.get("prf_f_prime", ""))( 79 | key_length=self.param_k, 80 | output_length=self.param_l) 81 | 82 | self.ske = toolkit.symmetric_encryption.get_symmetric_encryption_implementation(config_dict.get("ske", ""))( 83 | key_length=self.param_k_prime 84 | ) 85 | -------------------------------------------------------------------------------- /schemes/CT14/Pi/construction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: construction.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: Π Construction described by Cash et al. [CT14] 12 | """ 13 | import copy 14 | import math 15 | import os 16 | import random 17 | 18 | import schemes.interface.inverted_index_sse 19 | from schemes.CT14.Pi.config import DEFAULT_CONFIG, PiConfig 20 | from schemes.CT14.Pi.structures import PiKey, PiToken, PiEncryptedDatabase, PiResult 21 | from toolkit.bytes_utils import int_to_bytes 22 | from toolkit.database_utils import get_total_size, parse_identifiers_from_block_given_entry_count_in_one_block 23 | 24 | 25 | class Pi(schemes.interface.inverted_index_sse.InvertedIndexSSE): 26 | """Pi Construction described by Cash et al. [CT14]""" 27 | 28 | def __init__(self, config: dict = DEFAULT_CONFIG): 29 | super(Pi, self).__init__() 30 | self.config = PiConfig(config) 31 | pass 32 | 33 | def _Gen(self) -> PiKey: 34 | """ 35 | Generate Key 36 | K2 is not used here now. 37 | """ 38 | K = os.urandom(self.config.param_k) 39 | return PiKey(K) 40 | 41 | def _Enc(self, K: PiKey, database: dict) -> PiEncryptedDatabase: 42 | """Encrypted the given database under the key""" 43 | K = K.K 44 | N = get_total_size(database) 45 | t = math.ceil(math.log2(N)) 46 | 47 | padded_database = copy.deepcopy(database) # need to deep copy!! Otherwise, it will affect the original database 48 | 49 | # If N is not a power of two, we need to pad DB to 50 | # satisfy this by adding some dummy keyword-identifier pairs. 51 | while N < 2 ** t: 52 | random_keyword = os.urandom(32) 53 | random_id_list_len = random.randint(1, (2 ** t) - N) 54 | random_id_list = [os.urandom(self.config.param_identifier_size) for _ in range(random_id_list_len)] 55 | padded_database[random_keyword] = random_id_list 56 | 57 | N += random_id_list_len 58 | 59 | L_list = [[] for _ in range(t)] # t empty lists L0, L1, ... , Lt−1 60 | 61 | for keyword in padded_database: 62 | Kw0_concat_Kw1 = self.config.prf_f(K, keyword) 63 | Kw0, Kw1 = Kw0_concat_Kw1[:self.config.param_k], Kw0_concat_Kw1[self.config.param_k:] 64 | 65 | c = 0 66 | for j in range(int(math.log2(len(padded_database[keyword]))), -1, -1): 67 | if 2 ** j > len(padded_database[keyword]) - c: 68 | continue 69 | cipher_list = [self.config.ske.Encrypt(Kw1, padded_database[keyword][i]) for i in 70 | range(c, c + 2 ** j)] 71 | d = b''.join(cipher_list) 72 | l = self.config.prf_f_prime(Kw0, int_to_bytes(j)) 73 | L_list[j].append((l, d)) 74 | c += 2 ** j 75 | 76 | # padding each list 77 | for i in range(t): 78 | d_len = (2 ** i) * len(self.config.ske.Encrypt(b"\x00" * self.config.param_k_prime, 79 | b"\x00" * self.config.param_identifier_size)) 80 | L_list[i].extend( 81 | ((os.urandom(self.config.param_l), os.urandom(d_len)) for _ in range((2 ** (t - i)) - len(L_list[i])))) 82 | # create HT_0, ..., HT_{t-1} 83 | HT_list = [] 84 | for i in range(t): 85 | HT_list.append(PiEncryptedDatabase.create_hash_table(L_list[i])) 86 | 87 | return PiEncryptedDatabase(HT_list, self.config) 88 | 89 | def _Trap(self, K: PiKey, keyword: bytes) -> PiToken: 90 | """Trapdoor Generation Algorithm""" 91 | K = K.K 92 | K0_concat_K1 = self.config.prf_f(K, keyword) 93 | K0, K1 = K0_concat_K1[:self.config.param_k], K0_concat_K1[self.config.param_k:] 94 | return PiToken(K0, K1) 95 | 96 | def _Search(self, edb: PiEncryptedDatabase, tk: PiToken) -> PiResult: 97 | """Search Algorithm""" 98 | HT_list = edb.HT_list 99 | t = len(HT_list) 100 | K0, K1 = tk.K0, tk.K1 101 | result = [] 102 | for i in range(t - 1, -1, -1): 103 | l = self.config.prf_f_prime(K0, int_to_bytes(i)) 104 | d = HT_list[i].get(l) 105 | if d is not None: 106 | cipher_list = parse_identifiers_from_block_given_entry_count_in_one_block(d, 2 ** i) 107 | result.extend((self.config.ske.Decrypt(K1, cipher) for cipher in cipher_list)) 108 | 109 | return PiResult(result) 110 | 111 | def KeyGen(self) -> PiKey: 112 | key = self._Gen() 113 | return key 114 | 115 | def EDBSetup(self, 116 | key: PiKey, 117 | database: dict 118 | ) -> PiEncryptedDatabase: 119 | return self._Enc(key, database) 120 | 121 | def TokenGen(self, key: PiKey, keyword: bytes) -> PiToken: 122 | return self._Trap(key, keyword) 123 | 124 | def Search(self, 125 | edb: PiEncryptedDatabase, 126 | token: PiToken) -> PiResult: 127 | return self._Search(edb, token) 128 | -------------------------------------------------------------------------------- /schemes/CT14/Pi/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | 15 | from schemes.CT14.Pi.config import PiConfig, PI_HEADER 16 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 17 | 18 | 19 | class PiKey(SSEKey): 20 | __slots__ = ["K"] 21 | 22 | def __init__(self, K: bytes, config: PiConfig = None): 23 | super(PiKey, self).__init__(config) 24 | self.K = K 25 | 26 | def serialize(self) -> bytes: 27 | return self.K 28 | 29 | @classmethod 30 | def deserialize(cls, xbytes: bytes, config: PiConfig): 31 | if len(xbytes) != config.param_k: 32 | raise ValueError("The length of xbytes must be the same as the length of the parameter param_lambda.") 33 | 34 | return cls(xbytes) 35 | 36 | def __eq__(self, other): 37 | if not isinstance(other, PiKey): 38 | return False 39 | return self.K == other.K 40 | 41 | 42 | class PiEncryptedDatabase(SSEEncryptedDatabase): 43 | __slots__ = ["HT_list"] # dict D 44 | 45 | def __init__(self, HT_list: list, config: PiConfig = None): 46 | super(PiEncryptedDatabase, self).__init__(config) 47 | self.HT_list = HT_list 48 | 49 | @classmethod 50 | def create_hash_table(cls, kv_pairs: list): 51 | kv_pairs.sort(key=lambda pair: pair[0]) 52 | D = {key: value for key, value in kv_pairs} 53 | return D 54 | 55 | def serialize(self) -> bytes: 56 | data = PI_HEADER + pickle.dumps(self.HT_list) 57 | return data 58 | 59 | @classmethod 60 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 61 | if xbytes[:len(PI_HEADER)] != PI_HEADER: 62 | raise ValueError("Parse header error.") 63 | 64 | data_bytes = xbytes[len(PI_HEADER):] 65 | HT_list = pickle.loads(data_bytes) 66 | return cls(HT_list, config) 67 | 68 | def __eq__(self, other): 69 | if not isinstance(other, PiEncryptedDatabase): 70 | return False 71 | return self.HT_list == other.HT_list 72 | 73 | 74 | class PiToken(SSEToken): 75 | __slots__ = ["K0", "K1"] # K_{w,0}, K_{w,1} 76 | 77 | def __init__(self, K0: bytes, K1: bytes, config: PiConfig = None): 78 | super(PiToken, self).__init__(config) 79 | self.K0 = K0 80 | self.K1 = K1 81 | 82 | def serialize(self) -> bytes: 83 | return self.K0 + self.K1 84 | 85 | @classmethod 86 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 87 | if len(xbytes) != config.param_k + config.param_k_prime: 88 | raise ValueError("The length of xbytes must be param_k + param_k_prime.") 89 | 90 | K1, K2 = xbytes[:config.param_k], xbytes[config.param_k:] 91 | 92 | return cls(K1, K2, config) 93 | 94 | def __eq__(self, other): 95 | if not isinstance(other, PiToken): 96 | return False 97 | return self.K0 == other.K0 and self.K1 == other.K1 98 | 99 | 100 | class PiResult(SSEResult): 101 | __slots__ = ["result"] 102 | 103 | def __init__(self, result: list, config: PiConfig = None): 104 | super(PiResult, self).__init__(config) 105 | self.result = result 106 | 107 | def serialize(self) -> bytes: 108 | return pickle.dumps(self.result) 109 | 110 | @classmethod 111 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 112 | result = pickle.loads(xbytes) 113 | if not isinstance(result, list): 114 | return ValueError("The data contained in xbytes is not a list.") 115 | 116 | return cls(result, config) 117 | 118 | def __str__(self): 119 | return self.result.__str__() 120 | 121 | def __eq__(self, other): 122 | if not isinstance(other, PiResult): 123 | return False 124 | return self.result == other.result 125 | 126 | def get_result_list(self) -> list: 127 | return self.result 128 | -------------------------------------------------------------------------------- /schemes/CT14/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/13 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/DP17/Pi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/25 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import schemes.interface.module_loader 14 | 15 | 16 | class ModuleClassLoader(schemes.interface.module_loader.SSEModuleClassLoader): 17 | _sse_name = "Pi" 18 | _module_name = "DP17.Pi" 19 | 20 | 21 | # __init__.py in every SSE module must have sse_module_class_loader attribute 22 | sse_module_class_loader = ModuleClassLoader() 23 | -------------------------------------------------------------------------------- /schemes/DP17/Pi/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/25 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import toolkit.config_manager 15 | import toolkit.hash 16 | import toolkit.prf 17 | import toolkit.prp 18 | import toolkit.symmetric_encryption 19 | from schemes.interface.config import SSEConfig 20 | 21 | PI_HEADER = b"\x93\x94Demertzis2017LocalityPi" 22 | 23 | # If the param of the specified length is not suffixed, the default is in bytes 24 | # The length parameter of the bit suffix is the length in bits 25 | 26 | DEFAULT_CONFIG = { 27 | "scheme": "DP17.Pi", 28 | "param_lambda": 32, # key space K 29 | 30 | # Instead of using the parameter s, 31 | # we use a ratio to indicate the actual number of layers stored. 32 | "param_actual_storage_level_ratio": 0.2, 33 | 34 | # "param_s": 7, # level count 35 | "param_L": 1, # tunable locality 36 | "param_identifier_size": 8, 37 | "rnd": "AES-CBC", 38 | "prf_f": "HmacPRF", 39 | "hash_h": "SHA1" 40 | } 41 | 42 | 43 | class PiConfig(SSEConfig): 44 | __slots__ = [ 45 | "param_lambda", "param_s", "param_L", "param_identifier_size", "rnd", 46 | "prf_f", "hash_h", "param_identifier_cipher_len", 47 | "param_hash_h_digest_size" 48 | ] 49 | 50 | DEFAULT_CONFIG = DEFAULT_CONFIG 51 | 52 | def __init__(self, config_dict: dict): 53 | super(PiConfig, self).__init__(config_dict) 54 | self._parse_config(config_dict) 55 | 56 | def _parse_config(self, config_dict: dict): 57 | SSEConfig.check_param_exist([ 58 | "param_lambda", "param_actual_storage_level_ratio", "param_L", 59 | "param_identifier_size", "rnd", "prf_f", "hash_h" 60 | ], config_dict) 61 | 62 | self.param_lambda = config_dict.get("param_lambda") 63 | self.param_actual_storage_level_ratio = config_dict.get( 64 | "param_actual_storage_level_ratio") 65 | self.param_L = config_dict.get("param_L") 66 | self.param_identifier_size = config_dict.get("param_identifier_size") 67 | 68 | self.rnd = toolkit.symmetric_encryption.get_symmetric_encryption_implementation( 69 | config_dict.get("rnd", ""))(key_length=self.param_lambda) 70 | 71 | self.prf_f = toolkit.prf.get_prf_implementation( 72 | config_dict.get("prf_f", ""))(key_length=self.param_lambda, 73 | output_length=self.param_lambda) 74 | 75 | self.hash_h = toolkit.hash.get_hash_implementation( 76 | config_dict.get("hash_h", ""))() 77 | 78 | self.param_identifier_cipher_len = len( 79 | self.rnd.Encrypt( 80 | b"\x00" * self.param_lambda, 81 | b"\x00" * (self.param_identifier_size + self.param_lambda))) 82 | self.param_hash_h_digest_size = self.hash_h.output_length 83 | -------------------------------------------------------------------------------- /schemes/DP17/Pi/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/25 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import pickle 14 | import typing 15 | 16 | from schemes.DP17.Pi.config import PiConfig, PI_HEADER 17 | from schemes.interface.structures import SSEKey, SSEEncryptedDatabase, SSEToken, SSEResult 18 | from toolkit.bytes_utils import split_bytes_given_slice_len 19 | 20 | 21 | class PiKey(SSEKey): 22 | __slots__ = ["k1", "k2", "k3"] 23 | 24 | def __init__(self, 25 | k1: bytes, 26 | k2: bytes, 27 | k3: bytes, 28 | config: PiConfig = None): 29 | super(PiKey, self).__init__(config) 30 | self.k1, self.k2, self.k3 = k1, k2, k3 31 | 32 | def serialize(self) -> bytes: 33 | return self.k1 + self.k2 + self.k3 34 | 35 | @classmethod 36 | def deserialize(cls, xbytes: bytes, config: PiConfig): 37 | if len(xbytes) != 3 * config.param_lambda: 38 | raise ValueError( 39 | "The length of xbytes must be three times the length of the parameter param_lambda." 40 | ) 41 | k1, k2, k3 = split_bytes_given_slice_len(xbytes, 42 | [config.param_lambda] * 3) 43 | 44 | return cls(k1, k2, k3, config) 45 | 46 | def __eq__(self, other): 47 | if not isinstance(other, PiKey): 48 | return False 49 | return self.k1 == other.k1 and self.k2 == other.k2 and self.k3 == other.k3 50 | 51 | 52 | class PiEncryptedDatabase(SSEEncryptedDatabase): 53 | __slots__ = ["HT", "A_dict"] 54 | 55 | def __init__(self, 56 | HT: dict, 57 | A_dict: typing.Dict[int, typing.List[bytes]], 58 | config: PiConfig = None): 59 | super(PiEncryptedDatabase, self).__init__(config) 60 | self.HT = HT 61 | self.A_dict = A_dict 62 | 63 | def serialize(self) -> bytes: 64 | data = PI_HEADER + pickle.dumps((self.HT, self.A_dict)) 65 | return data 66 | 67 | @classmethod 68 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 69 | if xbytes[:len(PI_HEADER)] != PI_HEADER: 70 | raise ValueError("Parse header error.") 71 | 72 | HT, A_dict = pickle.loads(xbytes[len(PI_HEADER):]) 73 | return cls(HT, A_dict, config=config) 74 | 75 | def __eq__(self, other): 76 | if not isinstance(other, PiEncryptedDatabase): 77 | return False 78 | return self.HT == other.HT and self.A_dict == other.A_dict 79 | 80 | 81 | class PiToken(SSEToken): 82 | __slots__ = ["tag", "vtag", "etag"] 83 | 84 | def __init__(self, 85 | tag: bytes, 86 | vtag: bytes, 87 | etag: bytes, 88 | config: PiConfig = None): 89 | super(PiToken, self).__init__(config) 90 | self.tag, self.vtag, self.etag = tag, vtag, etag 91 | 92 | def serialize(self) -> bytes: 93 | return pickle.dumps((self.tag, self.vtag, self.etag)) 94 | 95 | @classmethod 96 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 97 | tag, vtag, etag = pickle.loads(xbytes) 98 | 99 | return cls(tag, vtag, etag, config) 100 | 101 | def __eq__(self, other): 102 | if not isinstance(other, PiToken): 103 | return False 104 | return self.tag == other.tag and self.vtag == other.vtag and self.etag == other.etag 105 | 106 | 107 | class PiResult(SSEResult): 108 | __slots__ = ["result"] 109 | 110 | def __init__(self, result: set, config: PiConfig = None): 111 | super(PiResult, self).__init__(config) 112 | self.result = result 113 | 114 | def serialize(self) -> bytes: 115 | return pickle.dumps(self.result) 116 | 117 | @classmethod 118 | def deserialize(cls, xbytes: bytes, config: PiConfig = None): 119 | result = pickle.loads(xbytes) 120 | if not isinstance(result, set): 121 | return ValueError("The data contained in xbytes is not a list.") 122 | 123 | return cls(result, config) 124 | 125 | def __str__(self): 126 | return self.result.__str__() 127 | 128 | def __eq__(self, other): 129 | if not isinstance(other, PiResult): 130 | return False 131 | return self.result == other.result 132 | 133 | def get_result_list(self) -> set: 134 | return self.result 135 | -------------------------------------------------------------------------------- /schemes/DP17/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/25 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/__init__.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | from schemes.interface.module_loader import SSEModuleClassLoader 4 | 5 | __builtin_module_class_loader_cache = {} 6 | 7 | __loader_name = "sse_module_class_loader" 8 | __schemes_module_path = "schemes" # 9 | 10 | 11 | def load_sse_module(sse_module_name: str) -> SSEModuleClassLoader: 12 | if sse_module_name in __builtin_module_class_loader_cache: 13 | return __builtin_module_class_loader_cache[sse_module_name] 14 | 15 | try: 16 | module = importlib.import_module(__schemes_module_path + "." + sse_module_name) 17 | if not hasattr(module, __loader_name): 18 | raise ImportError() 19 | loader = getattr(module, __loader_name) 20 | __builtin_module_class_loader_cache[sse_module_name] = loader 21 | return loader 22 | except ImportError: 23 | raise ValueError(f"Unsupported SSE Scheme: {sse_module_name}") 24 | -------------------------------------------------------------------------------- /schemes/interface/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /schemes/interface/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import abc 14 | import json 15 | 16 | 17 | class SSEConfig(metaclass=abc.ABCMeta): 18 | def __init__(self, *args, **kwargs): 19 | pass 20 | 21 | DEFAULT_CONFIG = {} 22 | 23 | @classmethod 24 | def get_default_config(cls): 25 | return cls.DEFAULT_CONFIG 26 | 27 | @abc.abstractmethod 28 | def _parse_config(self, config_dict: dict): 29 | return 30 | 31 | def to_json(self): 32 | config_dict = {key: getattr(self, key) for key in self.__slots__} 33 | return json.dumps(config_dict, indent=4) 34 | 35 | @classmethod 36 | def from_json(cls, json_str: str): 37 | config_dict = json.loads(json_str) 38 | return cls(config_dict) 39 | 40 | def to_dict(self): 41 | config_dict = {key: getattr(self, key) for key in self.__slots__} 42 | return config_dict 43 | 44 | @classmethod 45 | def from_dict(cls, config_dict: dict): 46 | return cls(config_dict) 47 | 48 | @staticmethod 49 | def check_param_exist(param_field_to_check: list, config_dict: dict): 50 | for param_field in param_field_to_check: 51 | if config_dict.get(param_field, -1) == -1: 52 | raise ValueError("Parameter {} is missing".format(param_field)) 53 | 54 | def __getitem__(self, item): 55 | return getattr(self, item) 56 | -------------------------------------------------------------------------------- /schemes/interface/inverted_index_sse.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: inverted_index_sse.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: Inverted Index Based SSE Scheme Abstraction 12 | """ 13 | 14 | import abc 15 | 16 | from schemes.interface.structures import SSEKey, SSEToken, SSEEncryptedDatabase, SSEResult 17 | 18 | 19 | # todo 有些SSE方案是可以允许所有方法是静态的, 而有些则应该分成客户端和服务端两个一起控制 20 | class InvertedIndexSSE(metaclass=abc.ABCMeta): 21 | """Inverted Index Based SSE Scheme Abstraction, Based on [CGKO06]""" 22 | 23 | def __init__(self, *args, **kwargs): 24 | pass 25 | 26 | # def _parse_param_dict(self, param_dict: dict): 27 | # for member in self.__slots__: 28 | # if param_dict.get(member, None) is None: 29 | # raise ValueError("param {} not exists.".format(member)) 30 | # setattr(self, member, param_dict[member]) 31 | 32 | @abc.abstractmethod 33 | def KeyGen(self) -> SSEKey: 34 | pass 35 | 36 | @abc.abstractmethod 37 | def EDBSetup(self, 38 | key: SSEKey, 39 | database) -> SSEEncryptedDatabase: # todo database abstraction 40 | pass 41 | 42 | @abc.abstractmethod 43 | def TokenGen(self, 44 | key: SSEKey, 45 | keyword: bytes) -> SSEToken: # todo keyword abstraction, now is single-keyword 46 | pass 47 | 48 | @abc.abstractmethod 49 | def Search(self, 50 | edb: SSEEncryptedDatabase, 51 | token: SSEToken) -> SSEResult: 52 | pass 53 | -------------------------------------------------------------------------------- /schemes/interface/module_loader.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: module_loader.py 7 | @time: 2022/03/16 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: SSE Module & Class Loader 12 | """ 13 | import abc 14 | import importlib 15 | 16 | _CONSTRUCTION_MODULE_NAME = ".construction" 17 | _STRUCTURE_MODULE_NAME = ".structures" 18 | _CONFIG_MODULE_NAME = ".config" 19 | 20 | """Concrete Class Name = sse name + class suffix""" 21 | _CONFIG_CLASS_SUFFIX = "Config" 22 | _KEY_CLASS_SUFFIX = "Key" 23 | _ENCRYPTED_DATABASE_CLASS_SUFFIX = "EncryptedDatabase" 24 | _TOKEN_CLASS_SUFFIX = "Token" 25 | _RESULT_CLASS_SUFFIX = "Result" 26 | 27 | SCHEMES_MODULE_PATH = "schemes" 28 | 29 | 30 | class SSEModuleClassLoader: 31 | _sse_name = "" # need to be overwritten! 32 | _module_name = "" # need to be overwritten! 33 | 34 | _construction_module = None 35 | _structure_module = None 36 | _config_module_module = None 37 | 38 | def _load_construction_module(self): 39 | actual_path = "{}.{}".format(SCHEMES_MODULE_PATH, self._module_name) 40 | if self._construction_module is None: 41 | self._construction_module = importlib.import_module(_CONSTRUCTION_MODULE_NAME, actual_path) 42 | 43 | def _load_structure_module(self): 44 | actual_path = "{}.{}".format(SCHEMES_MODULE_PATH, self._module_name) 45 | if self._structure_module is None: 46 | self._structure_module = importlib.import_module(_STRUCTURE_MODULE_NAME, actual_path) 47 | 48 | def _load_config_module(self): 49 | actual_path = "{}.{}".format(SCHEMES_MODULE_PATH, self._module_name) 50 | if self._config_module_module is None: 51 | self._config_module_module = importlib.import_module(_CONFIG_MODULE_NAME, actual_path) 52 | 53 | @property 54 | def SSEScheme(self): 55 | self._load_construction_module() 56 | class_name = self._sse_name 57 | 58 | if not hasattr(self._construction_module, class_name): 59 | raise ValueError(f"The construction class of SSE Scheme {self._sse_name} Load Error.") 60 | return getattr(self._construction_module, class_name) 61 | 62 | @property 63 | def SSEConfig(self): 64 | self._load_config_module() 65 | class_name = self._sse_name + _CONFIG_CLASS_SUFFIX 66 | 67 | if not hasattr(self._config_module_module, class_name): 68 | raise ValueError(f"The config class of SSE Scheme {self._sse_name} Load Error.") 69 | return getattr(self._config_module_module, class_name) 70 | 71 | @property 72 | def SSEKey(self): 73 | self._load_structure_module() 74 | class_name = self._sse_name + _KEY_CLASS_SUFFIX 75 | 76 | if not hasattr(self._structure_module, class_name): 77 | raise ValueError(f"The key class of SSE Scheme {self._sse_name} Load Error.") 78 | return getattr(self._structure_module, class_name) 79 | 80 | @property 81 | def SSEEncryptedDatabase(self): 82 | self._load_structure_module() 83 | class_name = self._sse_name + _ENCRYPTED_DATABASE_CLASS_SUFFIX 84 | 85 | if not hasattr(self._structure_module, class_name): 86 | raise ValueError(f"The encrypted database class of SSE Scheme {self._sse_name} Load Error.") 87 | return getattr(self._structure_module, class_name) 88 | 89 | @property 90 | def SSEToken(self): 91 | self._load_structure_module() 92 | class_name = self._sse_name + _TOKEN_CLASS_SUFFIX 93 | 94 | if not hasattr(self._structure_module, class_name): 95 | raise ValueError(f"The token class of SSE Scheme {self._sse_name} Load Error.") 96 | return getattr(self._structure_module, class_name) 97 | 98 | @property 99 | def SSEResult(self): 100 | self._load_structure_module() 101 | class_name = self._sse_name + _RESULT_CLASS_SUFFIX 102 | 103 | if not hasattr(self._structure_module, class_name): 104 | raise ValueError(f"The result class of SSE Scheme {self._sse_name} Load Error.") 105 | return getattr(self._structure_module, class_name) 106 | -------------------------------------------------------------------------------- /schemes/interface/objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: objects.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import abc 15 | 16 | from schemes.interface.config import SSEConfig 17 | 18 | 19 | class SSEObject(metaclass=abc.ABCMeta): 20 | def __init__(self, config: SSEConfig): 21 | pass 22 | 23 | @abc.abstractmethod 24 | def serialize(self) -> bytes: 25 | pass 26 | 27 | @classmethod 28 | @abc.abstractmethod 29 | def deserialize(cls, xbytes: bytes, config: SSEConfig): 30 | pass 31 | -------------------------------------------------------------------------------- /schemes/interface/structures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: structures.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import abc 14 | from schemes.interface.objects import SSEObject 15 | 16 | 17 | class SSEKey(SSEObject, metaclass=abc.ABCMeta): 18 | pass 19 | 20 | 21 | class SSEEncryptedDatabase(SSEObject, metaclass=abc.ABCMeta): 22 | pass 23 | 24 | 25 | class SSEToken(SSEObject, metaclass=abc.ABCMeta): 26 | pass 27 | 28 | 29 | class SSEResult(SSEObject, metaclass=abc.ABCMeta): 30 | @abc.abstractmethod 31 | def get_result_list(self) -> list: 32 | pass 33 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/12 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /test/test_database_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_database_utils.py 7 | @time: 2022/03/13 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import os 14 | import unittest 15 | 16 | from toolkit.database_utils import parse_identifiers_from_block_given_identifier_size, partition_identifiers_to_blocks 17 | 18 | 19 | def fake_identifiers(identifier_size: int, identifier_count: int) -> list: 20 | return [os.urandom(identifier_size) for _ in range(identifier_count)] 21 | 22 | 23 | class TestDatabaseUtils(unittest.TestCase): 24 | def test_identifiers_partition(self): 25 | identifier_count = 2000 26 | identifier_size = 8 27 | identifier_count_in_one_block = 64 28 | 29 | identifier_list = fake_identifiers(identifier_size, identifier_count) 30 | block_list = list( 31 | partition_identifiers_to_blocks(identifier_list, identifier_count_in_one_block, identifier_size)) 32 | 33 | parse_result = [] 34 | for block in block_list: 35 | parse_result.extend(parse_identifiers_from_block_given_identifier_size(block, identifier_size)) 36 | self.assertListEqual(identifier_list, parse_result) 37 | 38 | def test_identifiers_partition_given_block_size(self): 39 | identifier_count = 2000 40 | identifier_size = 8 41 | identifier_count_in_one_block = 64 42 | block_size = identifier_count_in_one_block * identifier_size + 2 43 | 44 | identifier_list = fake_identifiers(identifier_size, identifier_count) 45 | block_list = list( 46 | partition_identifiers_to_blocks(identifier_list, identifier_count_in_one_block, identifier_size, 47 | block_size_bytes=block_size)) 48 | 49 | parse_result = [] 50 | for block in block_list: 51 | parse_result.extend(parse_identifiers_from_block_given_identifier_size(block, identifier_size)) 52 | self.assertListEqual(identifier_list, parse_result) 53 | -------------------------------------------------------------------------------- /test/test_fpe.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_fpe.py 7 | @time: 2022/03/12 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | from test.tools.faker import generate_random_bitset 16 | from toolkit.symmetric_encryption.fpe import BitwiseFFX 17 | 18 | 19 | class TestFPE(unittest.TestCase): 20 | def test_fpe_correctness(self): 21 | fpe = BitwiseFFX() 22 | keys = [ 23 | b"JezaChen", 24 | b"Sun yat-sen university", 25 | b"\x00\xff\x01abccjkj\x11" 26 | ] 27 | test_num_per_bit_len = 20 28 | bit_len_to_test = [10, 20, 21, 40, 43, 50, 100, 200, 1024, 1033, 2047, 2048] 29 | for key in keys: 30 | for bit_len in bit_len_to_test: 31 | for _ in range(test_num_per_bit_len): 32 | xbits = generate_random_bitset(bit_len) 33 | self.assertEqual(fpe.decrypt(key, fpe.encrypt(key, xbits)), xbits) 34 | -------------------------------------------------------------------------------- /test/test_sse_schemes/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/14 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_ANSS16_Scheme3.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_ANSS16_Scheme3.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.ANSS16.Scheme3.config 16 | from schemes.ANSS16.Scheme3.config import PiConfig 17 | from schemes.ANSS16.Scheme3.construction import Pi 18 | from schemes.ANSS16.Scheme3.structures import PiKey, PiToken, PiEncryptedDatabase, PiResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPi(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.ANSS16.Scheme3.config.DEFAULT_CONFIG 34 | 35 | scheme = Pi(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.ANSS16.Scheme3.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = Pi(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(db[keyword], result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.ANSS16.Scheme3.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = Pi(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("ANSS16.Scheme3") 84 | self.assertEqual(loader.SSEScheme, Pi) 85 | self.assertEqual(loader.SSEConfig, PiConfig) 86 | self.assertEqual(loader.SSEKey, PiKey) 87 | self.assertEqual(loader.SSEToken, PiToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.ANSS16.Scheme3.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse( 97 | TEST_KEYWORD_SIZE, 98 | config_dict.get("param_identifier_size"), 99 | keyword_count, 100 | db_w_size_range=(1, 200)) 101 | 102 | scheme = Pi(config_dict) 103 | key = scheme.KeyGen() 104 | self.assertEqual(key, PiKey.deserialize(key.serialize(), scheme.config)) 105 | 106 | encrypted_index = scheme.EDBSetup(key, db) 107 | self.assertEqual(encrypted_index, 108 | PiEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 109 | 110 | for keyword in db: 111 | token = scheme.TokenGen(key, keyword) 112 | self.assertEqual(token, 113 | PiToken.deserialize(token.serialize(), 114 | scheme.config)) 115 | result = scheme.Search(encrypted_index, token) 116 | self.assertEqual(result, 117 | PiResult.deserialize(result.serialize(), 118 | scheme.config)) 119 | 120 | self.assertEqual(db[keyword], result.result) 121 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CGKO06_SSE2.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CGKO06_SSE2.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.CGKO06.SSE2.config 16 | from schemes.CGKO06.SSE2.config import SSE2Config 17 | from schemes.CGKO06.SSE2.construction import SSE2 18 | from schemes.CGKO06.SSE2.structures import SSE2Token, SSE2EncryptedDatabase, SSE2Result, SSE2Key 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | 22 | class TestSSE2(unittest.TestCase): 23 | 24 | def test_method_correctness_simple_version(self): 25 | db = { 26 | b"China": [b"12345678", b"23221233", b"23421232"], 27 | b"Ukraine": 28 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 29 | } 30 | 31 | config_dict = schemes.CGKO06.SSE2.config.DEFAULT_CONFIG 32 | schemes.CGKO06.SSE2.config.scan_database_and_update_config_dict( 33 | config_dict, database=db) 34 | 35 | scheme = SSE2(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 100 45 | 46 | config_dict = schemes.CGKO06.SSE2.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | config_dict["param_l"], config_dict["param_identifier_size"], 50 | keyword_count) 51 | 52 | schemes.CGKO06.SSE2.config.scan_database_and_update_config_dict( 53 | config_dict, database=db) 54 | 55 | scheme = SSE2(config_dict) 56 | key = scheme._Gen() 57 | 58 | encrypted_index = scheme._Enc(key, db) 59 | for keyword in db: 60 | token = scheme._Trap(key, keyword) 61 | result = scheme._Search(encrypted_index, token) 62 | self.assertEqual(db[keyword], result.result) 63 | 64 | def test_interface_correctness(self): 65 | keyword_count = 100 66 | 67 | config_dict = schemes.CGKO06.SSE2.config.DEFAULT_CONFIG 68 | 69 | db = fake_db_for_inverted_index_based_sse( 70 | config_dict["param_l"], config_dict["param_identifier_size"], 71 | keyword_count) 72 | 73 | schemes.CGKO06.SSE2.config.scan_database_and_update_config_dict( 74 | config_dict, database=db) 75 | 76 | scheme = SSE2(config_dict) 77 | key = scheme.KeyGen() 78 | encrypted_index = scheme.EDBSetup(key, db) 79 | for keyword in db: 80 | token = scheme.TokenGen(key, keyword) 81 | result = scheme.Search(encrypted_index, token) 82 | self.assertEqual(db[keyword], result.result) 83 | 84 | def test_module_loader(self): 85 | loader = schemes.load_sse_module("CGKO06.SSE2") 86 | self.assertEqual(loader.SSEScheme, SSE2) 87 | self.assertEqual(loader.SSEConfig, SSE2Config) 88 | self.assertEqual(loader.SSEKey, SSE2Key) 89 | self.assertEqual(loader.SSEToken, SSE2Token) 90 | self.assertEqual(loader.SSEEncryptedDatabase, SSE2EncryptedDatabase) 91 | self.assertEqual(loader.SSEResult, SSE2Result) 92 | 93 | def test_structure_serialization(self): 94 | keyword_count = 10 95 | 96 | config_dict = schemes.CGKO06.SSE2.config.DEFAULT_CONFIG 97 | 98 | db = fake_db_for_inverted_index_based_sse( 99 | config_dict["param_l"], config_dict["param_identifier_size"], 100 | keyword_count) 101 | schemes.CGKO06.SSE2.config.scan_database_and_update_config_dict( 102 | config_dict, database=db) 103 | 104 | scheme = SSE2(config_dict) 105 | key = scheme.KeyGen() 106 | self.assertEqual(key, SSE2Key.deserialize(key.serialize(), scheme.config)) 107 | 108 | encrypted_index = scheme.EDBSetup(key, db) 109 | self.assertEqual(encrypted_index, 110 | SSE2EncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 111 | 112 | for keyword in db: 113 | token = scheme.TokenGen(key, keyword) 114 | self.assertEqual(token, 115 | SSE2Token.deserialize(token.serialize(), 116 | scheme.config)) 117 | result = scheme.Search(encrypted_index, token) 118 | self.assertEqual(result, 119 | SSE2Result.deserialize(result.serialize(), 120 | scheme.config)) 121 | 122 | self.assertEqual(db[keyword], result.result) 123 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CJJ14_Pi2Lev.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CJJ14_Pi2Lev.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.CJJ14.Pi2Lev.config 16 | from schemes.CJJ14.Pi2Lev.config import Pi2LevConfig 17 | from schemes.CJJ14.Pi2Lev.construction import Pi2Lev 18 | from schemes.CJJ14.Pi2Lev.structures import Pi2LevKey, Pi2LevToken, Pi2LevEncryptedDatabase, Pi2LevResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPi2Lev(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.CJJ14.Pi2Lev.config.DEFAULT_CONFIG 34 | 35 | scheme = Pi2Lev(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.CJJ14.Pi2Lev.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = Pi2Lev(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(db[keyword], result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.CJJ14.Pi2Lev.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = Pi2Lev(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("CJJ14.Pi2Lev") 84 | self.assertEqual(loader.SSEScheme, Pi2Lev) 85 | self.assertEqual(loader.SSEConfig, Pi2LevConfig) 86 | self.assertEqual(loader.SSEKey, Pi2LevKey) 87 | self.assertEqual(loader.SSEToken, Pi2LevToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, Pi2LevEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, Pi2LevResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.CJJ14.Pi2Lev.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 97 | config_dict.get("param_identifier_size"), 98 | keyword_count, 99 | db_w_size_range=(1, 200)) 100 | 101 | scheme = Pi2Lev(config_dict) 102 | key = scheme.KeyGen() 103 | self.assertEqual(key, Pi2LevKey.deserialize(key.serialize(), scheme.config)) 104 | 105 | encrypted_index = scheme.EDBSetup(key, db) 106 | self.assertEqual(encrypted_index, 107 | Pi2LevEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 108 | 109 | for keyword in db: 110 | token = scheme.TokenGen(key, keyword) 111 | self.assertEqual(token, 112 | Pi2LevToken.deserialize(token.serialize(), 113 | scheme.config)) 114 | result = scheme.Search(encrypted_index, token) 115 | self.assertEqual(result, 116 | Pi2LevResult.deserialize(result.serialize(), 117 | scheme.config)) 118 | 119 | self.assertEqual(db[keyword], result.result) 120 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CJJ14_PiBas.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CJJ14_PiBas.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes 16 | import schemes.CJJ14.PiBas.config 17 | from schemes.CJJ14.PiBas.config import PiBasConfig 18 | from schemes.CJJ14.PiBas.construction import PiBas 19 | from schemes.CJJ14.PiBas.structures import PiBasKey, PiBasToken, PiBasEncryptedDatabase, PiBasResult 20 | from test.tools.faker import fake_db_for_inverted_index_based_sse 21 | 22 | TEST_KEYWORD_SIZE = 16 23 | TEST_FILE_ID_SIZE = 4 24 | 25 | 26 | class TestPiBas(unittest.TestCase): 27 | 28 | def test_method_correctness_simple_version(self): 29 | db = { 30 | b"China": [b"12345678", b"23221233", b"23421232"], 31 | b"Ukraine": 32 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 33 | } 34 | 35 | config_dict = schemes.CJJ14.PiBas.config.DEFAULT_CONFIG 36 | 37 | scheme = PiBas(config_dict) 38 | key = scheme._Gen() 39 | 40 | encrypted_index = scheme._Enc(key, db) 41 | token = scheme._Trap(key, b"China") 42 | result = scheme._Search(encrypted_index, token) 43 | self.assertEqual(db[b"China"], result.result) 44 | 45 | def test_method_correctness(self): 46 | keyword_count = 1000 47 | 48 | config_dict = schemes.CJJ14.PiBas.config.DEFAULT_CONFIG 49 | 50 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 51 | TEST_FILE_ID_SIZE, 52 | keyword_count, 53 | db_w_size_range=(1, 200)) 54 | 55 | scheme = PiBas(config_dict) 56 | key = scheme._Gen() 57 | 58 | encrypted_index = scheme._Enc(key, db) 59 | for keyword in db: 60 | token = scheme._Trap(key, keyword) 61 | result = scheme._Search(encrypted_index, token) 62 | self.assertEqual(db[keyword], result.result) 63 | 64 | def test_interface_correctness(self): 65 | keyword_count = 1000 66 | 67 | config_dict = schemes.CJJ14.PiBas.config.DEFAULT_CONFIG 68 | 69 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 70 | TEST_FILE_ID_SIZE, 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = PiBas(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("CJJ14.PiBas") 84 | self.assertEqual(loader.SSEScheme, PiBas) 85 | self.assertEqual(loader.SSEConfig, PiBasConfig) 86 | self.assertEqual(loader.SSEKey, PiBasKey) 87 | self.assertEqual(loader.SSEToken, PiBasToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiBasEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiBasResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.CJJ14.PiBas.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 97 | TEST_FILE_ID_SIZE, 98 | keyword_count, 99 | db_w_size_range=(1, 200)) 100 | 101 | scheme = PiBas(config_dict) 102 | key = scheme.KeyGen() 103 | self.assertEqual(key, PiBasKey.deserialize(key.serialize(), scheme.config)) 104 | 105 | encrypted_index = scheme.EDBSetup(key, db) 106 | self.assertEqual(encrypted_index, 107 | PiBasEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 108 | 109 | for keyword in db: 110 | token = scheme.TokenGen(key, keyword) 111 | self.assertEqual(token, 112 | PiBasToken.deserialize(token.serialize(), 113 | scheme.config)) 114 | result = scheme.Search(encrypted_index, token) 115 | self.assertEqual(result, 116 | PiBasResult.deserialize(result.serialize(), 117 | scheme.config)) 118 | 119 | self.assertEqual(db[keyword], result.result) 120 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CJJ14_PiPack.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CJJ14_PiPack.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.CJJ14.PiPack.config 16 | from schemes.CJJ14.PiPack.config import PiPackConfig 17 | from schemes.CJJ14.PiPack.construction import PiPack 18 | from schemes.CJJ14.PiPack.structures import PiPackKey, PiPackToken, PiPackEncryptedDatabase, PiPackResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPiPack(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.CJJ14.PiPack.config.DEFAULT_CONFIG 34 | 35 | scheme = PiPack(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.CJJ14.PiPack.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = PiPack(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(db[keyword], result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.CJJ14.PiPack.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = PiPack(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("CJJ14.PiPack") 84 | self.assertEqual(loader.SSEScheme, PiPack) 85 | self.assertEqual(loader.SSEConfig, PiPackConfig) 86 | self.assertEqual(loader.SSEKey, PiPackKey) 87 | self.assertEqual(loader.SSEToken, PiPackToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiPackEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiPackResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.CJJ14.PiPack.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 97 | config_dict.get("param_identifier_size"), 98 | keyword_count, 99 | db_w_size_range=(1, 200)) 100 | 101 | scheme = PiPack(config_dict) 102 | key = scheme.KeyGen() 103 | self.assertEqual(key, PiPackKey.deserialize(key.serialize(), scheme.config)) 104 | 105 | encrypted_index = scheme.EDBSetup(key, db) 106 | self.assertEqual(encrypted_index, 107 | PiPackEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 108 | 109 | for keyword in db: 110 | token = scheme.TokenGen(key, keyword) 111 | self.assertEqual(token, 112 | PiPackToken.deserialize(token.serialize(), 113 | scheme.config)) 114 | result = scheme.Search(encrypted_index, token) 115 | self.assertEqual(result, 116 | PiPackResult.deserialize(result.serialize(), 117 | scheme.config)) 118 | 119 | self.assertEqual(db[keyword], result.result) 120 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CJJ14_PiPtr.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CJJ14_PiPtr.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.CJJ14.PiPtr.config 16 | from schemes.CJJ14.PiPtr.config import PiPtrConfig 17 | from schemes.CJJ14.PiPtr.construction import PiPtr 18 | from schemes.CJJ14.PiPtr.structures import PiPtrKey, PiPtrToken, PiPtrEncryptedDatabase, PiPtrResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPiPtr(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.CJJ14.PiPtr.config.DEFAULT_CONFIG 34 | 35 | scheme = PiPtr(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.CJJ14.PiPtr.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = PiPtr(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(db[keyword], result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.CJJ14.PiPtr.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = PiPtr(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("CJJ14.PiPtr") 84 | self.assertEqual(loader.SSEScheme, PiPtr) 85 | self.assertEqual(loader.SSEConfig, PiPtrConfig) 86 | self.assertEqual(loader.SSEKey, PiPtrKey) 87 | self.assertEqual(loader.SSEToken, PiPtrToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiPtrEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiPtrResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.CJJ14.PiPtr.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse(TEST_KEYWORD_SIZE, 97 | config_dict.get("param_identifier_size"), 98 | keyword_count, 99 | db_w_size_range=(1, 200)) 100 | 101 | scheme = PiPtr(config_dict) 102 | key = scheme.KeyGen() 103 | self.assertEqual(key, PiPtrKey.deserialize(key.serialize(), scheme.config)) 104 | 105 | encrypted_index = scheme.EDBSetup(key, db) 106 | self.assertEqual(encrypted_index, 107 | PiPtrEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 108 | 109 | for keyword in db: 110 | token = scheme.TokenGen(key, keyword) 111 | self.assertEqual(token, 112 | PiPtrToken.deserialize(token.serialize(), 113 | scheme.config)) 114 | result = scheme.Search(encrypted_index, token) 115 | self.assertEqual(result, 116 | PiPtrResult.deserialize(result.serialize(), 117 | scheme.config)) 118 | 119 | self.assertEqual(db[keyword], result.result) 120 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_CT14_Pi.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_CT14_Pi.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.CT14.Pi.config 16 | from schemes.CT14.Pi.config import PiConfig 17 | from schemes.CT14.Pi.construction import Pi 18 | from schemes.CT14.Pi.structures import PiKey, PiToken, PiEncryptedDatabase, PiResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPi(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.CT14.Pi.config.DEFAULT_CONFIG 34 | 35 | scheme = Pi(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(db[b"China"], result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.CT14.Pi.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = Pi(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(db[keyword], result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.CT14.Pi.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = Pi(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(db[keyword], result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("CT14.Pi") 84 | self.assertEqual(loader.SSEScheme, Pi) 85 | self.assertEqual(loader.SSEConfig, PiConfig) 86 | self.assertEqual(loader.SSEKey, PiKey) 87 | self.assertEqual(loader.SSEToken, PiToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.CT14.Pi.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse( 97 | TEST_KEYWORD_SIZE, 98 | config_dict.get("param_identifier_size"), 99 | keyword_count, 100 | db_w_size_range=(1, 200)) 101 | 102 | scheme = Pi(config_dict) 103 | key = scheme.KeyGen() 104 | self.assertEqual(key, PiKey.deserialize(key.serialize(), scheme.config)) 105 | 106 | encrypted_index = scheme.EDBSetup(key, db) 107 | self.assertEqual(encrypted_index, 108 | PiEncryptedDatabase.deserialize(encrypted_index.serialize(), scheme.config)) 109 | 110 | for keyword in db: 111 | token = scheme.TokenGen(key, keyword) 112 | self.assertEqual(token, 113 | PiToken.deserialize(token.serialize(), 114 | scheme.config)) 115 | result = scheme.Search(encrypted_index, token) 116 | self.assertEqual(result, 117 | PiResult.deserialize(result.serialize(), 118 | scheme.config)) 119 | 120 | self.assertEqual(db[keyword], result.result) 121 | -------------------------------------------------------------------------------- /test/test_sse_schemes/test_DP17_Pi.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: test_DP17_Pi.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import unittest 14 | 15 | import schemes.DP17.Pi.config 16 | from schemes.DP17.Pi.config import PiConfig 17 | from schemes.DP17.Pi.construction import Pi 18 | from schemes.DP17.Pi.structures import PiKey, PiToken, PiEncryptedDatabase, PiResult 19 | from test.tools.faker import fake_db_for_inverted_index_based_sse 20 | 21 | TEST_KEYWORD_SIZE = 16 22 | 23 | 24 | class TestPi(unittest.TestCase): 25 | 26 | def test_method_correctness_simple_version(self): 27 | db = { 28 | b"China": [b"12345678", b"23221233", b"23421232"], 29 | b"Ukraine": 30 | [b"\x00\x00az\x02\x03sc", b"\x00\x00\x00\x00\x01\x00\x02\x01"] 31 | } 32 | 33 | config_dict = schemes.DP17.Pi.config.DEFAULT_CONFIG 34 | 35 | scheme = Pi(config_dict) 36 | key = scheme._Gen() 37 | 38 | encrypted_index = scheme._Enc(key, db) 39 | token = scheme._Trap(key, b"China") 40 | result = scheme._Search(encrypted_index, token) 41 | self.assertEqual(set(db[b"China"]), result.result) 42 | 43 | def test_method_correctness(self): 44 | keyword_count = 1000 45 | 46 | config_dict = schemes.DP17.Pi.config.DEFAULT_CONFIG 47 | 48 | db = fake_db_for_inverted_index_based_sse( 49 | TEST_KEYWORD_SIZE, 50 | config_dict.get("param_identifier_size"), 51 | keyword_count, 52 | db_w_size_range=(1, 200)) 53 | 54 | scheme = Pi(config_dict) 55 | key = scheme._Gen() 56 | 57 | encrypted_index = scheme._Enc(key, db) 58 | for keyword in db: 59 | token = scheme._Trap(key, keyword) 60 | result = scheme._Search(encrypted_index, token) 61 | self.assertEqual(set(db[keyword]), result.result) 62 | 63 | def test_interface_correctness(self): 64 | keyword_count = 1000 65 | 66 | config_dict = schemes.DP17.Pi.config.DEFAULT_CONFIG 67 | 68 | db = fake_db_for_inverted_index_based_sse( 69 | TEST_KEYWORD_SIZE, 70 | config_dict.get("param_identifier_size"), 71 | keyword_count, 72 | db_w_size_range=(1, 200)) 73 | 74 | scheme = Pi(config_dict) 75 | key = scheme.KeyGen() 76 | encrypted_index = scheme.EDBSetup(key, db) 77 | for keyword in db: 78 | token = scheme.TokenGen(key, keyword) 79 | result = scheme.Search(encrypted_index, token) 80 | self.assertEqual(set(db[keyword]), result.result) 81 | 82 | def test_module_loader(self): 83 | loader = schemes.load_sse_module("DP17.Pi") 84 | self.assertEqual(loader.SSEScheme, Pi) 85 | self.assertEqual(loader.SSEConfig, PiConfig) 86 | self.assertEqual(loader.SSEKey, PiKey) 87 | self.assertEqual(loader.SSEToken, PiToken) 88 | self.assertEqual(loader.SSEEncryptedDatabase, PiEncryptedDatabase) 89 | self.assertEqual(loader.SSEResult, PiResult) 90 | 91 | def test_structure_serialization(self): 92 | keyword_count = 10 93 | 94 | config_dict = schemes.DP17.Pi.config.DEFAULT_CONFIG 95 | 96 | db = fake_db_for_inverted_index_based_sse( 97 | TEST_KEYWORD_SIZE, 98 | config_dict.get("param_identifier_size"), 99 | keyword_count, 100 | db_w_size_range=(1, 200)) 101 | 102 | scheme = Pi(config_dict) 103 | key = scheme.KeyGen() 104 | self.assertEqual(key, PiKey.deserialize(key.serialize(), 105 | scheme.config)) 106 | 107 | encrypted_index = scheme.EDBSetup(key, db) 108 | self.assertEqual( 109 | encrypted_index, 110 | PiEncryptedDatabase.deserialize(encrypted_index.serialize(), 111 | scheme.config)) 112 | 113 | for keyword in db: 114 | token = scheme.TokenGen(key, keyword) 115 | self.assertEqual( 116 | token, PiToken.deserialize(token.serialize(), scheme.config)) 117 | result = scheme.Search(encrypted_index, token) 118 | self.assertEqual( 119 | result, PiResult.deserialize(result.serialize(), 120 | scheme.config)) 121 | 122 | self.assertEqual(set(db[keyword]), result.result) 123 | -------------------------------------------------------------------------------- /test/tools/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/06/01 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /test/tools/faker.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: tools.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import os 14 | import random 15 | 16 | from toolkit.bits import Bitset 17 | 18 | __all__ = ["fake_db_for_inverted_index_based_sse", "generate_random_bitset"] 19 | 20 | 21 | def fake_db_for_inverted_index_based_sse(fixed_keyword_size: int, 22 | fixed_file_id_size: int, 23 | distinct_keyword_count: int, 24 | db_w_size_range: tuple = (1, 10), 25 | ): 26 | """ 27 | Generate a test database for inverted index-based SSE schemes 28 | Note that the sizes of keywords and file identifiers are fixed 29 | :param fixed_keyword_size: (bytes) the size of keyword 30 | :param fixed_file_id_size: (bytes) the size of file identifier 31 | :param distinct_keyword_count: the number of distinct keywords in the fake DB 32 | :param db_w_size_range: The size range of DB(w) corresponding to a single keyword, default value is [1, 10] 33 | :return: 34 | """ 35 | db = {} 36 | for _ in range(distinct_keyword_count): 37 | keyword = os.urandom(fixed_keyword_size) 38 | db[keyword] = [] 39 | for _ in range(random.randint(*db_w_size_range)): 40 | db[keyword].append(os.urandom(fixed_file_id_size)) 41 | return db 42 | 43 | 44 | def generate_random_bitset(bit_len): 45 | num = random.getrandbits(bit_len) 46 | return Bitset(num, length=bit_len) 47 | -------------------------------------------------------------------------------- /toolkit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JezaChen/SSEPy/33ba96ac72d5c6ab565abaafd2b4a52ce25c51d1/toolkit/__init__.py -------------------------------------------------------------------------------- /toolkit/bits_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: bits_utils.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import typing 14 | 15 | from toolkit.bits import Bitset 16 | 17 | 18 | def half_bits(xbits: typing.Union[int, Bitset]) -> (Bitset, Bitset): 19 | """Get the first half of xbits and the second half of bits, 20 | where the length of the two bits are (len(xbits) + 1) // 2 21 | """ 22 | if isinstance(xbits, int): 23 | xbits = Bitset(xbits) 24 | 25 | half_len = (len(xbits) + 1) // 2 26 | right_half = xbits.get_lower_bits(half_len) 27 | left_half = xbits.get_higher_bits(len(xbits) - half_len) 28 | if len(left_half) < half_len: # half_len - 1 29 | left_half.length = half_len 30 | 31 | return left_half, right_half 32 | 33 | 34 | def half_bits_not_padding(xbits: typing.Union[int, Bitset]) -> (Bitset, Bitset): 35 | """Get the first half of xbits and the second half of bits, 36 | where the length of the two bits are not equal when len(xbits) % 2 == 1 37 | """ 38 | if isinstance(xbits, int): 39 | xbits = Bitset(xbits) 40 | 41 | half_len = (len(xbits) + 1) // 2 42 | right_half = xbits.get_lower_bits(half_len) 43 | left_half = xbits.get_higher_bits(len(xbits) - half_len) 44 | 45 | return left_half, right_half 46 | -------------------------------------------------------------------------------- /toolkit/bytes_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: bytes_utils.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @software: PyCharm 10 | @description: Collection of tools for byte manipulation 11 | """ 12 | import itertools 13 | 14 | 15 | def bytes_xor(a: bytes, b: bytes): 16 | """Bytes Xor Operation: a xor b""" 17 | result = bytearray(a) 18 | for i, b_byte in enumerate(b): 19 | result[i] ^= b_byte 20 | return bytes(result) 21 | 22 | 23 | def int_to_bytes(x: int, output_len: int = -1) -> bytes: 24 | if output_len == -1: 25 | output_len = (x.bit_length() + 7) // 8 26 | return x.to_bytes(output_len, 'big') 27 | 28 | 29 | def int_from_bytes(xbytes: bytes) -> int: 30 | return int.from_bytes(xbytes, 'big') 31 | 32 | 33 | def add_leading_zeros(xbytes: bytes, output_len: int): 34 | return b'\x00' * max(output_len - len(xbytes), 0) + xbytes 35 | 36 | 37 | def split_bytes_given_slice_len(xbytes: bytes, slice_len_list: list) -> list: 38 | if len(xbytes) != sum(slice_len for slice_len in slice_len_list): 39 | raise ValueError("Length mismatch, please ensure that the length of xbytes is equal to " 40 | "the sum of the individual values of slice_len_list") 41 | result = [] 42 | c = 0 43 | slice_len_accumulation = itertools.accumulate(slice_len_list) 44 | while c != len(xbytes): 45 | next_c = next(slice_len_accumulation) 46 | result.append(xbytes[c: next_c]) 47 | c = next_c 48 | return result 49 | 50 | 51 | class BytesConverter: 52 | """Convert bytes to given format""" 53 | supported_format = [ 54 | "int", "hex", "raw", "utf8" 55 | ] 56 | 57 | @staticmethod 58 | def bytes_to_int(xbytes: bytes): 59 | return int_from_bytes(xbytes) 60 | 61 | @staticmethod 62 | def bytes_to_hex(xbytes: bytes): 63 | return xbytes.hex() 64 | 65 | @staticmethod 66 | def bytes_to_raw(xbytes: bytes): 67 | return xbytes 68 | 69 | @staticmethod 70 | def bytes_to_utf8(xbytes: bytes): 71 | return xbytes.decode(encoding="utf-8") 72 | 73 | @staticmethod 74 | def convert_bytes(xbytes: bytes, output_format: str): 75 | if not hasattr(BytesConverter, "bytes_to_" + output_format): 76 | raise ValueError(f"Unsupported format {output_format}") 77 | return getattr(BytesConverter, "bytes_to_" + output_format)(xbytes) 78 | -------------------------------------------------------------------------------- /toolkit/config_manager.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: config_manager.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import json 14 | 15 | 16 | def read_config(config_file_path: str) -> dict: 17 | with open(config_file_path, "r") as f: 18 | return json.load(f) 19 | 20 | 21 | def write_config(config: dict, config_file_path: str): 22 | with open(config_file_path, "w") as f: 23 | return json.dump(config, f) 24 | -------------------------------------------------------------------------------- /toolkit/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: constants.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @software: PyCharm 10 | @description: 11 | """ 12 | 13 | LENGTH_UNLIMITED = -1 14 | LENGTH_NOT_GIVEN = 0 15 | -------------------------------------------------------------------------------- /toolkit/data_structures/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/05/07 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /toolkit/data_structures/extended_bytes.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: extended_bytes.py 7 | @time: 2022/05/07 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import operator 14 | from collections.abc import Sequence 15 | 16 | 17 | class ExtendedBytes(Sequence): 18 | """ Extended Version of bytes 19 | """ 20 | 21 | def __init__(self, original_bytes: bytes): 22 | self.__original_bytes = bytes(original_bytes) # initialize __original_bytes from bytes(...) 23 | 24 | def __getitem__(self, key): 25 | if isinstance(key, slice): 26 | cls = type(self) 27 | return cls(self.__original_bytes[key]) # build another ExtendedBytes 28 | index = operator.index(key) 29 | return self.__original_bytes[index] 30 | 31 | def __len__(self): 32 | return len(self.__original_bytes) 33 | 34 | def __getattr__(self, item): 35 | return getattr(self.__original_bytes, item) 36 | 37 | def __iter__(self): 38 | return iter(self.__original_bytes) 39 | 40 | def __repr__(self) -> str: 41 | class_name = type(self).__name__ 42 | return '{}({})'.format(class_name, self.__original_bytes) 43 | 44 | def __eq__(self, other): 45 | if isinstance(other, ExtendedBytes): 46 | return self.__original_bytes == other.__original_bytes 47 | else: 48 | return NotImplemented 49 | 50 | def concat(self, o): 51 | print("concat") 52 | -------------------------------------------------------------------------------- /toolkit/database_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: database_utils.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | Database related utility functions, 13 | such as getting the number of individual keywords, database size, etc. 14 | """ 15 | 16 | 17 | def get_total_size(db: dict): 18 | """Get the total size of the database N""" 19 | return sum(len(identifier_list) for identifier_list in db.values()) 20 | 21 | 22 | def get_distinct_keyword_count(db: dict): 23 | """Get the number of distinct keywords in the database""" 24 | return len(db) 25 | 26 | 27 | def get_distinct_file_count(db: dict): 28 | """Get the number of distinct file identifiers in the database""" 29 | file_set = set() 30 | for identifier_list in db.values(): 31 | file_set.update(identifier_list) 32 | return len(file_set) 33 | 34 | 35 | def partition_identifiers_to_blocks(identifier_list: list, 36 | entry_count_in_one_block: int, 37 | identifier_size: int, 38 | block_size_bytes: int = 0): 39 | """ 40 | Store multiple file identifiers in blocks, where each block is a byte string. 41 | :param identifier_list: A list of file identifiers 42 | :param entry_count_in_one_block: Number of file identifiers contained on a block 43 | :param identifier_size: Size of the file identifier, in bytes 44 | :param block_size_bytes: Size of the block, in bytes 45 | :return: 46 | todo use toolkit.list_utils.chunks method 47 | """ 48 | if block_size_bytes == 0: 49 | block_size_bytes = entry_count_in_one_block * identifier_size 50 | 51 | if block_size_bytes < entry_count_in_one_block * identifier_size: 52 | raise ValueError( 53 | "parameter block_size_bytes should be greater than or equal to " 54 | "entry_count_in_one_block * identifier_size") 55 | 56 | for i in range(0, len(identifier_list), entry_count_in_one_block): 57 | block = b''.join(identifier_list[i:i + entry_count_in_one_block]) 58 | if len(block) < block_size_bytes: 59 | block += b'\x00' * (block_size_bytes - len(block)) 60 | yield block 61 | 62 | 63 | def parse_identifiers_from_block_given_identifier_size(block: bytes, 64 | identifier_size: int): 65 | """Parses a list of file identifiers from a block, given the file identifier size.""" 66 | result = [] 67 | for i in range(0, len(block), identifier_size): 68 | identifier = block[i:i + identifier_size] 69 | if identifier == b'\x00' * len(identifier): 70 | break 71 | result.append(identifier) 72 | return result 73 | 74 | 75 | def parse_identifiers_from_block_given_entry_count_in_one_block( 76 | block: bytes, entry_count_in_one_block: int): 77 | """Parses a list of file identifiers from a block, given the number of file identifiers in the block.""" 78 | identifier_size = len(block) // entry_count_in_one_block 79 | return parse_identifiers_from_block_given_identifier_size( 80 | block, identifier_size) 81 | 82 | 83 | def convert_database_keyword_to_bytes(db: dict, encoding="utf-8"): 84 | """Make sure that all keywords in db are strings and all values are hex-strings. """ 85 | result = {} 86 | for keyword in db: 87 | keyword_bytes = bytes(keyword, encoding=encoding) 88 | identifier_bytes_list = [] 89 | for identifier in db[keyword]: 90 | identifier_bytes_list.append(bytes.fromhex(identifier)) 91 | result[keyword_bytes] = identifier_bytes_list 92 | return result 93 | 94 | 95 | if __name__ == '__main__': 96 | test_db = { 97 | b"a": [1, 2, 3, 4, 6], 98 | b"b": [1, 4, 5, 6, 7], 99 | b"c": [1, 2, 3, 9, 11] 100 | } 101 | print(get_total_size(test_db)) 102 | print(get_distinct_keyword_count(test_db)) 103 | print(get_distinct_file_count(test_db)) 104 | -------------------------------------------------------------------------------- /toolkit/hash.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: hash.py 7 | @time: 2022/03/25 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import abc 14 | import functools 15 | import hashlib 16 | 17 | from toolkit.bytes_utils import int_to_bytes 18 | from toolkit.constants import LENGTH_NOT_GIVEN 19 | 20 | 21 | class AbstractHash(metaclass=abc.ABCMeta): 22 | 23 | def __init__(self, *, output_length: int): 24 | self.output_length = output_length 25 | 26 | def __call__(self, message: bytes) -> bytes: 27 | raise NotImplementedError("Class AbstractHash is an abstract class.") 28 | 29 | 30 | class HashlibHashVariableOutputLengthWrapper(AbstractHash): 31 | """Wrap hash functions of hashlib to support variable length output 32 | """ 33 | 34 | def __init__(self, 35 | *, 36 | output_length: int = LENGTH_NOT_GIVEN, 37 | hash_func_name: str): 38 | super(HashlibHashVariableOutputLengthWrapper, 39 | self).__init__(output_length=output_length) 40 | if hash_func_name.lower() not in hashlib.algorithms_available: 41 | raise ValueError( 42 | "Hash type {} is not supported".format(hash_func_name)) 43 | 44 | hash_func = functools.partial(hashlib.new, hash_func_name) 45 | if output_length == LENGTH_NOT_GIVEN: 46 | self.output_length = hash_func(b"").digest_size 47 | 48 | self.hash_func_name = hash_func_name 49 | self.hash_func = hash_func 50 | 51 | def _ctr_expand(self, message: bytes): 52 | """Extended output length using counter mode""" 53 | result = b'' 54 | c = 1 55 | while len(result) < self.output_length: 56 | result += self.hash_func(message + int_to_bytes(c)).digest() 57 | c += 1 58 | return result[:self.output_length] 59 | 60 | def __call__(self, message: bytes) -> bytes: 61 | if self.hash_func_name in {"shake_128", "shake_256"}: 62 | return self.hash_func(message).digest(self.output_length) 63 | return self._ctr_expand(message) 64 | 65 | 66 | __builtin_hash_functions_cache = {} 67 | 68 | 69 | def get_hash_implementation(hash_function_name: str): 70 | cache = __builtin_hash_functions_cache 71 | hash_function = cache.get(hash_function_name.lower()) 72 | if hash_function is not None: 73 | return hash_function 74 | 75 | if hash_function_name.lower() in hashlib.algorithms_available: 76 | cache[hash_function_name.lower()] = functools.partial( 77 | HashlibHashVariableOutputLengthWrapper, 78 | hash_func_name=hash_function_name) 79 | 80 | hash_function = cache.get(hash_function_name.lower()) 81 | if hash_function is not None: 82 | return hash_function 83 | 84 | raise ValueError('unsupported Hash type ' + hash_function_name) 85 | -------------------------------------------------------------------------------- /toolkit/list_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: list_utils.py 7 | @time: 2022/03/29 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | 15 | def chunks(lst, n): 16 | """Yield successive n-sized chunks from lst.""" 17 | for i in range(0, len(lst), n): 18 | yield lst[i:i + n] 19 | -------------------------------------------------------------------------------- /toolkit/logger/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/18 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | -------------------------------------------------------------------------------- /toolkit/logger/logger.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: logger.py 7 | @time: 2022/03/18 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: logging 12 | """ 13 | import logging 14 | import pathlib 15 | import time 16 | 17 | __logger_cache = {} 18 | _PROGRAM_DIR_PATH = pathlib.Path.home().joinpath(".sse") 19 | _LOG_DIR_PATH = _PROGRAM_DIR_PATH.joinpath("log") 20 | if not _LOG_DIR_PATH.exists(): 21 | _LOG_DIR_PATH.mkdir(parents=True) 22 | 23 | 24 | def getSSELogger(logger_name="sse", console_log_level=logging.INFO, file_log_level=logging.INFO): 25 | if logger_name in __logger_cache: 26 | return __logger_cache[logger_name] 27 | 28 | logger = logging.getLogger(logger_name) 29 | logger.setLevel(logging.INFO) 30 | 31 | # 1. Console 32 | sh = logging.StreamHandler() 33 | sh.setLevel(console_log_level) 34 | # 2. File 35 | rq = time.strftime('%Y-%m-%d-%H-%M-%S', time.localtime((time.time()))) 36 | log_name = logger_name + '_' + rq + '.log' 37 | log_path = _LOG_DIR_PATH.joinpath(log_name) 38 | fh = logging.FileHandler(log_path) 39 | fh.setLevel(file_log_level) 40 | 41 | # Format 42 | formatter = logging.Formatter("[%(asctime)s][%(filename)s-->line:%(lineno)d][%(levelname)s] %(message)s") 43 | sh.setFormatter(formatter) 44 | fh.setFormatter(formatter) 45 | 46 | # add handler 47 | logger.addHandler(fh) 48 | logger.addHandler(sh) 49 | 50 | __logger_cache[logger_name] = logger 51 | return logger 52 | -------------------------------------------------------------------------------- /toolkit/prf/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: Pseudorandom Function Implementation 12 | @chinese_description: 伪随机函数实现 13 | """ 14 | __builtin_constructor_cache = {} 15 | 16 | 17 | def get_prf_implementation(prf_name: str): 18 | cache = __builtin_constructor_cache 19 | prf = cache.get(prf_name.lower()) 20 | if prf is not None: 21 | return prf 22 | 23 | try: 24 | if prf_name.lower() in {'hmacprf', 'hmac-prf', 'hmac_prf'}: 25 | from toolkit.prf.hmac_prf import HmacPRF 26 | cache['hmacprf'] = cache['hmac-prf'] = cache['hmac_prf'] = HmacPRF 27 | except ImportError: 28 | pass # no extension module, this hash is unsupported. 29 | 30 | prf = cache.get(prf_name.lower()) 31 | if prf is not None: 32 | return prf 33 | 34 | raise ValueError('unsupported PRF type ' + prf_name) 35 | -------------------------------------------------------------------------------- /toolkit/prf/abstraction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: abstraction.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import abc 14 | 15 | 16 | class AbstractPRF(metaclass=abc.ABCMeta): 17 | def __init__(self, *, output_length: int, key_length: int, message_length: int): 18 | self.output_length = output_length 19 | self.key_length = key_length 20 | self.message_length = message_length 21 | 22 | def __call__(self, key: bytes, message: bytes) -> bytes: 23 | raise NotImplementedError("Class AbstractPRF is an abstract class.") -------------------------------------------------------------------------------- /toolkit/prf/hmac_prf.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: hmac_prf.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | import functools 15 | import hashlib 16 | import hmac 17 | 18 | from toolkit.constants import LENGTH_UNLIMITED, LENGTH_NOT_GIVEN 19 | from toolkit.prf.abstraction import AbstractPRF 20 | 21 | 22 | def _tls_p_hash(key: bytes, 23 | message: bytes, 24 | output_len: int, 25 | hash_func_name: "str" = "sha1") -> bytes: 26 | """ 27 | A data expansion function defined in section 5 of RFC 4346 (or section 5 of RFC 5246) 28 | In RFC 4346 (or RFC 5246), P_hash(secret, data) uses a single hash function to expand 29 | a secret and seed into an arbitrary quantity of output: 30 | :param key: the key of HMAC, the secret parameter of P_hash function 31 | :param message: the input message of HMAC, the data parameter of P_hash function 32 | :param output_len: the output length 33 | :return: bytes, the tag of the message 34 | """ 35 | if hash_func_name not in hashlib.algorithms_available: 36 | raise ValueError( 37 | "Hash type {} is not supported".format(hash_func_name)) 38 | 39 | hash_func = functools.partial(hmac.new, digestmod=hash_func_name) 40 | 41 | hash_len = hash_func(key, b"").digest_size 42 | n = (output_len + hash_len - 1) // hash_len 43 | 44 | res = b"" 45 | a = hash_func(key, message).digest() # A(1) 46 | 47 | while n > 0: 48 | res += hash_func(key, a + message).digest() 49 | a = hash_func(key, a).digest() 50 | n -= 1 51 | 52 | return res[:output_len] 53 | 54 | 55 | class HmacPRF(AbstractPRF): 56 | """The HMAC function being used as a PRF, described in NIST SP 800-35 Rev. 1.""" 57 | 58 | def __init__(self, 59 | *, 60 | output_length: int = LENGTH_NOT_GIVEN, 61 | key_length: int = LENGTH_UNLIMITED, 62 | message_length: int = LENGTH_UNLIMITED, 63 | hash_func_name: str = "sha1"): 64 | """ 65 | Constructor of HMAC-PRF 66 | :param output_length: The length of output values 67 | :param key_length: The length of keys, LENGTH_UNLIMITED represents no limit 68 | :param message_length: The length of message, LENGTH_UNLIMITED represents no limit 69 | """ 70 | super(HmacPRF, self).__init__(output_length=output_length, 71 | message_length=message_length, 72 | key_length=key_length) 73 | if hash_func_name not in hashlib.algorithms_available: 74 | raise ValueError( 75 | "Hash type {} is not supported".format(hash_func_name)) 76 | self.hash_func_name = hash_func_name 77 | if output_length == LENGTH_NOT_GIVEN: 78 | self.output_length = hmac.new( 79 | b"", b"", digestmod=self.hash_func_name).digest_size 80 | 81 | def __call__(self, key: bytes, message: bytes) -> bytes: 82 | if self.key_length != LENGTH_UNLIMITED and len(key) != self.key_length: 83 | raise ValueError( 84 | "The key length of the PRF does not meet the definition.") 85 | if self.message_length != LENGTH_UNLIMITED and len( 86 | message) != self.message_length: 87 | raise ValueError( 88 | "The message length of the PRF does not meet the definition") 89 | 90 | return _tls_p_hash(key, message, self.output_length, 91 | self.hash_func_name) 92 | -------------------------------------------------------------------------------- /toolkit/prp/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: Pseudorandom Permutation Implementation 12 | @chinese_description: 伪随机置换实现 13 | """ 14 | 15 | __builtin_constructor_cache = {} 16 | 17 | 18 | def get_prp_implementation(prp_name: str): 19 | cache = __builtin_constructor_cache 20 | prp = cache.get(prp_name.lower()) 21 | if prp is not None: 22 | return prp 23 | 24 | try: 25 | if prp_name.lower() in {'lubyrackoffprp', 'luby-rackoff-prp', 'luby_rackoff_prp'}: 26 | from .luby_rackoff_prp import LubyRackoffPRP 27 | cache['lubyrackoffprp'] = cache['luby-rackoff-prp'] = cache['luby_rackoff_prp'] = LubyRackoffPRP 28 | elif prp_name.lower() in {'hmaclubyrackoffprp', 'hmac-luby-rackoff-prp', 'hmac_luby_rackoff_prp'}: 29 | from .hmac_luby_rackoff_prp import HmacLubyRackoffPRP 30 | cache['hmaclubyrackoffprp'] = cache['hmac-luby-rackoff-prp'] = cache['hmac_luby_rackoff_prp'] = \ 31 | HmacLubyRackoffPRP 32 | elif prp_name.lower() in {'bitwise_fpe_prp', 'bitwise-fpe-prp', 'bitwisefpeprp'}: 33 | from .bitwise_fpe_prp import BitwiseFPEPRP 34 | cache['bitwise_fpe_prp'] = cache['bitwise-fpe-prp'] = cache['bitwisefpeprp'] = \ 35 | BitwiseFPEPRP 36 | except ImportError: 37 | pass # no extension module, this hash is unsupported. 38 | 39 | prp = cache.get(prp_name.lower()) 40 | if prp is not None: 41 | return prp 42 | 43 | raise ValueError('unsupported PRP type ' + prp_name) 44 | -------------------------------------------------------------------------------- /toolkit/prp/abstraction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: abstraction.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | """ 12 | import abc 13 | 14 | from toolkit.bits import Bitset 15 | 16 | 17 | class AbstractPRP(metaclass=abc.ABCMeta): 18 | def __init__(self, *, message_length: int, key_length: int): 19 | self.key_length = key_length 20 | self.message_length = message_length 21 | 22 | def __call__(self, key: bytes, message: bytes) -> bytes: 23 | raise NotImplementedError("Class AbstractPRP is an abstract class.") 24 | 25 | 26 | class AbstractBitwisePRP(metaclass=abc.ABCMeta): 27 | def __init__(self, *, message_bit_length: int, key_bit_length: int): 28 | self.key_bit_length = key_bit_length 29 | self.message_bit_length = message_bit_length 30 | 31 | def __call__(self, key: bytes, message: Bitset) -> Bitset: 32 | raise NotImplementedError("Class AbstractBitwisePRP is an abstract class.") 33 | -------------------------------------------------------------------------------- /toolkit/prp/bitwise_fpe_prp.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: bitwise_fpe_prp.py 7 | @time: 2022/03/12 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | from toolkit.bits import Bitset 14 | from toolkit.prp.abstraction import AbstractBitwisePRP 15 | from toolkit.symmetric_encryption.fpe import BitwiseFFX 16 | 17 | 18 | class BitwiseFPEPRP(AbstractBitwisePRP): 19 | """Luby-Rackoff Construction where its underlying prp is HMAC-PRP""" 20 | 21 | def __init__(self, *, message_bit_length: int, key_bit_length: int): 22 | super(BitwiseFPEPRP, self).__init__(message_bit_length=message_bit_length, 23 | key_bit_length=key_bit_length) 24 | self.underlying_fpe = BitwiseFFX() 25 | 26 | def __call__(self, key: Bitset, message: Bitset) -> Bitset: 27 | if len(key) != self.key_bit_length: 28 | raise ValueError("Key bit length mismatch for PRP.") 29 | if len(message) != self.message_bit_length: 30 | raise ValueError("Message(Input) bit length mismatch for PRP.") 31 | 32 | return self.underlying_fpe.encrypt(bytes(key), message) 33 | -------------------------------------------------------------------------------- /toolkit/prp/hmac_luby_rackoff_prp.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: hmac_luby_rackoff_prp.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | 14 | from toolkit.prf.hmac_prf import HmacPRF 15 | from toolkit.prp.abstraction import AbstractPRP 16 | from toolkit.prp.luby_rackoff_prp import LubyRackoffPRP 17 | 18 | 19 | class HmacLubyRackoffPRP(AbstractPRP): 20 | """Luby-Rackoff Construction where its underlying prp is HMAC-PRP""" 21 | 22 | def __init__(self, *, message_length: int, key_length: int, hash_func_name: "str" = "sha1"): 23 | super(HmacLubyRackoffPRP, self).__init__(message_length=message_length, key_length=key_length) 24 | if message_length % 2: 25 | raise ValueError("The message length needs to be divisible by 2.") 26 | if key_length % 3: 27 | raise ValueError("The key length needs to be divisible by 3.") 28 | underlying_prf = HmacPRF(output_length=message_length // 2, 29 | message_length=message_length // 2, 30 | key_length=key_length // 3, 31 | hash_func_name=hash_func_name) 32 | 33 | self.underlying_prp = LubyRackoffPRP(message_length=message_length, 34 | key_length=key_length, 35 | underlying_prf=underlying_prf) 36 | 37 | def __call__(self, key: bytes, message: bytes) -> bytes: 38 | return self.underlying_prp.__call__(key, message) 39 | -------------------------------------------------------------------------------- /toolkit/prp/luby_rackoff_prp.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: luby_rackoff_prp.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | from toolkit.bytes_utils import bytes_xor 14 | from toolkit.prf.abstraction import AbstractPRF 15 | from toolkit.prp.abstraction import AbstractPRP 16 | 17 | 18 | class LubyRackoffPRP(AbstractPRP): 19 | """ 20 | Luby-Rackoff Construction. To construct a PRP from a PRF 21 | @note: Instead of inheriting directly from the RubyRackoffPRP class, it is used here as a member 22 | """ 23 | 24 | def __init__(self, *, message_length: int, key_length: int, underlying_prf: AbstractPRF): 25 | super(LubyRackoffPRP, self).__init__(message_length=message_length, key_length=key_length) 26 | if underlying_prf.key_length * 3 != key_length: 27 | raise ValueError("The key length of the PRP needs to be 3 times the length of the underlying PRF key.") 28 | if underlying_prf.message_length != underlying_prf.output_length: 29 | raise ValueError("The input length of the PRF needs to be equal to the output length.") 30 | if underlying_prf.message_length * 2 != message_length: 31 | # This implies that the message length of this PRP needs to be an even number 32 | raise ValueError( 33 | "The input (output) length of the PRP needs to be equal to twice the input length of the PRF." 34 | ) 35 | 36 | self.underlying_prf = underlying_prf 37 | 38 | def __call__(self, key: bytes, message: bytes) -> bytes: 39 | if len(key) != self.key_length: 40 | raise ValueError("Key length mismatch for PRP.") 41 | if len(message) != self.message_length: 42 | raise ValueError("Message(Input) length mismatch for PRP.") 43 | 44 | curr_left, curr_right = message[:self.message_length // 2], message[self.message_length // 2:] 45 | # K0, K1, K2 46 | key_list = [key[i: i + self.key_length // 3] for i in range(0, self.key_length, self.key_length // 3)] 47 | 48 | for i in range(3): 49 | next_left, next_right = curr_right, bytes_xor(curr_left, self.underlying_prf(key_list[i], curr_right)) 50 | curr_left, curr_right = next_left, next_right 51 | 52 | return curr_left + curr_right 53 | -------------------------------------------------------------------------------- /toolkit/symmetric_encryption/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: __init__.py.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | __builtin_constructor_cache = {} 14 | 15 | 16 | def get_symmetric_encryption_implementation(se_name: str): 17 | cache = __builtin_constructor_cache 18 | se = cache.get(se_name.lower()) 19 | if se is not None: 20 | return se 21 | 22 | try: 23 | if se_name.lower() in {'aes-cbc', 'aes_cbc', 'aescbc'}: 24 | from .aes import AESxCBC 25 | cache['aes-cbc'] = cache['aes_cbc'] = cache['aescbc'] = AESxCBC 26 | except ImportError: 27 | pass # no extension module, this hash is unsupported. 28 | 29 | se = cache.get(se_name.lower()) 30 | if se is not None: 31 | return se 32 | 33 | raise ValueError('unsupported symmetric encryption type ' + se_name) 34 | -------------------------------------------------------------------------------- /toolkit/symmetric_encryption/abstraction.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: abstraction.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import abc 14 | 15 | 16 | class AbstractSymmetricEncryption(metaclass=abc.ABCMeta): 17 | def __init__(self, *, cipher_length: int, key_length: int, message_length: int): 18 | self.cipher_length = cipher_length 19 | self.key_length = key_length 20 | self.message_length = message_length 21 | 22 | def KeyGen(self) -> bytes: 23 | pass 24 | 25 | def Encrypt(self, key: bytes, message: bytes) -> bytes: 26 | pass 27 | 28 | def Decrypt(self, key: bytes, cipher_text: bytes) -> bytes: 29 | pass 30 | -------------------------------------------------------------------------------- /toolkit/symmetric_encryption/aes.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: aes.py 7 | @time: 2022/03/10 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: 12 | """ 13 | import os 14 | 15 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes 16 | 17 | from toolkit.constants import LENGTH_UNLIMITED 18 | from toolkit.symmetric_encryption.abstraction import AbstractSymmetricEncryption 19 | from toolkit.symmetric_padding import pkcs7_pad, pkcs7_unpad 20 | 21 | 22 | class AESxCBC(AbstractSymmetricEncryption): 23 | """AES-CBC Schemes, using PKCS7 Padding""" 24 | 25 | def __init__(self, 26 | *, 27 | key_length=16, 28 | cipher_length=LENGTH_UNLIMITED, 29 | message_length=LENGTH_UNLIMITED): 30 | super(AESxCBC, self).__init__(cipher_length=cipher_length, 31 | key_length=key_length, 32 | message_length=message_length) 33 | if key_length not in [16, 24, 32]: 34 | raise ValueError( 35 | "The AES key length needs to be 16, 24 or 32 bytes.") 36 | if cipher_length != LENGTH_UNLIMITED and cipher_length % 16: 37 | raise ValueError( 38 | "The AES-CBC cipher length needs to be an integer multiple of 16 bytes." 39 | ) 40 | 41 | def KeyGen(self) -> bytes: 42 | return os.urandom(self.key_length) 43 | 44 | def Encrypt(self, key: bytes, message: bytes) -> bytes: 45 | if self.message_length != LENGTH_UNLIMITED and len( 46 | message) != self.message_length: 47 | raise ValueError("Message(Input) length mismatch for AES-CBC.") 48 | if len(key) != self.key_length: 49 | raise ValueError("Key length mismatch for AES-CBC.") 50 | 51 | # PKCS7 Padding 52 | padded_message = pkcs7_pad(message, algorithms.AES.block_size) 53 | 54 | iv = os.urandom(algorithms.AES.block_size // 8) 55 | cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) 56 | encryptor = cipher.encryptor() 57 | return iv + encryptor.update(padded_message) + encryptor.finalize() 58 | 59 | def Decrypt(self, key: bytes, cipher_text: bytes) -> bytes: 60 | if self.cipher_length != LENGTH_UNLIMITED and len( 61 | cipher_text) != self.cipher_length: 62 | raise ValueError("Ciphertext length mismatch for AES-CBC.") 63 | if len(key) != self.key_length: 64 | raise ValueError("Key length mismatch for AES-CBC.") 65 | 66 | iv, cipher_text = cipher_text[:algorithms.AES.block_size // 67 | 8], cipher_text[algorithms.AES. 68 | block_size // 8:] 69 | cipher = Cipher(algorithms.AES(key), modes.CBC(iv)) 70 | decryptor = cipher.decryptor() 71 | padded_plaintext = decryptor.update(cipher_text) + decryptor.finalize() 72 | 73 | # PKCS7 Unpadding 74 | output = pkcs7_unpad(padded_plaintext, algorithms.AES.block_size) 75 | return output 76 | -------------------------------------------------------------------------------- /toolkit/symmetric_encryption/fpe.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: fpe.py 7 | @time: 2022/03/11 8 | @contact: jeza@vip.qq.com 9 | @site: 10 | @software: PyCharm 11 | @description: Format-preserving encryption: bitwise version 12 | See https://github.com/emulbreh/pyffx (MIT License) 13 | """ 14 | import hashlib 15 | import hmac 16 | import struct 17 | 18 | from toolkit.bits import Bitset 19 | from toolkit.bits_utils import half_bits, half_bits_not_padding 20 | 21 | DEFAULT_ROUNDS = 10 22 | 23 | 24 | class BitwiseFFX: 25 | """ Format-preserving, Feistel-based encryption (FFX) 26 | See https://github.com/emulbreh/pyffx 27 | For performance reasons, we only implement the binary version of this 28 | """ 29 | 30 | def __init__(self, rounds=DEFAULT_ROUNDS, digest_mod=hashlib.sha1): 31 | self.rounds = rounds 32 | self.digest_mod = digest_mod 33 | self.digest_size = self.digest_mod().digest_size 34 | 35 | def round(self, key: bytes, i: int, s: Bitset, output_len=0) -> Bitset: 36 | if output_len == 0: 37 | output_len = len(s) 38 | 39 | pre = struct.pack('I%sI' % len(s), i, *s) 40 | output_len_per_hash = int(self.digest_size * 8) 41 | i = 0 42 | result = Bitset(0b0, 0) 43 | while True: 44 | h = hmac.new(key, pre + struct.pack('I', i), self.digest_mod) 45 | d = Bitset(int(h.hexdigest(), 16), output_len_per_hash) 46 | result = result + d 47 | if len(result) >= output_len: 48 | break 49 | 50 | return result.get_higher_bits(output_len) 51 | 52 | def split(self, v: Bitset) -> (Bitset, Bitset): 53 | return half_bits_not_padding(v) 54 | 55 | def encrypt(self, key: bytes, v: Bitset) -> Bitset: 56 | a, b = self.split(v) 57 | for i in range(self.rounds): 58 | c = a ^ self.round(key, i, b, len(a)) 59 | a, b = b, c 60 | return a + b 61 | 62 | def decrypt(self, key: bytes, v: Bitset) -> Bitset: 63 | a, b = self.split(v) 64 | for i in range(self.rounds - 1, -1, -1): 65 | b, c = a, b 66 | a = c ^ self.round(key, i, b, len(c)) 67 | return a + b 68 | -------------------------------------------------------------------------------- /toolkit/symmetric_padding.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 _*- 2 | """ 3 | LIB-SSE CODE 4 | @author: Jeza Chen 5 | @license: GPL-3.0 License 6 | @file: symmetric_padding.py 7 | @time: 2022/03/09 8 | @contact: jeza@vip.qq.com 9 | @software: PyCharm 10 | @description: 11 | """ 12 | 13 | from cryptography.hazmat.primitives import padding 14 | 15 | 16 | def pkcs7_pad(message: bytes, block_size: int) -> bytes: 17 | padder = padding.PKCS7(block_size).padder() 18 | padded_message = padder.update(message) 19 | padded_message += padder.finalize() 20 | return padded_message 21 | 22 | 23 | def pkcs7_unpad(padded_message: bytes, block_size: int) -> bytes: 24 | unpadder = padding.PKCS7(block_size).unpadder() 25 | output = unpadder.update(padded_message) 26 | output += unpadder.finalize() 27 | return output 28 | --------------------------------------------------------------------------------