├── .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 |
4 |
5 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------