final version
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -90,8 +90,8 @@ cd deploy
|
||||
# 或本地开发模式
|
||||
# 后端
|
||||
cd backend
|
||||
pip install -r requirements.txt
|
||||
uvicorn app.main:app --reload
|
||||
source venv/bin/activate
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
|
||||
|
||||
# 前端
|
||||
cd frontend
|
||||
@@ -155,7 +155,7 @@ algorithm-showcase/
|
||||
|
||||
## API文档
|
||||
|
||||
API文档可在运行服务后访问:http://localhost:8000/docs
|
||||
API文档可在运行服务后访问:http://localhost:8001/docs
|
||||
|
||||
详细API参考文档请参阅 [docs/api-reference.md](docs/api-reference.md)。
|
||||
|
||||
|
||||
170
RUNNING.md
170
RUNNING.md
@@ -2,53 +2,25 @@
|
||||
|
||||
## 系统要求
|
||||
|
||||
- Docker 和 Docker Compose
|
||||
- 或者 Python 3.8+ 和 Node.js 18+
|
||||
- Python 3.8+ 和 Node.js 18+
|
||||
- 或者 Docker 和 Docker Compose
|
||||
|
||||
## 部署选项
|
||||
## 本地开发模式
|
||||
|
||||
### 选项1:Docker Compose 部署(推荐)
|
||||
|
||||
#### 完整部署(适用于网络良好环境)
|
||||
|
||||
```bash
|
||||
# 进入部署目录
|
||||
cd deploy
|
||||
|
||||
# 运行部署脚本
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
#### 网络受限环境部署
|
||||
|
||||
```bash
|
||||
# 手动拉取镜像
|
||||
docker pull postgres:14-alpine
|
||||
docker pull redis:7-alpine
|
||||
docker pull minio/minio:latest
|
||||
docker pull python:3.9-slim
|
||||
docker pull node:18-alpine
|
||||
|
||||
# 使用预拉取镜像的Compose文件
|
||||
docker-compose -f compose-without-build.yml up -d
|
||||
```
|
||||
|
||||
### 选项2:本地开发模式
|
||||
|
||||
#### 后端服务
|
||||
### 后端服务
|
||||
|
||||
```bash
|
||||
# 进入后端目录
|
||||
cd backend
|
||||
|
||||
# 安装依赖
|
||||
pip install -r requirements.txt
|
||||
# 激活虚拟环境
|
||||
source venv/bin/activate
|
||||
|
||||
# 启动服务
|
||||
uvicorn app.main:app --reload
|
||||
python -m uvicorn app.main:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
#### 前端服务
|
||||
### 前端服务
|
||||
|
||||
```bash
|
||||
# 进入前端目录
|
||||
@@ -61,30 +33,7 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## 服务地址
|
||||
|
||||
部署完成后,可通过以下地址访问服务:
|
||||
|
||||
- **前端应用**: http://localhost:3000
|
||||
- **后端API**: http://localhost:8000
|
||||
- **API文档**: http://localhost:8000/docs
|
||||
- **MinIO控制台**: http://localhost:9001 (admin/minioadmin)
|
||||
- **PostgreSQL**: localhost:5432
|
||||
- **Redis**: localhost:6379
|
||||
|
||||
## 环境配置
|
||||
|
||||
### Docker环境变量
|
||||
|
||||
创建 `.env` 文件:
|
||||
|
||||
```bash
|
||||
# 在 deploy 目录下创建 .env 文件
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
SECRET_KEY=your_secret_key_here
|
||||
```
|
||||
|
||||
### 本地开发环境变量
|
||||
### 环境配置
|
||||
|
||||
在本地开发时,可以创建 `.env` 文件:
|
||||
|
||||
@@ -105,13 +54,83 @@ ACCESS_TOKEN_EXPIRE_MINUTES=30
|
||||
|
||||
```bash
|
||||
# frontend/.env
|
||||
VITE_API_BASE_URL=http://localhost:8000/api
|
||||
VITE_API_BASE_URL=http://localhost:8001/api
|
||||
```
|
||||
|
||||
## 服务地址
|
||||
|
||||
部署完成后,可通过以下地址访问服务:
|
||||
|
||||
- **前端应用**: http://localhost:3000
|
||||
- **后端API**: http://localhost:8001
|
||||
- **API文档**: http://localhost:8001/docs
|
||||
- **MinIO控制台**: http://localhost:9001 (admin/minioadmin)
|
||||
- **PostgreSQL**: localhost:5432
|
||||
- **Redis**: localhost:6379
|
||||
|
||||
## 验证部署
|
||||
|
||||
### 检查服务状态
|
||||
|
||||
```bash
|
||||
# 检查后端服务
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# 检查前端服务
|
||||
curl http://localhost:3000
|
||||
```
|
||||
|
||||
### API测试
|
||||
|
||||
```bash
|
||||
# 测试健康检查
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# 测试算法API
|
||||
curl http://localhost:8001/api/v1/algorithms
|
||||
|
||||
# 测试API文档
|
||||
open http://localhost:8001/docs
|
||||
```
|
||||
|
||||
## Docker Compose 部署
|
||||
|
||||
### 完整部署(适用于网络良好环境)
|
||||
|
||||
```bash
|
||||
# 进入部署目录
|
||||
cd deploy
|
||||
|
||||
# 运行部署脚本
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
### 网络受限环境部署
|
||||
|
||||
```bash
|
||||
# 手动拉取镜像
|
||||
docker pull postgres:14-alpine
|
||||
docker pull redis:7-alpine
|
||||
docker pull minio/minio:latest
|
||||
docker pull python:3.9-slim
|
||||
docker pull node:18-alpine
|
||||
|
||||
# 使用预拉取镜像的Compose文件
|
||||
docker-compose -f compose-without-build.yml up -d
|
||||
```
|
||||
|
||||
### Docker环境变量
|
||||
|
||||
创建 `.env` 文件:
|
||||
|
||||
```bash
|
||||
# 在 deploy 目录下创建 .env 文件
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
SECRET_KEY=your_secret_key_here
|
||||
```
|
||||
|
||||
### Docker服务状态检查
|
||||
|
||||
```bash
|
||||
# Docker Compose 环境
|
||||
docker-compose -f docker-compose-full.yml ps
|
||||
@@ -121,22 +140,7 @@ docker-compose -f docker-compose-full.yml logs backend
|
||||
docker-compose -f docker-compose-full.yml logs frontend
|
||||
```
|
||||
|
||||
### API测试
|
||||
|
||||
```bash
|
||||
# 测试健康检查
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# 测试算法API
|
||||
curl http://localhost:8000/api/v1/algorithms
|
||||
|
||||
# 测试API文档
|
||||
open http://localhost:8000/docs
|
||||
```
|
||||
|
||||
## 常用命令
|
||||
|
||||
### Docker Compose 管理
|
||||
## Docker常用命令
|
||||
|
||||
```bash
|
||||
# 启动所有服务
|
||||
@@ -155,18 +159,6 @@ docker-compose -f docker-compose-full.yml logs -f
|
||||
docker-compose -f docker-compose-full.yml restart backend
|
||||
```
|
||||
|
||||
### 本地开发
|
||||
|
||||
```bash
|
||||
# 后端开发
|
||||
cd backend
|
||||
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
|
||||
|
||||
# 前端开发
|
||||
cd frontend
|
||||
npm run dev -- --host 0.0.0.0 --port 3000
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
如果遇到问题,请参考 [TROUBLESHOOTING.md](TROUBLESHOOTING.md) 文档。
|
||||
|
||||
@@ -1,867 +0,0 @@
|
||||
"""Gitea服务,处理与Gitea相关的业务逻辑"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
import base64
|
||||
from typing import Optional, Dict, Any, List
|
||||
import uuid
|
||||
|
||||
from app.gitea.client import GiteaClient
|
||||
from app.config.settings import settings
|
||||
from app.models.database import SessionLocal
|
||||
from app.models.models import GiteaConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GiteaService:
|
||||
"""Gitea服务类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化Gitea服务"""
|
||||
self.config = self._load_config()
|
||||
self.client = None
|
||||
if self.config:
|
||||
self.client = GiteaClient(
|
||||
self.config['server_url'],
|
||||
self.config['access_token']
|
||||
)
|
||||
|
||||
def _load_config(self) -> Optional[Dict[str, Any]]:
|
||||
"""加载Gitea配置
|
||||
|
||||
Returns:
|
||||
Gitea配置信息
|
||||
"""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
# 从数据库中获取配置(只取第一个配置)
|
||||
config = db.query(GiteaConfig).filter_by(status="active").first()
|
||||
db.close()
|
||||
|
||||
if config:
|
||||
return {
|
||||
'id': config.id,
|
||||
'server_url': config.server_url,
|
||||
'access_token': config.access_token,
|
||||
'default_owner': config.default_owner,
|
||||
'repo_prefix': config.repo_prefix,
|
||||
'status': config.status
|
||||
}
|
||||
|
||||
# 配置不存在时返回默认值
|
||||
return {
|
||||
'server_url': getattr(settings, 'GITEA_SERVER_URL', ''),
|
||||
'access_token': getattr(settings, 'GITEA_ACCESS_TOKEN', ''),
|
||||
'default_owner': getattr(settings, 'GITEA_DEFAULT_OWNER', ''),
|
||||
'repo_prefix': getattr(settings, 'GITEA_REPO_PREFIX', '')
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load Gitea config from database: {str(e)}")
|
||||
# 出错时返回默认配置
|
||||
return {
|
||||
'server_url': getattr(settings, 'GITEA_SERVER_URL', ''),
|
||||
'access_token': getattr(settings, 'GITEA_ACCESS_TOKEN', ''),
|
||||
'default_owner': getattr(settings, 'GITEA_DEFAULT_OWNER', ''),
|
||||
'repo_prefix': getattr(settings, 'GITEA_REPO_PREFIX', '')
|
||||
}
|
||||
|
||||
def save_config(self, config: Dict[str, Any]) -> bool:
|
||||
"""保存Gitea配置
|
||||
|
||||
Args:
|
||||
config: Gitea配置信息
|
||||
|
||||
Returns:
|
||||
是否保存成功
|
||||
"""
|
||||
try:
|
||||
db = SessionLocal()
|
||||
|
||||
# 将所有现有配置设置为非活动状态
|
||||
db.query(GiteaConfig).update({GiteaConfig.status: "inactive"})
|
||||
|
||||
# 检查是否已有配置
|
||||
existing_config = db.query(GiteaConfig).first()
|
||||
|
||||
if existing_config:
|
||||
# 更新现有配置
|
||||
existing_config.server_url = config['server_url']
|
||||
existing_config.access_token = config['access_token']
|
||||
existing_config.default_owner = config['default_owner']
|
||||
existing_config.repo_prefix = config.get('repo_prefix', '')
|
||||
existing_config.status = "active"
|
||||
else:
|
||||
# 创建新配置
|
||||
new_config = GiteaConfig(
|
||||
id=f"gitea-config-{uuid.uuid4()}",
|
||||
server_url=config['server_url'],
|
||||
access_token=config['access_token'],
|
||||
default_owner=config['default_owner'],
|
||||
repo_prefix=config.get('repo_prefix', ''),
|
||||
status="active"
|
||||
)
|
||||
db.add(new_config)
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
# 更新内存中的配置
|
||||
self.config = config
|
||||
self.client = GiteaClient(
|
||||
config['server_url'],
|
||||
config['access_token']
|
||||
)
|
||||
|
||||
logger.info("Gitea config saved to database successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save Gitea config to database: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_config(self) -> Optional[Dict[str, Any]]:
|
||||
"""获取Gitea配置
|
||||
|
||||
Returns:
|
||||
Gitea配置信息
|
||||
"""
|
||||
return self.config
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
"""测试Gitea连接
|
||||
|
||||
Returns:
|
||||
是否连接成功
|
||||
"""
|
||||
if not self.client:
|
||||
return False
|
||||
return self.client.check_connection()
|
||||
|
||||
def create_repository(self, algorithm_id: str, algorithm_name: str, description: str = "") -> Optional[Dict[str, Any]]:
|
||||
"""为算法创建Gitea仓库
|
||||
|
||||
Args:
|
||||
algorithm_id: 算法ID
|
||||
algorithm_name: 算法名称
|
||||
description: 仓库描述
|
||||
|
||||
Returns:
|
||||
创建的仓库信息
|
||||
"""
|
||||
try:
|
||||
if not self.client:
|
||||
logger.error("Gitea client not initialized. Please check your Gitea configuration.")
|
||||
return None
|
||||
|
||||
if not self.config.get('default_owner'):
|
||||
logger.error("Default owner not set in Gitea configuration.")
|
||||
return None
|
||||
|
||||
# 记录传入的algorithm_id
|
||||
logger.info(f"Received algorithm_id: {algorithm_id}")
|
||||
|
||||
# 检查是否已经包含前缀
|
||||
repo_prefix = self.config.get('repo_prefix', '')
|
||||
if repo_prefix and algorithm_id.startswith(repo_prefix):
|
||||
logger.info(f"Algorithm ID already contains prefix: {repo_prefix}")
|
||||
repo_name = algorithm_id
|
||||
else:
|
||||
# 生成仓库名称,添加前缀
|
||||
repo_name = f"{repo_prefix}{algorithm_id}" if repo_prefix else algorithm_id
|
||||
logger.info(f"Generated repository name: {repo_name}")
|
||||
|
||||
logger.info(f"Creating repository: {repo_name} for owner: {self.config['default_owner']}")
|
||||
|
||||
# 创建仓库
|
||||
repo = self.client.create_repository(
|
||||
self.config['default_owner'],
|
||||
repo_name,
|
||||
description or f"Algorithm repository for {algorithm_name}",
|
||||
False
|
||||
)
|
||||
|
||||
if repo:
|
||||
logger.info(f"Repository created successfully: {repo}")
|
||||
# 验证仓库是否真的存在
|
||||
verify_repo = self.client.get_repository(self.config['default_owner'], repo_name)
|
||||
if not verify_repo:
|
||||
logger.error(f"Repository creation verified failed: {repo_name}")
|
||||
return None
|
||||
else:
|
||||
logger.error(f"Failed to create repository: {repo_name}")
|
||||
|
||||
return repo
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create repository: {str(e)}")
|
||||
return None
|
||||
|
||||
def clone_repository(self, repo_url: str, algorithm_id: str, branch: str = "main") -> bool:
|
||||
"""克隆Gitea仓库
|
||||
|
||||
Args:
|
||||
repo_url: 仓库URL
|
||||
algorithm_id: 算法ID
|
||||
branch: 分支名称
|
||||
|
||||
Returns:
|
||||
是否克隆成功
|
||||
"""
|
||||
try:
|
||||
# 创建本地目录
|
||||
repo_dir = f"/tmp/algorithms/{algorithm_id}"
|
||||
|
||||
logger.info(f"Cloning repository to: {repo_dir}")
|
||||
|
||||
# 导入需要的模块
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
# 如果目录已存在,先清理它
|
||||
if os.path.exists(repo_dir):
|
||||
logger.info(f"Cleaning existing repository directory: {repo_dir}")
|
||||
try:
|
||||
shutil.rmtree(repo_dir)
|
||||
logger.info(f"Successfully cleaned directory: {repo_dir}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to clean directory: {str(e)}")
|
||||
# 尝试使用sudo删除(如果有权限)
|
||||
try:
|
||||
subprocess.run(["sudo", "rm", "-rf", repo_dir], check=True)
|
||||
logger.info(f"Successfully cleaned directory with sudo: {repo_dir}")
|
||||
except Exception as e2:
|
||||
logger.error(f"Failed to clean directory with sudo: {str(e2)}")
|
||||
return False
|
||||
|
||||
# 重新创建目录
|
||||
logger.info(f"Creating directory: {repo_dir}")
|
||||
os.makedirs(repo_dir, exist_ok=True)
|
||||
logger.info(f"Directory created successfully: {repo_dir}")
|
||||
|
||||
# 克隆仓库
|
||||
cmd = ["git", "clone", "-b", branch, repo_url, repo_dir]
|
||||
logger.info(f"Running clone command: {' '.join(cmd)}")
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Repository cloned successfully: {repo_url}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to clone repository: {result.stderr}")
|
||||
|
||||
# 尝试初始化仓库
|
||||
logger.info(f"Trying to initialize repository in {repo_dir}")
|
||||
|
||||
# 初始化git仓库
|
||||
init_result = subprocess.run(["git", "init"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if init_result.returncode != 0:
|
||||
logger.error(f"Failed to initialize git repository: {init_result.stderr}")
|
||||
return False
|
||||
|
||||
# 添加远程仓库
|
||||
remote_result = subprocess.run(["git", "remote", "add", "origin", repo_url], cwd=repo_dir, capture_output=True, text=True)
|
||||
if remote_result.returncode != 0:
|
||||
logger.error(f"Failed to add remote repository: {remote_result.stderr}")
|
||||
# 如果远程仓库已存在,尝试更新它
|
||||
logger.info("Trying to update existing remote repository")
|
||||
update_result = subprocess.run(["git", "remote", "set-url", "origin", repo_url], cwd=repo_dir, capture_output=True, text=True)
|
||||
if update_result.returncode != 0:
|
||||
logger.error(f"Failed to update remote repository: {update_result.stderr}")
|
||||
return False
|
||||
logger.info("Successfully updated remote repository")
|
||||
|
||||
# 创建初始文件
|
||||
readme_path = os.path.join(repo_dir, "README.md")
|
||||
with open(readme_path, "w") as f:
|
||||
f.write("# Algorithm Repository\n\nThis is an algorithm repository.\n")
|
||||
|
||||
# 添加文件并提交
|
||||
add_result = subprocess.run(["git", "add", "README.md"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if add_result.returncode != 0:
|
||||
logger.error(f"Failed to add README.md: {add_result.stderr}")
|
||||
return False
|
||||
|
||||
commit_result = subprocess.run(["git", "commit", "-m", "Initial commit"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if commit_result.returncode != 0:
|
||||
logger.error(f"Failed to commit initial file: {commit_result.stderr}")
|
||||
return False
|
||||
|
||||
# 推送代码到远程仓库
|
||||
push_result = subprocess.run(["git", "push", "-u", "origin", branch], cwd=repo_dir, capture_output=True, text=True)
|
||||
if push_result.returncode != 0:
|
||||
logger.error(f"Failed to push initial commit: {push_result.stderr}")
|
||||
# 即使推送失败,初始化仓库也算成功
|
||||
logger.info(f"Repository initialized successfully, but push failed: {push_result.stderr}")
|
||||
return True
|
||||
|
||||
logger.info(f"Repository initialized and pushed successfully: {repo_url}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to clone repository: {str(e)}")
|
||||
return False
|
||||
|
||||
def push_to_repository(self, algorithm_id: str, message: str = "Update code") -> bool:
|
||||
"""推送代码到Gitea仓库
|
||||
|
||||
Args:
|
||||
algorithm_id: 算法ID
|
||||
message: 提交消息
|
||||
|
||||
Returns:
|
||||
是否推送成功
|
||||
"""
|
||||
try:
|
||||
logger.info("=== 开始推送代码到Gitea仓库 ===")
|
||||
logger.info(f"Algorithm ID: {algorithm_id}")
|
||||
logger.info(f"Commit message: {message}")
|
||||
|
||||
repo_dir = f"/tmp/algorithms/{algorithm_id}"
|
||||
logger.info(f"Repository directory: {repo_dir}")
|
||||
|
||||
if not os.path.exists(repo_dir):
|
||||
logger.error(f"❌ Repository directory not found: {repo_dir}")
|
||||
return False
|
||||
|
||||
# 首先尝试使用API上传(推荐方法,避免Git推送限制)
|
||||
logger.info("Attempting to upload files via Gitea API...")
|
||||
api_upload_success = self.upload_files_via_api(algorithm_id, message)
|
||||
|
||||
if api_upload_success:
|
||||
logger.info(f"✅ Code uploaded successfully via API for algorithm: {algorithm_id}")
|
||||
return True
|
||||
else:
|
||||
logger.warning("❌ API upload failed, falling back to Git push...")
|
||||
|
||||
# 如果API上传失败,回退到原来的Git推送方法
|
||||
import subprocess
|
||||
|
||||
# 检查是否是git仓库
|
||||
git_dir = os.path.join(repo_dir, ".git")
|
||||
if not os.path.exists(git_dir):
|
||||
logger.info(f"⚠️ Git repository not initialized, initializing...")
|
||||
# 初始化git仓库
|
||||
logger.info(f"Executing: git init in {repo_dir}")
|
||||
init_result = subprocess.run(["git", "init"], cwd=repo_dir, capture_output=True, text=True)
|
||||
logger.info(f"Git init output: {init_result.stdout}")
|
||||
if init_result.stderr:
|
||||
logger.warning(f"Git init stderr: {init_result.stderr}")
|
||||
if init_result.returncode != 0:
|
||||
logger.error(f"❌ Failed to initialize git repository: {init_result.stderr}")
|
||||
return False
|
||||
logger.info("✅ Git repository initialized successfully")
|
||||
|
||||
# 添加远程仓库(从配置中获取,包含访问令牌以确保认证)
|
||||
if self.config.get('default_owner'):
|
||||
# 使用访问令牌构建认证URL
|
||||
auth_repo_url = f"https://{self.config['access_token']}@{self.config['server_url'].replace('https://', '').replace('http://', '')}/{self.config['default_owner']}/{algorithm_id}.git"
|
||||
logger.info(f"Adding remote repository: {auth_repo_url}")
|
||||
remote_result = subprocess.run(["git", "remote", "add", "origin", auth_repo_url], cwd=repo_dir, capture_output=True, text=True)
|
||||
logger.info(f"Git remote add output: {remote_result.stdout}")
|
||||
if remote_result.stderr:
|
||||
logger.warning(f"Git remote add stderr: {remote_result.stderr}")
|
||||
if remote_result.returncode != 0:
|
||||
logger.error(f"❌ Failed to add remote repository: {remote_result.stderr}")
|
||||
return False
|
||||
logger.info("✅ Remote repository added successfully")
|
||||
else:
|
||||
logger.info("✅ Git repository already initialized")
|
||||
|
||||
# 执行git命令 - 分批添加文件以处理大量文件
|
||||
logger.info("=== 执行Git操作 ===")
|
||||
|
||||
# 获取所有需要添加的文件
|
||||
all_files = []
|
||||
for root, dirs, files in os.walk(repo_dir):
|
||||
if '.git' in root:
|
||||
continue
|
||||
for file in files:
|
||||
if not file.endswith('.git'):
|
||||
file_path = os.path.relpath(os.path.join(root, file), repo_dir)
|
||||
all_files.append(file_path)
|
||||
|
||||
logger.info(f"Total files to add: {len(all_files)}")
|
||||
|
||||
# 分批添加文件,避免命令行参数过长
|
||||
batch_size = 100 # 每次添加100个文件
|
||||
for i in range(0, len(all_files), batch_size):
|
||||
batch = all_files[i:i + batch_size]
|
||||
logger.info(f"Adding batch {i//batch_size + 1}: {len(batch)} files")
|
||||
|
||||
add_result = subprocess.run(["git", "add"] + batch, cwd=repo_dir, capture_output=True, text=True)
|
||||
if add_result.stderr and add_result.returncode != 0:
|
||||
logger.error(f"❌ Git add batch {i//batch_size + 1} failed: {add_result.stderr}")
|
||||
return False
|
||||
elif add_result.stderr:
|
||||
logger.warning(f"Git add batch {i//batch_size + 1} warning: {add_result.stderr}")
|
||||
|
||||
logger.info("✅ Git add completed successfully")
|
||||
|
||||
# 检查是否有更改需要提交
|
||||
logger.info("Executing: git status --porcelain")
|
||||
status_result = subprocess.run(["git", "status", "--porcelain"], cwd=repo_dir, capture_output=True, text=True)
|
||||
logger.info(f"Git status output: {status_result.stdout}")
|
||||
if status_result.stderr:
|
||||
logger.warning(f"Git status stderr: {status_result.stderr}")
|
||||
if status_result.returncode != 0:
|
||||
logger.error(f"❌ Git status failed: {status_result.stderr}")
|
||||
return False
|
||||
|
||||
# 如果有更改,执行commit和push
|
||||
if status_result.stdout.strip():
|
||||
logger.info("✅ Changes detected, proceeding with commit and push")
|
||||
# 执行git commit
|
||||
logger.info(f"Executing: git commit -m '{message}'")
|
||||
commit_result = subprocess.run(["git", "commit", "-m", message], cwd=repo_dir, capture_output=True, text=True)
|
||||
logger.info(f"Git commit output: {commit_result.stdout}")
|
||||
if commit_result.stderr:
|
||||
logger.warning(f"Git commit stderr: {commit_result.stderr}")
|
||||
if commit_result.returncode != 0:
|
||||
logger.error(f"❌ Git commit failed: {commit_result.stderr}")
|
||||
return False
|
||||
logger.info("✅ Git commit completed successfully")
|
||||
|
||||
# 检查仓库大小
|
||||
logger.info("Checking repository size before push")
|
||||
total_size = 0
|
||||
for dirpath, dirnames, filenames in os.walk(repo_dir):
|
||||
for filename in filenames:
|
||||
filepath = os.path.join(dirpath, filename)
|
||||
if not filepath.startswith(os.path.join(repo_dir, '.git')):
|
||||
total_size += os.path.getsize(filepath)
|
||||
logger.info(f"Repository size (excluding .git): {total_size / (1024 * 1024):.2f} MB")
|
||||
|
||||
if total_size > 100 * 1024 * 1024: # 100MB
|
||||
logger.warning(f"Repository is large: {total_size / (1024 * 1024):.2f} MB")
|
||||
logger.warning("This may cause HTTP 413 errors on push")
|
||||
|
||||
# 设置Git推送缓冲区大小(增加到1GB)
|
||||
logger.info("Setting Git http.postBuffer to 1GB")
|
||||
buffer_result = subprocess.run(["git", "config", "http.postBuffer", "1073741824"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if buffer_result.returncode != 0:
|
||||
logger.warning(f"Failed to set http.postBuffer: {buffer_result.stderr}")
|
||||
else:
|
||||
logger.info("✅ Git http.postBuffer set successfully")
|
||||
|
||||
# 禁用Git压缩
|
||||
logger.info("Disabling Git compression")
|
||||
compression_result = subprocess.run(["git", "config", "core.compression", "0"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if compression_result.returncode != 0:
|
||||
logger.warning(f"Failed to set core.compression: {compression_result.stderr}")
|
||||
else:
|
||||
logger.info("✅ Git core.compression disabled successfully")
|
||||
|
||||
# 针对大仓库优化的推送命令
|
||||
logger.info("Setting additional Git configs for large repositories...")
|
||||
subprocess.run(["git", "config", "http.postBuffer", "524288000"], cwd=repo_dir) # 500MB buffer
|
||||
subprocess.run(["git", "config", "pack.windowMemory", "128m"], cwd=repo_dir) # Limit memory usage
|
||||
subprocess.run(["git", "config", "pack.packSizeLimit", "128m"], cwd=repo_dir) # Limit pack size
|
||||
|
||||
# 执行git push,添加更多优化参数
|
||||
logger.info("Executing: git push with optimizations for large repositories")
|
||||
push_result = subprocess.run([
|
||||
"git", "push",
|
||||
"--verbose",
|
||||
"-u", "origin", "main",
|
||||
"--receive-pack='git receive-pack'", # Ensure proper receive pack
|
||||
"--progress" # Show progress for large pushes
|
||||
], cwd=repo_dir, capture_output=True, text=True, timeout=300) # 5 minute timeout
|
||||
logger.info(f"Git push output: {push_result.stdout}")
|
||||
if push_result.stderr:
|
||||
logger.warning(f"Git push stderr: {push_result.stderr}")
|
||||
if push_result.returncode != 0:
|
||||
# 检查是否是常见的大文件错误
|
||||
error_msg = push_result.stderr.lower()
|
||||
is_large_file_error = (
|
||||
"http 413" in error_msg or
|
||||
"payload too large" in error_msg or
|
||||
"unpack failed" in error_msg or
|
||||
"remote: fatal" in error_msg or
|
||||
"cannot spawn" in error_msg or
|
||||
"timeout" in error_msg
|
||||
)
|
||||
|
||||
if is_large_file_error:
|
||||
logger.error(f"❌ Git push failed likely due to repository size: {total_size / (1024 * 1024):.2f} MB")
|
||||
logger.error(f"Error details: {push_result.stderr}")
|
||||
logger.error("\n📋 解决方案建议:")
|
||||
logger.error("1. 检查Gitea服务器配置,增加MAX_UPLOAD_SIZE限制")
|
||||
logger.error("2. 尝试使用SSH协议进行推送(如果服务器支持)")
|
||||
logger.error("3. 优化仓库大小,移除不必要的大文件")
|
||||
logger.error("4. 考虑使用Git LFS(Large File Storage)管理大文件")
|
||||
|
||||
# 尝试使用SSH协议进行推送(如果URL是HTTPS格式)
|
||||
logger.info("\n🔄 尝试使用SSH协议进行推送...")
|
||||
try:
|
||||
# 获取当前远程URL
|
||||
remote_result = subprocess.run(["git", "remote", "get-url", "origin"], cwd=repo_dir, capture_output=True, text=True, timeout=30)
|
||||
if remote_result.returncode == 0:
|
||||
https_url = remote_result.stdout.strip()
|
||||
# 将HTTPS URL转换为SSH URL
|
||||
if https_url.startswith("https://"):
|
||||
ssh_url = https_url.replace("https://", "git@").replace(":", "/")
|
||||
logger.info(f"Converting HTTPS URL to SSH URL: {ssh_url}")
|
||||
# 更新远程URL
|
||||
set_url_result = subprocess.run(["git", "remote", "set-url", "origin", ssh_url], cwd=repo_dir, capture_output=True, text=True, timeout=30)
|
||||
if set_url_result.returncode == 0:
|
||||
logger.info("✅ Remote URL updated to SSH format")
|
||||
# 再次尝试推送,使用更保守的参数
|
||||
logger.info("Executing: git push with SSH and conservative parameters")
|
||||
ssh_push_result = subprocess.run([
|
||||
"git", "push",
|
||||
"--verbose",
|
||||
"-u", "origin", "main"
|
||||
], cwd=repo_dir, capture_output=True, text=True, timeout=600) # 10 minute timeout for SSH
|
||||
|
||||
if ssh_push_result.returncode == 0:
|
||||
logger.info("✅ Git push completed successfully with SSH")
|
||||
# 改回HTTPS URL
|
||||
reset_url_result = subprocess.run(["git", "remote", "set-url", "origin", https_url], cwd=repo_dir, capture_output=True, text=True, timeout=30)
|
||||
if reset_url_result.returncode != 0:
|
||||
logger.warning(f"Failed to reset remote URL to HTTPS: {reset_url_result.stderr}")
|
||||
return True
|
||||
else:
|
||||
logger.warning(f"SSH push failed: {ssh_push_result.stderr}")
|
||||
# 改回HTTPS URL
|
||||
reset_url_result = subprocess.run(["git", "remote", "set-url", "origin", https_url], cwd=repo_dir, capture_output=True, text=True, timeout=30)
|
||||
if reset_url_result.returncode != 0:
|
||||
logger.warning(f"Failed to reset remote URL to HTTPS: {reset_url_result.stderr}")
|
||||
|
||||
# 如果SSH也失败,尝试分阶段推送
|
||||
logger.info("\n🔄 尝试分阶段推送...")
|
||||
return self.push_repository_staged(repo_dir, https_url)
|
||||
else:
|
||||
logger.warning(f"Could not get remote URL: {remote_result.stderr}")
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.warning("Remote URL command timed out")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to try SSH push: {str(e)}")
|
||||
else:
|
||||
logger.error(f"❌ Git push failed: {push_result.stderr}")
|
||||
return False
|
||||
logger.info("✅ Git push completed successfully")
|
||||
else:
|
||||
logger.info("ℹ️ No changes to commit")
|
||||
|
||||
logger.info(f"✅ Code pushed successfully for algorithm: {algorithm_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"=== 推送代码失败 ===")
|
||||
logger.error(f"Error: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
def pull_from_repository(self, algorithm_id: str) -> bool:
|
||||
"""从Gitea仓库拉取代码
|
||||
|
||||
Args:
|
||||
algorithm_id: 算法ID
|
||||
|
||||
Returns:
|
||||
是否拉取成功
|
||||
"""
|
||||
try:
|
||||
repo_dir = f"/tmp/algorithms/{algorithm_id}"
|
||||
|
||||
if not os.path.exists(repo_dir):
|
||||
logger.error(f"Repository directory not found: {repo_dir}")
|
||||
return False
|
||||
|
||||
# 执行git pull命令
|
||||
result = subprocess.run(
|
||||
["git", "pull"],
|
||||
cwd=repo_dir,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
logger.info(f"Code pulled successfully for algorithm: {algorithm_id}")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to pull code: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to pull code: {str(e)}")
|
||||
return False
|
||||
|
||||
def push_repository_staged(self, repo_dir: str, origin_url: str) -> bool:
|
||||
"""
|
||||
分阶段推送仓库,用于处理超大仓库
|
||||
"""
|
||||
try:
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
logger.info("=== 开始分阶段推送仓库 ===")
|
||||
logger.info(f"Repository directory: {repo_dir}")
|
||||
|
||||
# 获取所有文件并按类型分组
|
||||
all_files = []
|
||||
for root, dirs, files in os.walk(repo_dir):
|
||||
# 跳过 .git 目录
|
||||
if '.git' in root:
|
||||
continue
|
||||
for file in files:
|
||||
file_path = os.path.relpath(os.path.join(root, file), repo_dir)
|
||||
if file_path.startswith('.git'):
|
||||
continue
|
||||
all_files.append(file_path)
|
||||
|
||||
logger.info(f"Total files to stage: {len(all_files)}")
|
||||
|
||||
# 按扩展名分类文件,优先推送小文件
|
||||
def get_file_size(file_path):
|
||||
try:
|
||||
return os.path.getsize(os.path.join(repo_dir, file_path))
|
||||
except:
|
||||
return 0
|
||||
|
||||
# 按文件大小排序(从小到大)
|
||||
sorted_files = sorted(all_files, key=get_file_size)
|
||||
|
||||
# 分批处理,每批最多50个文件或不超过50MB
|
||||
batch_size_limit = 50
|
||||
batch_size_bytes = 50 * 1024 * 1024 # 50MB
|
||||
|
||||
current_batch = []
|
||||
current_batch_size = 0
|
||||
batch_number = 1
|
||||
|
||||
for file_path in sorted_files:
|
||||
file_full_path = os.path.join(repo_dir, file_path)
|
||||
file_size = get_file_size(file_path)
|
||||
|
||||
# 如果单个文件太大,单独处理
|
||||
if file_size > batch_size_bytes:
|
||||
logger.info(f"Handling large file separately: {file_path} ({file_size / (1024*1024):.2f}MB)")
|
||||
# 单独添加和推送这个大文件
|
||||
add_result = subprocess.run(["git", "add", file_path], cwd=repo_dir, capture_output=True, text=True)
|
||||
if add_result.returncode != 0:
|
||||
logger.error(f"Failed to add large file {file_path}: {add_result.stderr}")
|
||||
continue
|
||||
|
||||
# 检查是否有暂存的更改
|
||||
status_result = subprocess.run(["git", "status", "--porcelain"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if status_result.stdout.strip():
|
||||
# 创建专门的提交
|
||||
commit_msg = f"Add large file: {file_path}"
|
||||
commit_result = subprocess.run(["git", "commit", "-m", commit_msg], cwd=repo_dir, capture_output=True, text=True)
|
||||
if commit_result.returncode == 0:
|
||||
logger.info(f"Committed large file: {file_path}")
|
||||
|
||||
# 推送这个提交
|
||||
push_result = subprocess.run([
|
||||
"git", "push", "--verbose", "origin", "main"
|
||||
], cwd=repo_dir, capture_output=True, text=True, timeout=300)
|
||||
|
||||
if push_result.returncode != 0:
|
||||
logger.warning(f"Push failed for large file {file_path}: {push_result.stderr}")
|
||||
# 如果推送失败,尝试重置这个文件的暂存状态
|
||||
subprocess.run(["git", "reset", "HEAD", file_path], cwd=repo_dir, capture_output=True, text=True)
|
||||
else:
|
||||
logger.info(f"Successfully pushed large file: {file_path}")
|
||||
else:
|
||||
logger.error(f"Failed to commit large file {file_path}: {commit_result.stderr}")
|
||||
else:
|
||||
# 尝试添加到当前批次
|
||||
if (len(current_batch) >= batch_size_limit or
|
||||
current_batch_size + file_size > batch_size_bytes):
|
||||
# 推送当前批次
|
||||
if current_batch:
|
||||
logger.info(f"Pushing batch {batch_number} with {len(current_batch)} files...")
|
||||
success = self.push_batch(repo_dir, current_batch, batch_number, origin_url)
|
||||
if not success:
|
||||
logger.error(f"Failed to push batch {batch_number}")
|
||||
return False
|
||||
batch_number += 1
|
||||
current_batch = []
|
||||
current_batch_size = 0
|
||||
|
||||
current_batch.append(file_path)
|
||||
current_batch_size += file_size
|
||||
|
||||
# 推送最后一批
|
||||
if current_batch:
|
||||
logger.info(f"Pushing final batch {batch_number} with {len(current_batch)} files...")
|
||||
success = self.push_batch(repo_dir, current_batch, batch_number, origin_url)
|
||||
if not success:
|
||||
logger.error(f"Failed to push final batch {batch_number}")
|
||||
return False
|
||||
|
||||
logger.info("✅ 分阶段推送完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ 分阶段推送失败: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
def push_batch(self, repo_dir: str, file_batch: list, batch_num: int, origin_url: str) -> bool:
|
||||
"""
|
||||
推送文件批次
|
||||
"""
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
logger.info(f"Processing batch {batch_num}: {len(file_batch)} files")
|
||||
|
||||
# 添加批次中的文件
|
||||
for file_path in file_batch:
|
||||
add_result = subprocess.run(["git", "add", file_path], cwd=repo_dir, capture_output=True, text=True)
|
||||
if add_result.returncode != 0:
|
||||
logger.error(f"Failed to add file {file_path}: {add_result.stderr}")
|
||||
return False
|
||||
|
||||
# 检查是否有更改需要提交
|
||||
status_result = subprocess.run(["git", "status", "--porcelain"], cwd=repo_dir, capture_output=True, text=True)
|
||||
if not status_result.stdout.strip():
|
||||
logger.info(f"No changes in batch {batch_num}")
|
||||
return True
|
||||
|
||||
# 提交批次
|
||||
commit_result = subprocess.run([
|
||||
"git", "commit", "-m", f"Batch {batch_num}: Add {len(file_batch)} files"
|
||||
], cwd=repo_dir, capture_output=True, text=True)
|
||||
|
||||
if commit_result.returncode != 0:
|
||||
logger.warning(f"Commit failed or no changes for batch {batch_num}: {commit_result.stderr}")
|
||||
# 即使没有更改,也可能正常(比如文件没变)
|
||||
|
||||
# 推送批次
|
||||
push_result = subprocess.run([
|
||||
"git", "push", "--verbose", "origin", "main"
|
||||
], cwd=repo_dir, capture_output=True, text=True, timeout=300)
|
||||
|
||||
if push_result.returncode == 0:
|
||||
logger.info(f"✅ Batch {batch_num} pushed successfully")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"❌ Batch {batch_num} push failed: {push_result.stderr}")
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error(f"❌ Batch {batch_num} push timed out")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Batch {batch_num} push failed with error: {str(e)}")
|
||||
return False
|
||||
|
||||
def get_repository_info(self, repo_owner: str, repo_name: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取仓库信息
|
||||
|
||||
Args:
|
||||
repo_owner: 仓库所有者
|
||||
repo_name: 仓库名称
|
||||
|
||||
Returns:
|
||||
仓库信息
|
||||
"""
|
||||
if not self.client:
|
||||
return None
|
||||
|
||||
return self.client.get_repository(repo_owner, repo_name)
|
||||
|
||||
def list_repositories(self, owner: Optional[str] = None) -> Optional[List[Dict[str, Any]]]:
|
||||
"""列出仓库
|
||||
|
||||
Args:
|
||||
owner: 所有者(用户或组织)
|
||||
|
||||
Returns:
|
||||
仓库列表
|
||||
"""
|
||||
if not self.client:
|
||||
return None
|
||||
|
||||
target_owner = owner or self.config.get('default_owner')
|
||||
if not target_owner:
|
||||
return None
|
||||
|
||||
return self.client.list_repositories(target_owner)
|
||||
|
||||
def register_algorithm_from_repo(self, repo_owner: str, repo_name: str, algorithm_id: str) -> bool:
|
||||
"""从仓库注册算法服务
|
||||
|
||||
Args:
|
||||
repo_owner: 仓库所有者
|
||||
repo_name: 仓库名称
|
||||
algorithm_id: 算法ID
|
||||
|
||||
Returns:
|
||||
是否注册成功
|
||||
"""
|
||||
try:
|
||||
# 这里应该实现从仓库注册算法服务的逻辑
|
||||
# 1. 克隆仓库
|
||||
# 2. 扫描仓库中的算法代码
|
||||
# 3. 注册算法服务
|
||||
|
||||
logger.info(f"Algorithm registered from repo: {repo_owner}/{repo_name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to register algorithm from repo: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
# 递归遍历目录中的所有文件
|
||||
for root, dirs, files in os.walk(repo_dir):
|
||||
# 跳过 .git 目录
|
||||
if '.git' in root:
|
||||
continue
|
||||
|
||||
for file in files:
|
||||
file_path = os.path.relpath(os.path.join(root, file), repo_dir)
|
||||
if file_path.startswith('.git'):
|
||||
continue
|
||||
|
||||
full_file_path = os.path.join(root, file)
|
||||
|
||||
# 读取文件内容并进行base64编码
|
||||
try:
|
||||
with open(full_file_path, 'rb') as f:
|
||||
file_content = f.read()
|
||||
encoded_content = base64.b64encode(file_content).decode('utf-8')
|
||||
|
||||
# 使用Gitea API创建或更新文件
|
||||
if self.client:
|
||||
# 移除开头的./,如果有的话
|
||||
clean_path = file_path.lstrip('./\\')
|
||||
result = self.client.create_file(
|
||||
self.config["default_owner"],
|
||||
algorithm_id,
|
||||
clean_path,
|
||||
encoded_content,
|
||||
f"{message} - Upload {clean_path}"
|
||||
)
|
||||
|
||||
if result:
|
||||
logger.info(f"✅ File uploaded via API: {clean_path}")
|
||||
else:
|
||||
logger.error(f"❌ Failed to upload file via API: {clean_path}")
|
||||
return False
|
||||
else:
|
||||
logger.error("❌ Gitea client not initialized")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error processing file {file_path}: {str(e)}")
|
||||
return False
|
||||
|
||||
logger.info(f"✅ All files uploaded successfully via API for algorithm: {algorithm_id}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to upload files via API: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
return False
|
||||
|
||||
|
||||
# 全局Gitea服务实例
|
||||
gitea_service = GiteaService()
|
||||
@@ -140,18 +140,6 @@ class AlgorithmRepository(Base):
|
||||
algorithm = relationship("Algorithm", back_populates="repository", uselist=False)
|
||||
|
||||
|
||||
class ServiceGroup(Base):
|
||||
"""服务分组模型"""
|
||||
__tablename__ = "service_groups"
|
||||
|
||||
id = Column(String, primary_key=True, index=True)
|
||||
name = Column(String, nullable=False, unique=True, index=True) # 分组名称
|
||||
description = Column(Text, default="") # 分组描述
|
||||
status = Column(String, default="active", index=True) # 状态
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
|
||||
class AlgorithmService(Base):
|
||||
"""算法服务模型"""
|
||||
__tablename__ = "algorithm_services"
|
||||
|
||||
@@ -5,10 +5,12 @@ from typing import List, Dict, Any, Optional
|
||||
from pydantic import BaseModel
|
||||
import uuid
|
||||
import os
|
||||
import logging
|
||||
|
||||
from app.config.settings import settings
|
||||
from app.models.models import AlgorithmService, ServiceGroup, AlgorithmRepository
|
||||
from app.models.models import AlgorithmService, AlgorithmRepository, Algorithm, AlgorithmVersion
|
||||
from app.models.database import SessionLocal
|
||||
from app.models.api import ApiEndpoint
|
||||
from app.routes.user import get_current_active_user
|
||||
from app.schemas.user import UserResponse
|
||||
from app.services.project_analyzer import ProjectAnalyzer
|
||||
@@ -17,6 +19,7 @@ from app.services.service_orchestrator import ServiceOrchestrator
|
||||
from app.gitea.service import gitea_service
|
||||
|
||||
router = APIRouter(prefix="/services", tags=["services"])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RegisterServiceRequest(BaseModel):
|
||||
@@ -28,7 +31,7 @@ class RegisterServiceRequest(BaseModel):
|
||||
tech_category: str = "computer_vision"
|
||||
output_type: str = "image"
|
||||
service_type: str = "http"
|
||||
host: str = "0.0.0.0"
|
||||
host: str = "localhost"
|
||||
port: int = 8000
|
||||
timeout: int = 30
|
||||
health_check_path: str = "/health"
|
||||
@@ -89,34 +92,6 @@ class RepositoryAlgorithmsResponse(BaseModel):
|
||||
algorithms: List[Dict[str, Any]]
|
||||
|
||||
|
||||
class ServiceGroupRequest(BaseModel):
|
||||
"""服务分组请求"""
|
||||
name: str
|
||||
description: str = ""
|
||||
|
||||
|
||||
class ServiceGroupResponse(BaseModel):
|
||||
"""服务分组响应"""
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
status: str
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
|
||||
class ServiceGroupListResponse(BaseModel):
|
||||
"""服务分组列表响应"""
|
||||
success: bool
|
||||
groups: List[ServiceGroupResponse]
|
||||
|
||||
|
||||
class ServiceGroupDetailResponse(BaseModel):
|
||||
"""服务分组详情响应"""
|
||||
success: bool
|
||||
group: ServiceGroupResponse
|
||||
|
||||
|
||||
class BatchOperationRequest(BaseModel):
|
||||
"""批量操作请求"""
|
||||
service_ids: List[str]
|
||||
@@ -228,7 +203,62 @@ async def register_service(
|
||||
db.commit()
|
||||
db.refresh(new_service)
|
||||
|
||||
# 6. 返回响应
|
||||
# 7. 自动创建API端点
|
||||
try:
|
||||
algorithm = db.query(Algorithm).filter(Algorithm.name == repo.name).first()
|
||||
if not algorithm:
|
||||
algorithm = Algorithm(
|
||||
id=str(uuid.uuid4()),
|
||||
name=repo.name,
|
||||
description=request.description or f"算法服务: {request.name}",
|
||||
type=request.tech_category,
|
||||
tech_category=request.tech_category,
|
||||
output_type=request.output_type
|
||||
)
|
||||
db.add(algorithm)
|
||||
db.commit()
|
||||
db.refresh(algorithm)
|
||||
|
||||
version = db.query(AlgorithmVersion).filter(
|
||||
AlgorithmVersion.algorithm_id == algorithm.id,
|
||||
AlgorithmVersion.version == request.version
|
||||
).first()
|
||||
if not version:
|
||||
version = AlgorithmVersion(
|
||||
id=str(uuid.uuid4()),
|
||||
algorithm_id=algorithm.id,
|
||||
version=request.version,
|
||||
url=request.service_url if hasattr(request, 'service_url') else ""
|
||||
)
|
||||
db.add(version)
|
||||
db.commit()
|
||||
db.refresh(version)
|
||||
|
||||
api_endpoint = ApiEndpoint(
|
||||
id=str(uuid.uuid4()),
|
||||
name=request.name,
|
||||
description=request.description or f"{request.name} API端点",
|
||||
path=f"/api/v1/algorithms/{algorithm.id}/call",
|
||||
method="POST",
|
||||
algorithm_id=algorithm.id,
|
||||
version_id=version.id,
|
||||
service_id=service_id,
|
||||
requires_auth=False,
|
||||
is_public=True,
|
||||
status="active",
|
||||
config={
|
||||
"service_url": deploy_result["api_url"],
|
||||
"timeout": request.timeout,
|
||||
"health_check_path": request.health_check_path
|
||||
}
|
||||
)
|
||||
db.add(api_endpoint)
|
||||
db.commit()
|
||||
logger.info(f"API端点创建成功: {api_endpoint.name}, 路径: {api_endpoint.path}")
|
||||
except Exception as e:
|
||||
logger.error(f"创建API端点失败: {str(e)}")
|
||||
|
||||
# 8. 返回响应
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务注册成功",
|
||||
@@ -537,6 +567,12 @@ async def delete_service(
|
||||
if not service:
|
||||
raise HTTPException(status_code=404, detail="Service not found")
|
||||
|
||||
# 先删除关联的API端点
|
||||
db.query(ApiEndpoint).filter(ApiEndpoint.service_id == service_id).delete()
|
||||
|
||||
# 获取算法名称,用于后续删除算法记录
|
||||
algorithm_name = service.algorithm_name
|
||||
|
||||
# 获取容器ID和镜像名称
|
||||
container_id = service.config.get("container_id")
|
||||
image_name = f"algorithm-service-{service_id}:{service.version}"
|
||||
@@ -549,6 +585,17 @@ async def delete_service(
|
||||
|
||||
# 删除数据库记录
|
||||
db.delete(service)
|
||||
|
||||
# 删除关联的算法记录(通过算法名称匹配)
|
||||
if algorithm_name:
|
||||
algorithm = db.query(Algorithm).filter(Algorithm.name == algorithm_name).first()
|
||||
if algorithm:
|
||||
# 先删除关联的算法版本
|
||||
db.query(AlgorithmVersion).filter(AlgorithmVersion.algorithm_id == algorithm.id).delete()
|
||||
# 再删除算法记录
|
||||
db.query(AlgorithmCall).filter(AlgorithmCall.algorithm_id == algorithm.id).delete()
|
||||
db.delete(algorithm)
|
||||
|
||||
db.commit()
|
||||
|
||||
# 返回响应
|
||||
@@ -677,202 +724,6 @@ async def get_repository_algorithms(
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
# 服务分组管理API
|
||||
|
||||
@router.post("/groups", status_code=status.HTTP_201_CREATED)
|
||||
async def create_service_group(
|
||||
request: ServiceGroupRequest,
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""创建服务分组"""
|
||||
# 检查用户权限
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 生成唯一ID
|
||||
group_id = str(uuid.uuid4())
|
||||
|
||||
# 创建分组实例
|
||||
group = ServiceGroup(
|
||||
id=group_id,
|
||||
name=request.name,
|
||||
description=request.description
|
||||
)
|
||||
|
||||
# 保存到数据库
|
||||
db.add(group)
|
||||
db.commit()
|
||||
db.refresh(group)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务分组创建成功",
|
||||
"group": {
|
||||
"id": group.id,
|
||||
"name": group.name,
|
||||
"description": group.description,
|
||||
"status": group.status,
|
||||
"created_at": group.created_at.isoformat(),
|
||||
"updated_at": group.updated_at.isoformat()
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/groups", response_model=ServiceGroupListResponse)
|
||||
async def list_service_groups(
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务分组列表"""
|
||||
# 检查用户权限
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询分组列表
|
||||
groups = db.query(ServiceGroup).all()
|
||||
|
||||
# 转换为响应格式
|
||||
group_list = []
|
||||
for group in groups:
|
||||
group_list.append(ServiceGroupResponse(
|
||||
id=group.id,
|
||||
name=group.name,
|
||||
description=group.description,
|
||||
status=group.status,
|
||||
created_at=group.created_at.isoformat(),
|
||||
updated_at=group.updated_at.isoformat()
|
||||
))
|
||||
|
||||
return ServiceGroupListResponse(
|
||||
success=True,
|
||||
groups=group_list
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.get("/groups/{group_id}", response_model=ServiceGroupDetailResponse)
|
||||
async def get_service_group(
|
||||
group_id: str,
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""获取服务分组详情"""
|
||||
# 检查用户权限
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询分组
|
||||
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
|
||||
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="Service group not found")
|
||||
|
||||
return ServiceGroupDetailResponse(
|
||||
success=True,
|
||||
group=ServiceGroupResponse(
|
||||
id=group.id,
|
||||
name=group.name,
|
||||
description=group.description,
|
||||
status=group.status,
|
||||
created_at=group.created_at.isoformat(),
|
||||
updated_at=group.updated_at.isoformat()
|
||||
)
|
||||
)
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.put("/groups/{group_id}")
|
||||
async def update_service_group(
|
||||
group_id: str,
|
||||
request: ServiceGroupRequest,
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""更新服务分组"""
|
||||
# 检查用户权限
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询分组
|
||||
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
|
||||
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="Service group not found")
|
||||
|
||||
# 更新分组信息
|
||||
group.name = request.name
|
||||
group.description = request.description
|
||||
|
||||
# 保存到数据库
|
||||
db.commit()
|
||||
db.refresh(group)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务分组更新成功",
|
||||
"group": {
|
||||
"id": group.id,
|
||||
"name": group.name,
|
||||
"description": group.description,
|
||||
"status": group.status,
|
||||
"created_at": group.created_at.isoformat(),
|
||||
"updated_at": group.updated_at.isoformat()
|
||||
}
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.delete("/groups/{group_id}")
|
||||
async def delete_service_group(
|
||||
group_id: str,
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""删除服务分组"""
|
||||
# 检查用户权限
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="Insufficient permissions")
|
||||
|
||||
# 创建数据库会话
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 查询分组
|
||||
group = db.query(ServiceGroup).filter(ServiceGroup.id == group_id).first()
|
||||
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="Service group not found")
|
||||
|
||||
# 检查分组是否有服务
|
||||
services_count = db.query(AlgorithmService).filter(AlgorithmService.group_id == group_id).count()
|
||||
if services_count > 0:
|
||||
raise HTTPException(status_code=400, detail=f"该分组下还有{services_count}个服务,无法删除")
|
||||
|
||||
# 删除分组
|
||||
db.delete(group)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "服务分组删除成功",
|
||||
"group_id": group_id
|
||||
}
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# 批量服务操作API
|
||||
|
||||
@router.post("/batch/start")
|
||||
@@ -1230,3 +1081,85 @@ async def call_service(
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/sync-api-endpoints")
|
||||
async def sync_api_endpoints(
|
||||
current_user: UserResponse = Depends(get_current_active_user)
|
||||
):
|
||||
"""同步所有服务到API端点"""
|
||||
if current_user.role_name != "admin":
|
||||
raise HTTPException(status_code=403, detail="权限不足")
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
services = db.query(AlgorithmService).all()
|
||||
synced_count = 0
|
||||
|
||||
for service in services:
|
||||
existing_endpoint = db.query(ApiEndpoint).filter(
|
||||
(ApiEndpoint.service_id == service.service_id) |
|
||||
(ApiEndpoint.path == f"/api/v1/algorithms/{service.algorithm_name}/call")
|
||||
).first()
|
||||
|
||||
if existing_endpoint:
|
||||
continue
|
||||
|
||||
algorithm = db.query(Algorithm).filter(Algorithm.name == service.algorithm_name).first()
|
||||
if not algorithm:
|
||||
algorithm = Algorithm(
|
||||
id=str(uuid.uuid4()),
|
||||
name=service.algorithm_name,
|
||||
description=f"算法服务: {service.name}",
|
||||
type=service.tech_category or "computer_vision",
|
||||
tech_category=service.tech_category or "computer_vision",
|
||||
output_type=service.output_type or "image"
|
||||
)
|
||||
db.add(algorithm)
|
||||
db.commit()
|
||||
db.refresh(algorithm)
|
||||
|
||||
version = db.query(AlgorithmVersion).filter(
|
||||
AlgorithmVersion.algorithm_id == algorithm.id
|
||||
).first()
|
||||
if not version:
|
||||
version = AlgorithmVersion(
|
||||
id=str(uuid.uuid4()),
|
||||
algorithm_id=algorithm.id,
|
||||
version=service.version or "1.0.0",
|
||||
url=service.api_url
|
||||
)
|
||||
db.add(version)
|
||||
db.commit()
|
||||
db.refresh(version)
|
||||
|
||||
api_endpoint = ApiEndpoint(
|
||||
id=str(uuid.uuid4()),
|
||||
name=service.name,
|
||||
description=f"{service.name} API端点",
|
||||
path=f"/api/v1/algorithms/{algorithm.id}/call/{service.service_id[:8]}",
|
||||
method="POST",
|
||||
algorithm_id=algorithm.id,
|
||||
version_id=version.id,
|
||||
service_id=service.service_id,
|
||||
requires_auth=False,
|
||||
is_public=True,
|
||||
status=service.status or "active",
|
||||
config={
|
||||
"service_url": service.api_url,
|
||||
"timeout": service.config.get("timeout") if service.config else 30
|
||||
}
|
||||
)
|
||||
db.add(api_endpoint)
|
||||
synced_count += 1
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"同步完成,共同步 {synced_count} 个API端点"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"同步API端点失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"同步失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
@@ -4,15 +4,19 @@ from typing import Optional, Tuple
|
||||
import io
|
||||
import os
|
||||
import logging
|
||||
import shutil
|
||||
|
||||
from app.config.settings import settings
|
||||
|
||||
|
||||
class MinioClient:
|
||||
"""MinIO客户端类"""
|
||||
"""MinIO客户端类,支持本地存储作为备选"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化MinIO客户端"""
|
||||
self.local_storage_path = "data_storage/local_uploads"
|
||||
os.makedirs(self.local_storage_path, exist_ok=True)
|
||||
|
||||
try:
|
||||
self.client = Minio(
|
||||
settings.MINIO_ENDPOINT,
|
||||
@@ -21,16 +25,24 @@ class MinioClient:
|
||||
secure=settings.MINIO_SECURE
|
||||
)
|
||||
self.bucket_name = settings.MINIO_BUCKET_NAME
|
||||
self.is_connected = True # 先设置为True,这样在调用其他方法时不会报错
|
||||
|
||||
# 测试真实连接
|
||||
self.client.list_buckets()
|
||||
self.is_connected = True
|
||||
logging.info("MinIO connected successfully")
|
||||
|
||||
# 确保存储桶存在
|
||||
self._ensure_bucket_exists()
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to connect to MinIO: {e}. Running in offline mode.")
|
||||
logging.warning(f"Failed to connect to MinIO: {e}. Using local storage.")
|
||||
self.client = None
|
||||
self.bucket_name = settings.MINIO_BUCKET_NAME
|
||||
self.is_connected = False
|
||||
|
||||
def _get_local_path(self, object_name: str) -> str:
|
||||
"""获取本地存储路径"""
|
||||
return os.path.join(self.local_storage_path, object_name)
|
||||
|
||||
def _ensure_bucket_exists(self):
|
||||
"""确保存储桶存在"""
|
||||
if not self.is_connected:
|
||||
@@ -60,24 +72,32 @@ class MinioClient:
|
||||
return False
|
||||
|
||||
def upload_from_bytes(self, data: bytes, object_name: str) -> bool:
|
||||
"""从字节数据上传文件"""
|
||||
if not self.is_connected:
|
||||
logging.warning("MinIO is not connected. Upload skipped.")
|
||||
return False
|
||||
"""从字节数据上传文件,优先使用MinIO,失败则使用本地存储"""
|
||||
if self.is_connected:
|
||||
try:
|
||||
import io
|
||||
file_obj = io.BytesIO(data)
|
||||
self.client.put_object(
|
||||
self.bucket_name,
|
||||
object_name,
|
||||
file_obj,
|
||||
length=len(data),
|
||||
part_size=10*1024*1024
|
||||
)
|
||||
return True
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO upload error: {e}, falling back to local storage")
|
||||
|
||||
# 使用本地存储作为备选
|
||||
try:
|
||||
import io
|
||||
file_obj = io.BytesIO(data)
|
||||
self.client.put_object(
|
||||
self.bucket_name,
|
||||
object_name,
|
||||
file_obj,
|
||||
length=len(data),
|
||||
part_size=10*1024*1024
|
||||
)
|
||||
local_path = self._get_local_path(object_name)
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
with open(local_path, 'wb') as f:
|
||||
f.write(data)
|
||||
logging.info(f"File saved to local storage: {local_path}")
|
||||
return True
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO upload error: {e}")
|
||||
except Exception as e:
|
||||
logging.error(f"Local storage save error: {e}")
|
||||
return False
|
||||
|
||||
def upload_fileobj(self, file_obj: io.BytesIO, object_name: str, content_type: str = "application/octet-stream") -> bool:
|
||||
@@ -118,38 +138,54 @@ class MinioClient:
|
||||
return False
|
||||
|
||||
def get_object(self, object_name: str) -> Optional[bytes]:
|
||||
"""获取对象内容"""
|
||||
if not self.is_connected:
|
||||
logging.warning("MinIO is not connected. Get object skipped.")
|
||||
return None
|
||||
"""获取对象内容,优先使用MinIO,失败则使用本地存储"""
|
||||
if self.is_connected:
|
||||
try:
|
||||
response = self.client.get_object(
|
||||
self.bucket_name,
|
||||
object_name
|
||||
)
|
||||
data = response.read()
|
||||
response.close()
|
||||
response.release_conn()
|
||||
return data
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO get object error: {e}, falling back to local storage")
|
||||
|
||||
# 使用本地存储作为备选
|
||||
try:
|
||||
response = self.client.get_object(
|
||||
self.bucket_name,
|
||||
object_name
|
||||
)
|
||||
data = response.read()
|
||||
response.close()
|
||||
response.release_conn()
|
||||
return data
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO get object error: {e}")
|
||||
local_path = self._get_local_path(object_name)
|
||||
if os.path.exists(local_path):
|
||||
with open(local_path, 'rb') as f:
|
||||
return f.read()
|
||||
else:
|
||||
logging.warning(f"File not found in local storage: {local_path}")
|
||||
return None
|
||||
except Exception as e:
|
||||
logging.error(f"Local storage get error: {e}")
|
||||
return None
|
||||
|
||||
def delete_object(self, object_name: str) -> bool:
|
||||
"""删除对象"""
|
||||
if not self.is_connected:
|
||||
logging.warning("MinIO is not connected. Delete object skipped.")
|
||||
return False
|
||||
"""删除对象,优先使用MinIO,失败则使用本地存储"""
|
||||
if self.is_connected:
|
||||
try:
|
||||
self.client.remove_object(
|
||||
self.bucket_name,
|
||||
object_name
|
||||
)
|
||||
return True
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO delete error: {e}")
|
||||
|
||||
# 使用本地存储作为备选
|
||||
try:
|
||||
self.client.remove_object(
|
||||
self.bucket_name,
|
||||
object_name
|
||||
)
|
||||
return True
|
||||
except S3Error as e:
|
||||
logging.warning(f"MinIO delete error: {e}")
|
||||
local_path = self._get_local_path(object_name)
|
||||
if os.path.exists(local_path):
|
||||
os.remove(local_path)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logging.error(f"Local storage delete error: {e}")
|
||||
return False
|
||||
|
||||
def list_objects(self, prefix: str = "") -> list:
|
||||
|
||||
4266
backend/uvicorn.log
4266
backend/uvicorn.log
File diff suppressed because it is too large
Load Diff
@@ -1,241 +0,0 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
||||
in a Python virtual environment. Makes use of the command line switches as
|
||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
||||
|
||||
.Parameter VenvDir
|
||||
Path to the directory that contains the virtual environment to activate. The
|
||||
default value for this is the parent of the directory that the Activate.ps1
|
||||
script is located within.
|
||||
|
||||
.Parameter Prompt
|
||||
The prompt prefix to display when this virtual environment is activated. By
|
||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
||||
|
||||
.Example
|
||||
Activate.ps1
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Verbose
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and shows extra information about the activation as it executes.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
||||
Activates the Python virtual environment located in the specified location.
|
||||
|
||||
.Example
|
||||
Activate.ps1 -Prompt "MyPython"
|
||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$VenvDir,
|
||||
[Parameter(Mandatory = $false)]
|
||||
[String]
|
||||
$Prompt
|
||||
)
|
||||
|
||||
<# Function declarations --------------------------------------------------- #>
|
||||
|
||||
<#
|
||||
.Synopsis
|
||||
Remove all shell session elements added by the Activate script, including the
|
||||
addition of the virtual environment's Python executable from the beginning of
|
||||
the PATH variable.
|
||||
|
||||
.Parameter NonDestructive
|
||||
If present, do not remove this function from the global namespace for the
|
||||
session.
|
||||
|
||||
#>
|
||||
function global:deactivate ([switch]$NonDestructive) {
|
||||
# Revert to original values
|
||||
|
||||
# The prior prompt:
|
||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
|
||||
# The prior PYTHONHOME:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
}
|
||||
|
||||
# The prior PATH:
|
||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
||||
}
|
||||
|
||||
# Just remove the VIRTUAL_ENV altogether:
|
||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
||||
Remove-Item -Path env:VIRTUAL_ENV
|
||||
}
|
||||
|
||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
||||
}
|
||||
|
||||
# Leave deactivate function in the global namespace if requested:
|
||||
if (-not $NonDestructive) {
|
||||
Remove-Item -Path function:deactivate
|
||||
}
|
||||
}
|
||||
|
||||
<#
|
||||
.Description
|
||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
||||
given folder, and returns them in a map.
|
||||
|
||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
||||
then it is considered a `key = value` line. The left hand string is the key,
|
||||
the right hand is the value.
|
||||
|
||||
If the value starts with a `'` or a `"` then the first and last character is
|
||||
stripped from the value before being captured.
|
||||
|
||||
.Parameter ConfigDir
|
||||
Path to the directory that contains the `pyvenv.cfg` file.
|
||||
#>
|
||||
function Get-PyVenvConfig(
|
||||
[String]
|
||||
$ConfigDir
|
||||
) {
|
||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
||||
|
||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
||||
|
||||
# An empty map will be returned if no config file is found.
|
||||
$pyvenvConfig = @{ }
|
||||
|
||||
if ($pyvenvConfigPath) {
|
||||
|
||||
Write-Verbose "File exists, parse `key = value` lines"
|
||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
||||
|
||||
$pyvenvConfigContent | ForEach-Object {
|
||||
$keyval = $PSItem -split "\s*=\s*", 2
|
||||
if ($keyval[0] -and $keyval[1]) {
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
$pyvenvConfig[$keyval[0]] = $val
|
||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
||||
}
|
||||
}
|
||||
}
|
||||
return $pyvenvConfig
|
||||
}
|
||||
|
||||
|
||||
<# Begin Activate script --------------------------------------------------- #>
|
||||
|
||||
# Determine the containing directory of this script
|
||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
||||
|
||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
|
||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
||||
# First, get the location of the virtual environment, it might not be
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
}
|
||||
|
||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
||||
# as `prompt`.
|
||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
|
||||
# Next, set the prompt from the command line, or the config file, or
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
$Prompt = $pyvenvCfg['prompt'];
|
||||
}
|
||||
else {
|
||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)"
|
||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
||||
}
|
||||
}
|
||||
|
||||
Write-Verbose "Prompt = '$Prompt'"
|
||||
Write-Verbose "VenvDir='$VenvDir'"
|
||||
|
||||
# Deactivate any currently active virtual environment, but leave the
|
||||
# deactivate function in place.
|
||||
deactivate -nondestructive
|
||||
|
||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
||||
# that there is an activated venv.
|
||||
$env:VIRTUAL_ENV = $VenvDir
|
||||
|
||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
||||
|
||||
Write-Verbose "Setting prompt to '$Prompt'"
|
||||
|
||||
# Set the prompt to include the env name
|
||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
||||
|
||||
function global:prompt {
|
||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
||||
_OLD_VIRTUAL_PROMPT
|
||||
}
|
||||
}
|
||||
|
||||
# Clear PYTHONHOME
|
||||
if (Test-Path -Path Env:PYTHONHOME) {
|
||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
||||
Remove-Item -Path Env:PYTHONHOME
|
||||
}
|
||||
|
||||
# Add the venv to the PATH
|
||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
||||
@@ -1,66 +0,0 @@
|
||||
# This file must be used with "source bin/activate" *from bash*
|
||||
# you cannot run it directly
|
||||
|
||||
deactivate () {
|
||||
# reset old environment variables
|
||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||
export PATH
|
||||
unset _OLD_VIRTUAL_PATH
|
||||
fi
|
||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||
export PYTHONHOME
|
||||
unset _OLD_VIRTUAL_PYTHONHOME
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
|
||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||
export PS1
|
||||
unset _OLD_VIRTUAL_PS1
|
||||
fi
|
||||
|
||||
unset VIRTUAL_ENV
|
||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
||||
# Self destruct!
|
||||
unset -f deactivate
|
||||
fi
|
||||
}
|
||||
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
export PATH
|
||||
|
||||
# unset PYTHONHOME if set
|
||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||
unset PYTHONHOME
|
||||
fi
|
||||
|
||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||
PS1="(venv39) ${PS1:-}"
|
||||
export PS1
|
||||
fi
|
||||
|
||||
# This should detect bash and zsh, which have a hash command that must
|
||||
# be called to get it to forget past commands. Without forgetting
|
||||
# past commands the $PATH changes we made may not be respected
|
||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||
hash -r 2> /dev/null
|
||||
fi
|
||||
@@ -1,25 +0,0 @@
|
||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||
# You cannot run it directly.
|
||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||
|
||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
|
||||
|
||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||
|
||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
set prompt = "(venv39) $prompt"
|
||||
endif
|
||||
|
||||
alias pydoc python -m pydoc
|
||||
|
||||
rehash
|
||||
@@ -1,64 +0,0 @@
|
||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
||||
# (https://fishshell.com/); you cannot run it directly.
|
||||
|
||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
||||
# reset old environment variables
|
||||
if test -n "$_OLD_VIRTUAL_PATH"
|
||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||
set -e _OLD_VIRTUAL_PATH
|
||||
end
|
||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||
end
|
||||
|
||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||
functions -e fish_prompt
|
||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||
functions -c _old_fish_prompt fish_prompt
|
||||
functions -e _old_fish_prompt
|
||||
end
|
||||
|
||||
set -e VIRTUAL_ENV
|
||||
if test "$argv[1]" != "nondestructive"
|
||||
# Self-destruct!
|
||||
functions -e deactivate
|
||||
end
|
||||
end
|
||||
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
|
||||
# Unset PYTHONHOME if set.
|
||||
if set -q PYTHONHOME
|
||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||
set -e PYTHONHOME
|
||||
end
|
||||
|
||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
# fish uses a function instead of an env var to generate the prompt.
|
||||
|
||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
||||
functions -c fish_prompt _old_fish_prompt
|
||||
|
||||
# With the original prompt function renamed, we can override with our own.
|
||||
function fish_prompt
|
||||
# Save the return status of the last command.
|
||||
set -l old_status $status
|
||||
|
||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
||||
printf "%s%s%s" (set_color 4B8BBE) "(venv39) " (set_color normal)
|
||||
|
||||
# Restore the return status of the previous command.
|
||||
echo "exit $old_status" | .
|
||||
# Output the original/"old" prompt.
|
||||
_old_fish_prompt
|
||||
end
|
||||
|
||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||
end
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from distro.distro import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from dotenv.__main__ import cli
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(cli())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from email_validator.__main__ import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from fastapi.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from httpx import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from charset_normalizer.cli import cli_detect
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(cli_detect())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from openai.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.cli import decrypt
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(decrypt())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.cli import encrypt
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(encrypt())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.cli import keygen
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(keygen())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.util import private_to_public
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(private_to_public())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.cli import sign
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(sign())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from rsa.cli import verify
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(verify())
|
||||
@@ -1 +0,0 @@
|
||||
python3.9
|
||||
@@ -1 +0,0 @@
|
||||
python3.9
|
||||
@@ -1 +0,0 @@
|
||||
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/bin/python3.9
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from tqdm.cli import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/Users/duguoyou/MLFlow/algorithm-showcase/backend/venv39/bin/python3.9
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
from uvicorn.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
@@ -1,235 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/AES.py : AES
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer,
|
||||
c_size_t, c_uint8_ptr)
|
||||
|
||||
from Crypto.Util import _cpu_features
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
MODE_ECB = 1 #: Electronic Code Book (:ref:`ecb_mode`)
|
||||
MODE_CBC = 2 #: Cipher-Block Chaining (:ref:`cbc_mode`)
|
||||
MODE_CFB = 3 #: Cipher Feedback (:ref:`cfb_mode`)
|
||||
MODE_OFB = 5 #: Output Feedback (:ref:`ofb_mode`)
|
||||
MODE_CTR = 6 #: Counter mode (:ref:`ctr_mode`)
|
||||
MODE_OPENPGP = 7 #: OpenPGP mode (:ref:`openpgp_mode`)
|
||||
MODE_CCM = 8 #: Counter with CBC-MAC (:ref:`ccm_mode`)
|
||||
MODE_EAX = 9 #: :ref:`eax_mode`
|
||||
MODE_SIV = 10 #: Synthetic Initialization Vector (:ref:`siv_mode`)
|
||||
MODE_GCM = 11 #: Galois Counter Mode (:ref:`gcm_mode`)
|
||||
MODE_OCB = 12 #: Offset Code Book (:ref:`ocb_mode`)
|
||||
MODE_KW = 13 #: Key Wrap (:ref:`kw_mode`)
|
||||
MODE_KWP = 14 #: Key Wrap with Padding (:ref:`kwp_mode`)
|
||||
|
||||
_cproto = """
|
||||
int AES_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
void **pResult);
|
||||
int AES_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int AES_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int AES_stop_operation(void *state);
|
||||
"""
|
||||
|
||||
|
||||
# Load portable AES
|
||||
_raw_aes_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_aes",
|
||||
_cproto)
|
||||
|
||||
# Try to load AES with AES NI instructions
|
||||
try:
|
||||
_raw_aesni_lib = None
|
||||
if _cpu_features.have_aes_ni():
|
||||
_raw_aesni_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_aesni",
|
||||
_cproto.replace("AES",
|
||||
"AESNI"))
|
||||
# _raw_aesni may not have been compiled in
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a handle to a low-level
|
||||
base cipher. It will absorb named parameters in the process."""
|
||||
|
||||
use_aesni = dict_parameters.pop("use_aesni", True)
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect AES key length (%d bytes)" % len(key))
|
||||
|
||||
if use_aesni and _raw_aesni_lib:
|
||||
start_operation = _raw_aesni_lib.AESNI_start_operation
|
||||
stop_operation = _raw_aesni_lib.AESNI_stop_operation
|
||||
else:
|
||||
start_operation = _raw_aes_lib.AES_start_operation
|
||||
stop_operation = _raw_aes_lib.AES_stop_operation
|
||||
|
||||
cipher = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
cipher.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the AES cipher"
|
||||
% result)
|
||||
return SmartPointer(cipher.get(), stop_operation)
|
||||
|
||||
|
||||
def _derive_Poly1305_key_pair(key, nonce):
|
||||
"""Derive a tuple (r, s, nonce) for a Poly1305 MAC.
|
||||
|
||||
If nonce is ``None``, a new 16-byte nonce is generated.
|
||||
"""
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("Poly1305 with AES requires a 32-byte key")
|
||||
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(16)
|
||||
elif len(nonce) != 16:
|
||||
raise ValueError("Poly1305 with AES requires a 16-byte nonce")
|
||||
|
||||
s = new(key[:16], MODE_ECB).encrypt(nonce)
|
||||
return key[16:], s, nonce
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new AES cipher.
|
||||
|
||||
Args:
|
||||
key(bytes/bytearray/memoryview):
|
||||
The secret key to use in the symmetric cipher.
|
||||
|
||||
It must be 16 (*AES-128)*, 24 (*AES-192*) or 32 (*AES-256*) bytes long.
|
||||
|
||||
For ``MODE_SIV`` only, it doubles to 32, 48, or 64 bytes.
|
||||
mode (a ``MODE_*`` constant):
|
||||
The chaining mode to use for encryption or decryption.
|
||||
If in doubt, use ``MODE_EAX``.
|
||||
|
||||
Keyword Args:
|
||||
iv (bytes/bytearray/memoryview):
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 16 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 16 bytes long for encryption
|
||||
and 18 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
nonce (bytes/bytearray/memoryview):
|
||||
(Only applicable for ``MODE_CCM``, ``MODE_EAX``, ``MODE_GCM``,
|
||||
``MODE_SIV``, ``MODE_OCB``, and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key (except possibly for ``MODE_SIV``, see below).
|
||||
|
||||
For ``MODE_EAX``, ``MODE_GCM`` and ``MODE_SIV`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CCM``, its length must be in the range **[7..13]**.
|
||||
Bear in mind that with CCM there is a trade-off between nonce
|
||||
length and maximum message size. Recommendation: **11** bytes.
|
||||
|
||||
For ``MODE_OCB``, its length must be in the range **[1..15]**
|
||||
(recommended: **15**).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..15]**
|
||||
(recommended: **8**).
|
||||
|
||||
For ``MODE_SIV``, the nonce is optional, if it is not specified,
|
||||
then no nonce is being used, which renders the encryption
|
||||
deterministic.
|
||||
|
||||
If not provided, for modes other than ``MODE_SIV``, a random
|
||||
byte string of the recommended length is used (you must then
|
||||
read its value with the :attr:`nonce` attribute).
|
||||
|
||||
segment_size (integer):
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
mac_len (integer):
|
||||
(Only ``MODE_EAX``, ``MODE_GCM``, ``MODE_OCB``, ``MODE_CCM``)
|
||||
Length of the authentication tag, in bytes.
|
||||
|
||||
It must be even and in the range **[4..16]**.
|
||||
The recommended value (and the default, if not specified) is **16**.
|
||||
|
||||
msg_len (integer):
|
||||
(Only ``MODE_CCM``). Length of the message to (de)cipher.
|
||||
If not specified, ``encrypt`` must be called with the entire message.
|
||||
Similarly, ``decrypt`` can only be called once.
|
||||
|
||||
assoc_len (integer):
|
||||
(Only ``MODE_CCM``). Length of the associated data.
|
||||
If not specified, all associated data is buffered internally,
|
||||
which may represent a problem for very large messages.
|
||||
|
||||
initial_value (integer or bytes/bytearray/memoryview):
|
||||
(Only ``MODE_CTR``).
|
||||
The initial value for the counter. If not present, the cipher will
|
||||
start counting from 0. The value is incremented by one for each block.
|
||||
The counter number is encoded in big endian mode.
|
||||
|
||||
counter (object):
|
||||
(Only ``MODE_CTR``).
|
||||
Instance of ``Crypto.Util.Counter``, which allows full customization
|
||||
of the counter block. This parameter is incompatible to both ``nonce``
|
||||
and ``initial_value``.
|
||||
|
||||
use_aesni: (boolean):
|
||||
Use Intel AES-NI hardware extensions (default: use if available).
|
||||
|
||||
Returns:
|
||||
an AES object, of the applicable mode.
|
||||
"""
|
||||
|
||||
kwargs["add_aes_modes"] = True
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 16
|
||||
# Size of a key (in bytes)
|
||||
key_size = (16, 24, 32)
|
||||
@@ -1,156 +0,0 @@
|
||||
from typing import Dict, Optional, Tuple, Union, overload
|
||||
from typing_extensions import Literal
|
||||
|
||||
Buffer=bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_ccm import CcmMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
from Crypto.Cipher._mode_gcm import GcmMode
|
||||
from Crypto.Cipher._mode_siv import SivMode
|
||||
from Crypto.Cipher._mode_ocb import OcbMode
|
||||
|
||||
MODE_ECB: Literal[1]
|
||||
MODE_CBC: Literal[2]
|
||||
MODE_CFB: Literal[3]
|
||||
MODE_OFB: Literal[5]
|
||||
MODE_CTR: Literal[6]
|
||||
MODE_OPENPGP: Literal[7]
|
||||
MODE_CCM: Literal[8]
|
||||
MODE_EAX: Literal[9]
|
||||
MODE_SIV: Literal[10]
|
||||
MODE_GCM: Literal[11]
|
||||
MODE_OCB: Literal[12]
|
||||
|
||||
# MODE_ECB
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[1],
|
||||
use_aesni : bool = ...) -> \
|
||||
EcbMode: ...
|
||||
|
||||
# MODE_CBC
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[2],
|
||||
iv : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CbcMode: ...
|
||||
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[2],
|
||||
IV : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CbcMode: ...
|
||||
|
||||
# MODE_CFB
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[3],
|
||||
iv : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CfbMode: ...
|
||||
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[3],
|
||||
IV : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CfbMode: ...
|
||||
|
||||
# MODE_OFB
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[5],
|
||||
iv : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
OfbMode: ...
|
||||
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[5],
|
||||
IV : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
OfbMode: ...
|
||||
|
||||
# MODE_CTR
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[6],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CtrMode: ...
|
||||
|
||||
# MODE_OPENPGP
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[7],
|
||||
iv : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
OpenPgpMode: ...
|
||||
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[7],
|
||||
IV : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
OpenPgpMode: ...
|
||||
|
||||
# MODE_CCM
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[8],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
mac_len : int = ...,
|
||||
assoc_len : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
CcmMode: ...
|
||||
|
||||
# MODE_EAX
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[9],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
mac_len : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
EaxMode: ...
|
||||
|
||||
# MODE_GCM
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[10],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
SivMode: ...
|
||||
|
||||
# MODE_SIV
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[11],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
mac_len : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
GcmMode: ...
|
||||
|
||||
# MODE_OCB
|
||||
@overload
|
||||
def new(key: Buffer,
|
||||
mode: Literal[12],
|
||||
nonce : Optional[Buffer] = ...,
|
||||
mac_len : int = ...,
|
||||
use_aesni : bool = ...) -> \
|
||||
OcbMode: ...
|
||||
|
||||
|
||||
block_size: int
|
||||
key_size: Tuple[int, int, int]
|
||||
@@ -1,175 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/ARC2.py : ARC2.py
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
"""
|
||||
Module's constants for the modes of operation supported with ARC2:
|
||||
|
||||
:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
|
||||
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
|
||||
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
|
||||
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
|
||||
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
|
||||
:var MODE_OPENPGP: :ref:`OpenPGP Mode <openpgp_mode>`
|
||||
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util.py3compat import byte_string
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer,
|
||||
c_size_t, c_uint8_ptr)
|
||||
|
||||
_raw_arc2_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_arc2",
|
||||
"""
|
||||
int ARC2_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
size_t effective_key_len,
|
||||
void **pResult);
|
||||
int ARC2_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int ARC2_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int ARC2_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a handle to a low-level
|
||||
base cipher. It will absorb named parameters in the process."""
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
effective_keylen = dict_parameters.pop("effective_keylen", 1024)
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect ARC2 key length (%d bytes)" % len(key))
|
||||
|
||||
if not (40 <= effective_keylen <= 1024):
|
||||
raise ValueError("'effective_key_len' must be at least 40 and no larger than 1024 "
|
||||
"(not %d)" % effective_keylen)
|
||||
|
||||
start_operation = _raw_arc2_lib.ARC2_start_operation
|
||||
stop_operation = _raw_arc2_lib.ARC2_stop_operation
|
||||
|
||||
cipher = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
c_size_t(effective_keylen),
|
||||
cipher.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the ARC2 cipher"
|
||||
% result)
|
||||
|
||||
return SmartPointer(cipher.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new RC2 cipher.
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
Its length can vary from 5 to 128 bytes; the actual search space
|
||||
(and the cipher strength) can be reduced with the ``effective_keylen`` parameter.
|
||||
:type key: bytes, bytearray, memoryview
|
||||
|
||||
:param mode:
|
||||
The chaining mode to use for encryption or decryption.
|
||||
:type mode: One of the supported ``MODE_*`` constants
|
||||
|
||||
:Keyword Arguments:
|
||||
* **iv** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 8 bytes long for encryption
|
||||
and 10 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
* **nonce** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_EAX`` and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key.
|
||||
|
||||
For ``MODE_EAX`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..7]**.
|
||||
|
||||
If not provided for ``MODE_EAX``, a random byte string is generated (you
|
||||
can read it back via the ``nonce`` attribute).
|
||||
|
||||
* **effective_keylen** (*integer*) --
|
||||
Optional. Maximum strength in bits of the actual key used by the ARC2 algorithm.
|
||||
If the supplied ``key`` parameter is longer (in bits) of the value specified
|
||||
here, it will be weakened to match it.
|
||||
If not specified, no limitation is applied.
|
||||
|
||||
* **segment_size** (*integer*) --
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
* **mac_len** : (*integer*) --
|
||||
(Only ``MODE_EAX``)
|
||||
Length of the authentication tag, in bytes.
|
||||
It must be no longer than 8 (default).
|
||||
|
||||
* **initial_value** : (*integer*) --
|
||||
(Only ``MODE_CTR``). The initial value for the counter within
|
||||
the counter block. By default it is **0**.
|
||||
|
||||
:Return: an ARC2 object, of the applicable mode.
|
||||
"""
|
||||
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
MODE_ECB = 1
|
||||
MODE_CBC = 2
|
||||
MODE_CFB = 3
|
||||
MODE_OFB = 5
|
||||
MODE_CTR = 6
|
||||
MODE_OPENPGP = 7
|
||||
MODE_EAX = 9
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = range(5, 128 + 1)
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Union, Dict, Iterable, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
|
||||
ARC2Mode = int
|
||||
|
||||
MODE_ECB: ARC2Mode
|
||||
MODE_CBC: ARC2Mode
|
||||
MODE_CFB: ARC2Mode
|
||||
MODE_OFB: ARC2Mode
|
||||
MODE_CTR: ARC2Mode
|
||||
MODE_OPENPGP: ARC2Mode
|
||||
MODE_EAX: ARC2Mode
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: ARC2Mode,
|
||||
iv : Optional[Buffer] = ...,
|
||||
IV : Optional[Buffer] = ...,
|
||||
nonce : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
mac_len : int = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...) -> \
|
||||
Union[EcbMode, CbcMode, CfbMode, OfbMode, CtrMode, OpenPgpMode]: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Iterable[int]
|
||||
@@ -1,136 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/ARC4.py : ARC4
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr)
|
||||
|
||||
|
||||
_raw_arc4_lib = load_pycryptodome_raw_lib("Crypto.Cipher._ARC4", """
|
||||
int ARC4_stream_encrypt(void *rc4State, const uint8_t in[],
|
||||
uint8_t out[], size_t len);
|
||||
int ARC4_stream_init(uint8_t *key, size_t keylen,
|
||||
void **pRc4State);
|
||||
int ARC4_stream_destroy(void *rc4State);
|
||||
""")
|
||||
|
||||
|
||||
class ARC4Cipher:
|
||||
"""ARC4 cipher object. Do not create it directly. Use
|
||||
:func:`Crypto.Cipher.ARC4.new` instead.
|
||||
"""
|
||||
|
||||
def __init__(self, key, *args, **kwargs):
|
||||
"""Initialize an ARC4 cipher object
|
||||
|
||||
See also `new()` at the module level."""
|
||||
|
||||
if len(args) > 0:
|
||||
ndrop = args[0]
|
||||
args = args[1:]
|
||||
else:
|
||||
ndrop = kwargs.pop('drop', 0)
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect ARC4 key length (%d bytes)" %
|
||||
len(key))
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = _raw_arc4_lib.ARC4_stream_init(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
self._state.address_of())
|
||||
if result != 0:
|
||||
raise ValueError("Error %d while creating the ARC4 cipher"
|
||||
% result)
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
_raw_arc4_lib.ARC4_stream_destroy)
|
||||
|
||||
if ndrop > 0:
|
||||
# This is OK even if the cipher is used for decryption,
|
||||
# since encrypt and decrypt are actually the same thing
|
||||
# with ARC4.
|
||||
self.encrypt(b'\x00' * ndrop)
|
||||
|
||||
self.block_size = 1
|
||||
self.key_size = len(key)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""Encrypt a piece of data.
|
||||
|
||||
:param plaintext: The data to encrypt, of any size.
|
||||
:type plaintext: bytes, bytearray, memoryview
|
||||
:returns: the encrypted byte string, of equal length as the
|
||||
plaintext.
|
||||
"""
|
||||
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
result = _raw_arc4_lib.ARC4_stream_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
ciphertext,
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while encrypting with RC4" % result)
|
||||
return get_raw_buffer(ciphertext)
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""Decrypt a piece of data.
|
||||
|
||||
:param ciphertext: The data to decrypt, of any size.
|
||||
:type ciphertext: bytes, bytearray, memoryview
|
||||
:returns: the decrypted byte string, of equal length as the
|
||||
ciphertext.
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.encrypt(ciphertext)
|
||||
except ValueError as e:
|
||||
raise ValueError(str(e).replace("enc", "dec"))
|
||||
|
||||
|
||||
def new(key, *args, **kwargs):
|
||||
"""Create a new ARC4 cipher.
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
Its length must be in the range ``[1..256]``.
|
||||
The recommended length is 16 bytes.
|
||||
:type key: bytes, bytearray, memoryview
|
||||
|
||||
:Keyword Arguments:
|
||||
* *drop* (``integer``) --
|
||||
The amount of bytes to discard from the initial part of the keystream.
|
||||
In fact, such part has been found to be distinguishable from random
|
||||
data (while it shouldn't) and also correlated to key.
|
||||
|
||||
The recommended value is 3072_ bytes. The default value is 0.
|
||||
|
||||
:Return: an `ARC4Cipher` object
|
||||
|
||||
.. _3072: http://eprint.iacr.org/2002/067.pdf
|
||||
"""
|
||||
return ARC4Cipher(key, *args, **kwargs)
|
||||
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 1
|
||||
# Size of a key (in bytes)
|
||||
key_size = range(1, 256+1)
|
||||
@@ -1,16 +0,0 @@
|
||||
from typing import Any, Union, Iterable
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
class ARC4Cipher:
|
||||
block_size: int
|
||||
key_size: int
|
||||
|
||||
def __init__(self, key: Buffer, *args: Any, **kwargs: Any) -> None: ...
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
def decrypt(self, ciphertext: Buffer) -> bytes: ...
|
||||
|
||||
def new(key: Buffer, drop : int = ...) -> ARC4Cipher: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Iterable[int]
|
||||
@@ -1,159 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/Blowfish.py : Blowfish
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
"""
|
||||
Module's constants for the modes of operation supported with Blowfish:
|
||||
|
||||
:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
|
||||
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
|
||||
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
|
||||
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
|
||||
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
|
||||
:var MODE_OPENPGP: :ref:`OpenPGP Mode <openpgp_mode>`
|
||||
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer, c_size_t,
|
||||
c_uint8_ptr)
|
||||
|
||||
_raw_blowfish_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_blowfish",
|
||||
"""
|
||||
int Blowfish_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
void **pResult);
|
||||
int Blowfish_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int Blowfish_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int Blowfish_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a smart pointer to
|
||||
a low-level base cipher. It will absorb named parameters in
|
||||
the process."""
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect Blowfish key length (%d bytes)" % len(key))
|
||||
|
||||
start_operation = _raw_blowfish_lib.Blowfish_start_operation
|
||||
stop_operation = _raw_blowfish_lib.Blowfish_stop_operation
|
||||
|
||||
void_p = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
void_p.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the Blowfish cipher"
|
||||
% result)
|
||||
return SmartPointer(void_p.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new Blowfish cipher
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
Its length can vary from 5 to 56 bytes.
|
||||
:type key: bytes, bytearray, memoryview
|
||||
|
||||
:param mode:
|
||||
The chaining mode to use for encryption or decryption.
|
||||
:type mode: One of the supported ``MODE_*`` constants
|
||||
|
||||
:Keyword Arguments:
|
||||
* **iv** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 8 bytes long for encryption
|
||||
and 10 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
* **nonce** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_EAX`` and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key.
|
||||
|
||||
For ``MODE_EAX`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..7]**.
|
||||
|
||||
If not provided for ``MODE_EAX``, a random byte string is generated (you
|
||||
can read it back via the ``nonce`` attribute).
|
||||
|
||||
* **segment_size** (*integer*) --
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
* **mac_len** : (*integer*) --
|
||||
(Only ``MODE_EAX``)
|
||||
Length of the authentication tag, in bytes.
|
||||
It must be no longer than 8 (default).
|
||||
|
||||
* **initial_value** : (*integer*) --
|
||||
(Only ``MODE_CTR``). The initial value for the counter within
|
||||
the counter block. By default it is **0**.
|
||||
|
||||
:Return: a Blowfish object, of the applicable mode.
|
||||
"""
|
||||
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
MODE_ECB = 1
|
||||
MODE_CBC = 2
|
||||
MODE_CFB = 3
|
||||
MODE_OFB = 5
|
||||
MODE_CTR = 6
|
||||
MODE_OPENPGP = 7
|
||||
MODE_EAX = 9
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = range(4, 56 + 1)
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Union, Dict, Iterable, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
|
||||
BlowfishMode = int
|
||||
|
||||
MODE_ECB: BlowfishMode
|
||||
MODE_CBC: BlowfishMode
|
||||
MODE_CFB: BlowfishMode
|
||||
MODE_OFB: BlowfishMode
|
||||
MODE_CTR: BlowfishMode
|
||||
MODE_OPENPGP: BlowfishMode
|
||||
MODE_EAX: BlowfishMode
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: BlowfishMode,
|
||||
iv : Optional[Buffer] = ...,
|
||||
IV : Optional[Buffer] = ...,
|
||||
nonce : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
mac_len : int = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...) -> \
|
||||
Union[EcbMode, CbcMode, CfbMode, OfbMode, CtrMode, OpenPgpMode]: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Iterable[int]
|
||||
@@ -1,159 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/CAST.py : CAST
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
"""
|
||||
Module's constants for the modes of operation supported with CAST:
|
||||
|
||||
:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
|
||||
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
|
||||
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
|
||||
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
|
||||
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
|
||||
:var MODE_OPENPGP: :ref:`OpenPGP Mode <openpgp_mode>`
|
||||
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util.py3compat import byte_string
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer,
|
||||
c_size_t, c_uint8_ptr)
|
||||
|
||||
_raw_cast_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_cast",
|
||||
"""
|
||||
int CAST_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
void **pResult);
|
||||
int CAST_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CAST_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CAST_stop_operation(void *state);
|
||||
""")
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a handle to a low-level
|
||||
base cipher. It will absorb named parameters in the process."""
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect CAST key length (%d bytes)" % len(key))
|
||||
|
||||
start_operation = _raw_cast_lib.CAST_start_operation
|
||||
stop_operation = _raw_cast_lib.CAST_stop_operation
|
||||
|
||||
cipher = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
cipher.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the CAST cipher"
|
||||
% result)
|
||||
|
||||
return SmartPointer(cipher.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new CAST cipher
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
Its length can vary from 5 to 16 bytes.
|
||||
:type key: bytes, bytearray, memoryview
|
||||
|
||||
:param mode:
|
||||
The chaining mode to use for encryption or decryption.
|
||||
:type mode: One of the supported ``MODE_*`` constants
|
||||
|
||||
:Keyword Arguments:
|
||||
* **iv** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 8 bytes long for encryption
|
||||
and 10 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
* **nonce** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_EAX`` and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key.
|
||||
|
||||
For ``MODE_EAX`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..7]**.
|
||||
|
||||
If not provided for ``MODE_EAX``, a random byte string is generated (you
|
||||
can read it back via the ``nonce`` attribute).
|
||||
|
||||
* **segment_size** (*integer*) --
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
* **mac_len** : (*integer*) --
|
||||
(Only ``MODE_EAX``)
|
||||
Length of the authentication tag, in bytes.
|
||||
It must be no longer than 8 (default).
|
||||
|
||||
* **initial_value** : (*integer*) --
|
||||
(Only ``MODE_CTR``). The initial value for the counter within
|
||||
the counter block. By default it is **0**.
|
||||
|
||||
:Return: a CAST object, of the applicable mode.
|
||||
"""
|
||||
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
MODE_ECB = 1
|
||||
MODE_CBC = 2
|
||||
MODE_CFB = 3
|
||||
MODE_OFB = 5
|
||||
MODE_CTR = 6
|
||||
MODE_OPENPGP = 7
|
||||
MODE_EAX = 9
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = range(5, 16 + 1)
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Union, Dict, Iterable, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
|
||||
CASTMode = int
|
||||
|
||||
MODE_ECB: CASTMode
|
||||
MODE_CBC: CASTMode
|
||||
MODE_CFB: CASTMode
|
||||
MODE_OFB: CASTMode
|
||||
MODE_CTR: CASTMode
|
||||
MODE_OPENPGP: CASTMode
|
||||
MODE_EAX: CASTMode
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: CASTMode,
|
||||
iv : Optional[Buffer] = ...,
|
||||
IV : Optional[Buffer] = ...,
|
||||
nonce : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
mac_len : int = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...) -> \
|
||||
Union[EcbMode, CbcMode, CfbMode, OfbMode, CtrMode, OpenPgpMode]: ...
|
||||
|
||||
block_size: int
|
||||
key_size : Iterable[int]
|
||||
@@ -1,291 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
create_string_buffer,
|
||||
get_raw_buffer, VoidPointer,
|
||||
SmartPointer, c_size_t,
|
||||
c_uint8_ptr, c_ulong,
|
||||
is_writeable_buffer)
|
||||
|
||||
_raw_chacha20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._chacha20",
|
||||
"""
|
||||
int chacha20_init(void **pState,
|
||||
const uint8_t *key,
|
||||
size_t keySize,
|
||||
const uint8_t *nonce,
|
||||
size_t nonceSize);
|
||||
|
||||
int chacha20_destroy(void *state);
|
||||
|
||||
int chacha20_encrypt(void *state,
|
||||
const uint8_t in[],
|
||||
uint8_t out[],
|
||||
size_t len);
|
||||
|
||||
int chacha20_seek(void *state,
|
||||
unsigned long block_high,
|
||||
unsigned long block_low,
|
||||
unsigned offset);
|
||||
|
||||
int hchacha20( const uint8_t key[32],
|
||||
const uint8_t nonce16[16],
|
||||
uint8_t subkey[32]);
|
||||
""")
|
||||
|
||||
|
||||
def _HChaCha20(key, nonce):
|
||||
|
||||
assert(len(key) == 32)
|
||||
assert(len(nonce) == 16)
|
||||
|
||||
subkey = bytearray(32)
|
||||
result = _raw_chacha20_lib.hchacha20(
|
||||
c_uint8_ptr(key),
|
||||
c_uint8_ptr(nonce),
|
||||
c_uint8_ptr(subkey))
|
||||
if result:
|
||||
raise ValueError("Error %d when deriving subkey with HChaCha20" % result)
|
||||
|
||||
return subkey
|
||||
|
||||
|
||||
class ChaCha20Cipher(object):
|
||||
"""ChaCha20 (or XChaCha20) cipher object.
|
||||
Do not create it directly. Use :py:func:`new` instead.
|
||||
|
||||
:var nonce: The nonce with length 8, 12 or 24 bytes
|
||||
:vartype nonce: bytes
|
||||
"""
|
||||
|
||||
block_size = 1
|
||||
|
||||
def __init__(self, key, nonce):
|
||||
"""Initialize a ChaCha20/XChaCha20 cipher object
|
||||
|
||||
See also `new()` at the module level."""
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
|
||||
# XChaCha20 requires a key derivation with HChaCha20
|
||||
# See 2.3 in https://tools.ietf.org/html/draft-arciszewski-xchacha-03
|
||||
if len(nonce) == 24:
|
||||
key = _HChaCha20(key, nonce[:16])
|
||||
nonce = b'\x00' * 4 + nonce[16:]
|
||||
self._name = "XChaCha20"
|
||||
else:
|
||||
self._name = "ChaCha20"
|
||||
nonce = self.nonce
|
||||
|
||||
self._next = ("encrypt", "decrypt")
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = _raw_chacha20_lib.chacha20_init(
|
||||
self._state.address_of(),
|
||||
c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
nonce,
|
||||
c_size_t(len(nonce)))
|
||||
if result:
|
||||
raise ValueError("Error %d instantiating a %s cipher" % (result,
|
||||
self._name))
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
_raw_chacha20_lib.chacha20_destroy)
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt a piece of data.
|
||||
|
||||
Args:
|
||||
plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the ciphertext
|
||||
is written to. If ``None``, the ciphertext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("Cipher object can only be used for decryption")
|
||||
self._next = ("encrypt",)
|
||||
return self._encrypt(plaintext, output)
|
||||
|
||||
def _encrypt(self, plaintext, output):
|
||||
"""Encrypt without FSM checks"""
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = _raw_chacha20_lib.chacha20_encrypt(
|
||||
self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while encrypting with %s" % (result, self._name))
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt a piece of data.
|
||||
|
||||
Args:
|
||||
ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the plaintext
|
||||
is written to. If ``None``, the plaintext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("Cipher object can only be used for encryption")
|
||||
self._next = ("decrypt",)
|
||||
|
||||
try:
|
||||
return self._encrypt(ciphertext, output)
|
||||
except ValueError as e:
|
||||
raise ValueError(str(e).replace("enc", "dec"))
|
||||
|
||||
def seek(self, position):
|
||||
"""Seek to a certain position in the key stream.
|
||||
|
||||
If you want to seek to a certain block,
|
||||
use ``seek(block_number * 64)``.
|
||||
|
||||
Args:
|
||||
position (integer):
|
||||
The absolute position within the key stream, in bytes.
|
||||
"""
|
||||
|
||||
block_number, offset = divmod(position, 64)
|
||||
block_low = block_number & 0xFFFFFFFF
|
||||
block_high = block_number >> 32
|
||||
|
||||
result = _raw_chacha20_lib.chacha20_seek(
|
||||
self._state.get(),
|
||||
c_ulong(block_high),
|
||||
c_ulong(block_low),
|
||||
offset
|
||||
)
|
||||
if result:
|
||||
raise ValueError("Error %d while seeking with %s" % (result, self._name))
|
||||
|
||||
|
||||
def _derive_Poly1305_key_pair(key, nonce):
|
||||
"""Derive a tuple (r, s, nonce) for a Poly1305 MAC.
|
||||
|
||||
If nonce is ``None``, a new 12-byte nonce is generated.
|
||||
"""
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("Poly1305 with ChaCha20 requires a 32-byte key")
|
||||
|
||||
if nonce is None:
|
||||
padded_nonce = nonce = get_random_bytes(12)
|
||||
elif len(nonce) == 8:
|
||||
# See RFC7538, 2.6: [...] ChaCha20 as specified here requires a 96-bit
|
||||
# nonce. So if the provided nonce is only 64-bit, then the first 32
|
||||
# bits of the nonce will be set to a constant number.
|
||||
# This will usually be zero, but for protocols with multiple senders it may be
|
||||
# different for each sender, but should be the same for all
|
||||
# invocations of the function with the same key by a particular
|
||||
# sender.
|
||||
padded_nonce = b'\x00\x00\x00\x00' + nonce
|
||||
elif len(nonce) == 12:
|
||||
padded_nonce = nonce
|
||||
else:
|
||||
raise ValueError("Poly1305 with ChaCha20 requires an 8- or 12-byte nonce")
|
||||
|
||||
rs = new(key=key, nonce=padded_nonce).encrypt(b'\x00' * 32)
|
||||
return rs[:16], rs[16:], nonce
|
||||
|
||||
|
||||
def new(**kwargs):
|
||||
"""Create a new ChaCha20 or XChaCha20 cipher
|
||||
|
||||
Keyword Args:
|
||||
key (bytes/bytearray/memoryview): The secret key to use.
|
||||
It must be 32 bytes long.
|
||||
nonce (bytes/bytearray/memoryview): A mandatory value that
|
||||
must never be reused for any other encryption
|
||||
done with this key.
|
||||
|
||||
For ChaCha20, it must be 8 or 12 bytes long.
|
||||
|
||||
For XChaCha20, it must be 24 bytes long.
|
||||
|
||||
If not provided, 8 bytes will be randomly generated
|
||||
(you can find them back in the ``nonce`` attribute).
|
||||
|
||||
:Return: a :class:`Crypto.Cipher.ChaCha20.ChaCha20Cipher` object
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter %s" % e)
|
||||
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(8)
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("ChaCha20/XChaCha20 key must be 32 bytes long")
|
||||
|
||||
if len(nonce) not in (8, 12, 24):
|
||||
raise ValueError("Nonce must be 8/12 bytes(ChaCha20) or 24 bytes (XChaCha20)")
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters: " + str(kwargs))
|
||||
|
||||
return ChaCha20Cipher(key, nonce)
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 1
|
||||
|
||||
# Size of a key (in bytes)
|
||||
key_size = 32
|
||||
@@ -1,25 +0,0 @@
|
||||
from typing import Union, overload, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
def _HChaCha20(key: Buffer, nonce: Buffer) -> bytearray: ...
|
||||
|
||||
class ChaCha20Cipher:
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
def seek(self, position: int) -> None: ...
|
||||
|
||||
def new(key: Buffer, nonce: Optional[Buffer] = ...) -> ChaCha20Cipher: ...
|
||||
|
||||
block_size: int
|
||||
key_size: int
|
||||
@@ -1,334 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2018, Helder Eijs <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
from binascii import unhexlify
|
||||
|
||||
from Crypto.Cipher import ChaCha20
|
||||
from Crypto.Cipher.ChaCha20 import _HChaCha20
|
||||
from Crypto.Hash import Poly1305, BLAKE2s
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
from Crypto.Util.number import long_to_bytes
|
||||
from Crypto.Util.py3compat import _copy_bytes, bord
|
||||
from Crypto.Util._raw_api import is_buffer
|
||||
|
||||
|
||||
def _enum(**enums):
|
||||
return type('Enum', (), enums)
|
||||
|
||||
|
||||
_CipherStatus = _enum(PROCESSING_AUTH_DATA=1,
|
||||
PROCESSING_CIPHERTEXT=2,
|
||||
PROCESSING_DONE=3)
|
||||
|
||||
|
||||
class ChaCha20Poly1305Cipher(object):
|
||||
"""ChaCha20-Poly1305 and XChaCha20-Poly1305 cipher object.
|
||||
Do not create it directly. Use :py:func:`new` instead.
|
||||
|
||||
:var nonce: The nonce with length 8, 12 or 24 bytes
|
||||
:vartype nonce: byte string
|
||||
"""
|
||||
|
||||
def __init__(self, key, nonce):
|
||||
"""Initialize a ChaCha20-Poly1305 AEAD cipher object
|
||||
|
||||
See also `new()` at the module level."""
|
||||
|
||||
self._next = ("update", "encrypt", "decrypt", "digest",
|
||||
"verify")
|
||||
|
||||
self._authenticator = Poly1305.new(key=key, nonce=nonce, cipher=ChaCha20)
|
||||
|
||||
self._cipher = ChaCha20.new(key=key, nonce=nonce)
|
||||
self._cipher.seek(64) # Block counter starts at 1
|
||||
|
||||
self._len_aad = 0
|
||||
self._len_ct = 0
|
||||
self._mac_tag = None
|
||||
self._status = _CipherStatus.PROCESSING_AUTH_DATA
|
||||
|
||||
def update(self, data):
|
||||
"""Protect the associated data.
|
||||
|
||||
Associated data (also known as *additional authenticated data* - AAD)
|
||||
is the piece of the message that must stay in the clear, while
|
||||
still allowing the receiver to verify its integrity.
|
||||
An example is packet headers.
|
||||
|
||||
The associated data (possibly split into multiple segments) is
|
||||
fed into :meth:`update` before any call to :meth:`decrypt` or :meth:`encrypt`.
|
||||
If there is no associated data, :meth:`update` is not called.
|
||||
|
||||
:param bytes/bytearray/memoryview assoc_data:
|
||||
A piece of associated data. There are no restrictions on its size.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() method cannot be called")
|
||||
|
||||
self._len_aad += len(data)
|
||||
self._authenticator.update(data)
|
||||
|
||||
def _pad_aad(self):
|
||||
|
||||
assert(self._status == _CipherStatus.PROCESSING_AUTH_DATA)
|
||||
if self._len_aad & 0x0F:
|
||||
self._authenticator.update(b'\x00' * (16 - (self._len_aad & 0x0F)))
|
||||
self._status = _CipherStatus.PROCESSING_CIPHERTEXT
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt a piece of data.
|
||||
|
||||
Args:
|
||||
plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the ciphertext
|
||||
is written to. If ``None``, the ciphertext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() method cannot be called")
|
||||
|
||||
if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
|
||||
self._pad_aad()
|
||||
|
||||
self._next = ("encrypt", "digest")
|
||||
|
||||
result = self._cipher.encrypt(plaintext, output=output)
|
||||
self._len_ct += len(plaintext)
|
||||
if output is None:
|
||||
self._authenticator.update(result)
|
||||
else:
|
||||
self._authenticator.update(output)
|
||||
return result
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt a piece of data.
|
||||
|
||||
Args:
|
||||
ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the plaintext
|
||||
is written to. If ``None``, the plaintext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() method cannot be called")
|
||||
|
||||
if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
|
||||
self._pad_aad()
|
||||
|
||||
self._next = ("decrypt", "verify")
|
||||
|
||||
self._len_ct += len(ciphertext)
|
||||
self._authenticator.update(ciphertext)
|
||||
return self._cipher.decrypt(ciphertext, output=output)
|
||||
|
||||
def _compute_mac(self):
|
||||
"""Finalize the cipher (if not done already) and return the MAC."""
|
||||
|
||||
if self._mac_tag:
|
||||
assert(self._status == _CipherStatus.PROCESSING_DONE)
|
||||
return self._mac_tag
|
||||
|
||||
assert(self._status != _CipherStatus.PROCESSING_DONE)
|
||||
|
||||
if self._status == _CipherStatus.PROCESSING_AUTH_DATA:
|
||||
self._pad_aad()
|
||||
|
||||
if self._len_ct & 0x0F:
|
||||
self._authenticator.update(b'\x00' * (16 - (self._len_ct & 0x0F)))
|
||||
|
||||
self._status = _CipherStatus.PROCESSING_DONE
|
||||
|
||||
self._authenticator.update(long_to_bytes(self._len_aad, 8)[::-1])
|
||||
self._authenticator.update(long_to_bytes(self._len_ct, 8)[::-1])
|
||||
self._mac_tag = self._authenticator.digest()
|
||||
return self._mac_tag
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* authentication tag (MAC).
|
||||
|
||||
:Return: the MAC tag, as 16 ``bytes``.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() method cannot be called")
|
||||
self._next = ("digest",)
|
||||
|
||||
return self._compute_mac()
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* authentication tag (MAC).
|
||||
|
||||
This method is like :meth:`digest`.
|
||||
|
||||
:Return: the MAC tag, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* authentication tag (MAC).
|
||||
|
||||
The receiver invokes this method at the very end, to
|
||||
check if the associated data (if any) and the decrypted
|
||||
messages are valid.
|
||||
|
||||
:param bytes/bytearray/memoryview received_mac_tag:
|
||||
This is the 16-byte *binary* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called"
|
||||
" when encrypting a message")
|
||||
self._next = ("verify",)
|
||||
|
||||
secret = get_random_bytes(16)
|
||||
|
||||
self._compute_mac()
|
||||
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret,
|
||||
data=self._mac_tag)
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret,
|
||||
data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* authentication tag (MAC).
|
||||
|
||||
This method is like :meth:`verify`.
|
||||
|
||||
:param string hex_mac_tag:
|
||||
This is the *printable* MAC.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext):
|
||||
"""Perform :meth:`encrypt` and :meth:`digest` in one step.
|
||||
|
||||
:param plaintext: The data to encrypt, of any size.
|
||||
:type plaintext: bytes/bytearray/memoryview
|
||||
:return: a tuple with two ``bytes`` objects:
|
||||
|
||||
- the ciphertext, of equal length as the plaintext
|
||||
- the 16-byte MAC tag
|
||||
"""
|
||||
|
||||
return self.encrypt(plaintext), self.digest()
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, received_mac_tag):
|
||||
"""Perform :meth:`decrypt` and :meth:`verify` in one step.
|
||||
|
||||
:param ciphertext: The piece of data to decrypt.
|
||||
:type ciphertext: bytes/bytearray/memoryview
|
||||
:param bytes received_mac_tag:
|
||||
This is the 16-byte *binary* MAC, as received from the sender.
|
||||
:return: the decrypted data (as ``bytes``)
|
||||
:raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
plaintext = self.decrypt(ciphertext)
|
||||
self.verify(received_mac_tag)
|
||||
return plaintext
|
||||
|
||||
|
||||
def new(**kwargs):
|
||||
"""Create a new ChaCha20-Poly1305 or XChaCha20-Poly1305 AEAD cipher.
|
||||
|
||||
:keyword key: The secret key to use. It must be 32 bytes long.
|
||||
:type key: byte string
|
||||
|
||||
:keyword nonce:
|
||||
A value that must never be reused for any other encryption
|
||||
done with this key.
|
||||
|
||||
For ChaCha20-Poly1305, it must be 8 or 12 bytes long.
|
||||
|
||||
For XChaCha20-Poly1305, it must be 24 bytes long.
|
||||
|
||||
If not provided, 12 ``bytes`` will be generated randomly
|
||||
(you can find them back in the ``nonce`` attribute).
|
||||
:type nonce: bytes, bytearray, memoryview
|
||||
|
||||
:Return: a :class:`Crypto.Cipher.ChaCha20.ChaCha20Poly1305Cipher` object
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter %s" % e)
|
||||
|
||||
if len(key) != 32:
|
||||
raise ValueError("Key must be 32 bytes long")
|
||||
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(12)
|
||||
|
||||
if len(nonce) in (8, 12):
|
||||
chacha20_poly1305_nonce = nonce
|
||||
elif len(nonce) == 24:
|
||||
key = _HChaCha20(key, nonce[:16])
|
||||
chacha20_poly1305_nonce = b'\x00\x00\x00\x00' + nonce[16:]
|
||||
else:
|
||||
raise ValueError("Nonce must be 8, 12 or 24 bytes long")
|
||||
|
||||
if not is_buffer(nonce):
|
||||
raise TypeError("nonce must be bytes, bytearray or memoryview")
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters: " + str(kwargs))
|
||||
|
||||
cipher = ChaCha20Poly1305Cipher(key, chacha20_poly1305_nonce)
|
||||
cipher.nonce = _copy_bytes(None, None, nonce)
|
||||
return cipher
|
||||
|
||||
|
||||
# Size of a key (in bytes)
|
||||
key_size = 32
|
||||
@@ -1,28 +0,0 @@
|
||||
from typing import Union, Tuple, overload, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
class ChaCha20Poly1305Cipher:
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self, key: Buffer, nonce: Buffer) -> None: ...
|
||||
def update(self, data: Buffer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, received_mac_tag: str) -> None: ...
|
||||
def encrypt_and_digest(self, plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
def decrypt_and_verify(self, ciphertext: Buffer, received_mac_tag: Buffer) -> bytes: ...
|
||||
|
||||
def new(key: Buffer, nonce: Optional[Buffer] = ...) -> ChaCha20Poly1305Cipher: ...
|
||||
|
||||
block_size: int
|
||||
key_size: int
|
||||
@@ -1,158 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/DES.py : DES
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
"""
|
||||
Module's constants for the modes of operation supported with Single DES:
|
||||
|
||||
:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
|
||||
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
|
||||
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
|
||||
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
|
||||
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
|
||||
:var MODE_OPENPGP: :ref:`OpenPGP Mode <openpgp_mode>`
|
||||
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util.py3compat import byte_string
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer,
|
||||
c_size_t, c_uint8_ptr)
|
||||
|
||||
_raw_des_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_des",
|
||||
"""
|
||||
int DES_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
void **pResult);
|
||||
int DES_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int DES_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int DES_stop_operation(void *state);
|
||||
""")
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a handle to a low-level
|
||||
base cipher. It will absorb named parameters in the process."""
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
if len(key) != key_size:
|
||||
raise ValueError("Incorrect DES key length (%d bytes)" % len(key))
|
||||
|
||||
start_operation = _raw_des_lib.DES_start_operation
|
||||
stop_operation = _raw_des_lib.DES_stop_operation
|
||||
|
||||
cipher = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
cipher.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the DES cipher"
|
||||
% result)
|
||||
return SmartPointer(cipher.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new DES cipher.
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
It must be 8 byte long. The parity bits will be ignored.
|
||||
:type key: bytes/bytearray/memoryview
|
||||
|
||||
:param mode:
|
||||
The chaining mode to use for encryption or decryption.
|
||||
:type mode: One of the supported ``MODE_*`` constants
|
||||
|
||||
:Keyword Arguments:
|
||||
* **iv** (*byte string*) --
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 8 bytes long for encryption
|
||||
and 10 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
* **nonce** (*byte string*) --
|
||||
(Only applicable for ``MODE_EAX`` and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key.
|
||||
|
||||
For ``MODE_EAX`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..7]**.
|
||||
|
||||
If not provided for ``MODE_EAX``, a random byte string is generated (you
|
||||
can read it back via the ``nonce`` attribute).
|
||||
|
||||
* **segment_size** (*integer*) --
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
* **mac_len** : (*integer*) --
|
||||
(Only ``MODE_EAX``)
|
||||
Length of the authentication tag, in bytes.
|
||||
It must be no longer than 8 (default).
|
||||
|
||||
* **initial_value** : (*integer*) --
|
||||
(Only ``MODE_CTR``). The initial value for the counter within
|
||||
the counter block. By default it is **0**.
|
||||
|
||||
:Return: a DES object, of the applicable mode.
|
||||
"""
|
||||
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
MODE_ECB = 1
|
||||
MODE_CBC = 2
|
||||
MODE_CFB = 3
|
||||
MODE_OFB = 5
|
||||
MODE_CTR = 6
|
||||
MODE_OPENPGP = 7
|
||||
MODE_EAX = 9
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = 8
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Union, Dict, Iterable, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
|
||||
DESMode = int
|
||||
|
||||
MODE_ECB: DESMode
|
||||
MODE_CBC: DESMode
|
||||
MODE_CFB: DESMode
|
||||
MODE_OFB: DESMode
|
||||
MODE_CTR: DESMode
|
||||
MODE_OPENPGP: DESMode
|
||||
MODE_EAX: DESMode
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: DESMode,
|
||||
iv : Optional[Buffer] = ...,
|
||||
IV : Optional[Buffer] = ...,
|
||||
nonce : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
mac_len : int = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...) -> \
|
||||
Union[EcbMode, CbcMode, CfbMode, OfbMode, CtrMode, OpenPgpMode]: ...
|
||||
|
||||
block_size: int
|
||||
key_size: int
|
||||
@@ -1,187 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/DES3.py : DES3
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
"""
|
||||
Module's constants for the modes of operation supported with Triple DES:
|
||||
|
||||
:var MODE_ECB: :ref:`Electronic Code Book (ECB) <ecb_mode>`
|
||||
:var MODE_CBC: :ref:`Cipher-Block Chaining (CBC) <cbc_mode>`
|
||||
:var MODE_CFB: :ref:`Cipher FeedBack (CFB) <cfb_mode>`
|
||||
:var MODE_OFB: :ref:`Output FeedBack (OFB) <ofb_mode>`
|
||||
:var MODE_CTR: :ref:`CounTer Mode (CTR) <ctr_mode>`
|
||||
:var MODE_OPENPGP: :ref:`OpenPGP Mode <openpgp_mode>`
|
||||
:var MODE_EAX: :ref:`EAX Mode <eax_mode>`
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util.py3compat import byte_string, bchr, bord, bstr
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer,
|
||||
c_size_t)
|
||||
|
||||
_raw_des3_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_des3",
|
||||
"""
|
||||
int DES3_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
void **pResult);
|
||||
int DES3_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int DES3_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int DES3_stop_operation(void *state);
|
||||
""")
|
||||
|
||||
|
||||
def adjust_key_parity(key_in):
|
||||
"""Set the parity bits in a TDES key.
|
||||
|
||||
:param key_in: the TDES key whose bits need to be adjusted
|
||||
:type key_in: byte string
|
||||
|
||||
:returns: a copy of ``key_in``, with the parity bits correctly set
|
||||
:rtype: byte string
|
||||
|
||||
:raises ValueError: if the TDES key is not 16 or 24 bytes long
|
||||
:raises ValueError: if the TDES key degenerates into Single DES
|
||||
"""
|
||||
|
||||
def parity_byte(key_byte):
|
||||
parity = 1
|
||||
for i in range(1, 8):
|
||||
parity ^= (key_byte >> i) & 1
|
||||
return (key_byte & 0xFE) | parity
|
||||
|
||||
if len(key_in) not in key_size:
|
||||
raise ValueError("Not a valid TDES key")
|
||||
|
||||
key_out = b"".join([ bchr(parity_byte(bord(x))) for x in key_in ])
|
||||
|
||||
if key_out[:8] == key_out[8:16] or key_out[-16:-8] == key_out[-8:]:
|
||||
raise ValueError("Triple DES key degenerates to single DES")
|
||||
|
||||
return key_out
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a handle to a low-level base cipher.
|
||||
It will absorb named parameters in the process."""
|
||||
|
||||
try:
|
||||
key_in = dict_parameters.pop("key")
|
||||
except KeyError:
|
||||
raise TypeError("Missing 'key' parameter")
|
||||
|
||||
key = adjust_key_parity(bstr(key_in))
|
||||
|
||||
start_operation = _raw_des3_lib.DES3_start_operation
|
||||
stop_operation = _raw_des3_lib.DES3_stop_operation
|
||||
|
||||
cipher = VoidPointer()
|
||||
result = start_operation(key,
|
||||
c_size_t(len(key)),
|
||||
cipher.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the TDES cipher"
|
||||
% result)
|
||||
return SmartPointer(cipher.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, *args, **kwargs):
|
||||
"""Create a new Triple DES cipher.
|
||||
|
||||
:param key:
|
||||
The secret key to use in the symmetric cipher.
|
||||
It must be 16 or 24 byte long. The parity bits will be ignored.
|
||||
:type key: bytes/bytearray/memoryview
|
||||
|
||||
:param mode:
|
||||
The chaining mode to use for encryption or decryption.
|
||||
:type mode: One of the supported ``MODE_*`` constants
|
||||
|
||||
:Keyword Arguments:
|
||||
* **iv** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_CBC``, ``MODE_CFB``, ``MODE_OFB``,
|
||||
and ``MODE_OPENPGP`` modes).
|
||||
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For ``MODE_CBC``, ``MODE_CFB``, and ``MODE_OFB`` it must be 8 bytes long.
|
||||
|
||||
For ``MODE_OPENPGP`` mode only,
|
||||
it must be 8 bytes long for encryption
|
||||
and 10 bytes for decryption (in the latter case, it is
|
||||
actually the *encrypted* IV which was prefixed to the ciphertext).
|
||||
|
||||
If not provided, a random byte string is generated (you must then
|
||||
read its value with the :attr:`iv` attribute).
|
||||
|
||||
* **nonce** (*bytes*, *bytearray*, *memoryview*) --
|
||||
(Only applicable for ``MODE_EAX`` and ``MODE_CTR``).
|
||||
|
||||
A value that must never be reused for any other encryption done
|
||||
with this key.
|
||||
|
||||
For ``MODE_EAX`` there are no
|
||||
restrictions on its length (recommended: **16** bytes).
|
||||
|
||||
For ``MODE_CTR``, its length must be in the range **[0..7]**.
|
||||
|
||||
If not provided for ``MODE_EAX``, a random byte string is generated (you
|
||||
can read it back via the ``nonce`` attribute).
|
||||
|
||||
* **segment_size** (*integer*) --
|
||||
(Only ``MODE_CFB``).The number of **bits** the plaintext and ciphertext
|
||||
are segmented in. It must be a multiple of 8.
|
||||
If not specified, it will be assumed to be 8.
|
||||
|
||||
* **mac_len** : (*integer*) --
|
||||
(Only ``MODE_EAX``)
|
||||
Length of the authentication tag, in bytes.
|
||||
It must be no longer than 8 (default).
|
||||
|
||||
* **initial_value** : (*integer*) --
|
||||
(Only ``MODE_CTR``). The initial value for the counter within
|
||||
the counter block. By default it is **0**.
|
||||
|
||||
:Return: a Triple DES object, of the applicable mode.
|
||||
"""
|
||||
|
||||
return _create_cipher(sys.modules[__name__], key, mode, *args, **kwargs)
|
||||
|
||||
MODE_ECB = 1
|
||||
MODE_CBC = 2
|
||||
MODE_CFB = 3
|
||||
MODE_OFB = 5
|
||||
MODE_CTR = 6
|
||||
MODE_OPENPGP = 7
|
||||
MODE_EAX = 9
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = (16, 24)
|
||||
@@ -1,37 +0,0 @@
|
||||
from typing import Union, Dict, Tuple, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
from Crypto.Cipher._mode_cbc import CbcMode
|
||||
from Crypto.Cipher._mode_cfb import CfbMode
|
||||
from Crypto.Cipher._mode_ofb import OfbMode
|
||||
from Crypto.Cipher._mode_ctr import CtrMode
|
||||
from Crypto.Cipher._mode_openpgp import OpenPgpMode
|
||||
from Crypto.Cipher._mode_eax import EaxMode
|
||||
|
||||
def adjust_key_parity(key_in: bytes) -> bytes: ...
|
||||
|
||||
DES3Mode = int
|
||||
|
||||
MODE_ECB: DES3Mode
|
||||
MODE_CBC: DES3Mode
|
||||
MODE_CFB: DES3Mode
|
||||
MODE_OFB: DES3Mode
|
||||
MODE_CTR: DES3Mode
|
||||
MODE_OPENPGP: DES3Mode
|
||||
MODE_EAX: DES3Mode
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: DES3Mode,
|
||||
iv : Optional[Buffer] = ...,
|
||||
IV : Optional[Buffer] = ...,
|
||||
nonce : Optional[Buffer] = ...,
|
||||
segment_size : int = ...,
|
||||
mac_len : int = ...,
|
||||
initial_value : Union[int, Buffer] = ...,
|
||||
counter : Dict = ...) -> \
|
||||
Union[EcbMode, CbcMode, CfbMode, OfbMode, CtrMode, OpenPgpMode]: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Tuple[int, int]
|
||||
@@ -1,231 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/PKCS1_OAEP.py : PKCS#1 OAEP
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
from Crypto.Signature.pss import MGF1
|
||||
import Crypto.Hash.SHA1
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
import Crypto.Util.number
|
||||
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto import Random
|
||||
from ._pkcs1_oaep_decode import oaep_decode
|
||||
|
||||
|
||||
class PKCS1OAEP_Cipher:
|
||||
"""Cipher object for PKCS#1 v1.5 OAEP.
|
||||
Do not create directly: use :func:`new` instead."""
|
||||
|
||||
def __init__(self, key, hashAlgo, mgfunc, label, randfunc):
|
||||
"""Initialize this PKCS#1 OAEP cipher object.
|
||||
|
||||
:Parameters:
|
||||
key : an RSA key object
|
||||
If a private half is given, both encryption and decryption are possible.
|
||||
If a public half is given, only encryption is possible.
|
||||
hashAlgo : hash object
|
||||
The hash function to use. This can be a module under `Crypto.Hash`
|
||||
or an existing hash object created from any of such modules. If not specified,
|
||||
`Crypto.Hash.SHA1` is used.
|
||||
mgfunc : callable
|
||||
A mask generation function that accepts two parameters: a string to
|
||||
use as seed, and the lenth of the mask to generate, in bytes.
|
||||
If not specified, the standard MGF1 consistent with ``hashAlgo`` is used (a safe choice).
|
||||
label : bytes/bytearray/memoryview
|
||||
A label to apply to this particular encryption. If not specified,
|
||||
an empty string is used. Specifying a label does not improve
|
||||
security.
|
||||
randfunc : callable
|
||||
A function that returns random bytes.
|
||||
|
||||
:attention: Modify the mask generation function only if you know what you are doing.
|
||||
Sender and receiver must use the same one.
|
||||
"""
|
||||
self._key = key
|
||||
|
||||
if hashAlgo:
|
||||
self._hashObj = hashAlgo
|
||||
else:
|
||||
self._hashObj = Crypto.Hash.SHA1
|
||||
|
||||
if mgfunc:
|
||||
self._mgf = mgfunc
|
||||
else:
|
||||
self._mgf = lambda x, y: MGF1(x, y, self._hashObj)
|
||||
|
||||
self._label = _copy_bytes(None, None, label)
|
||||
self._randfunc = randfunc
|
||||
|
||||
def can_encrypt(self):
|
||||
"""Legacy function to check if you can call :meth:`encrypt`.
|
||||
|
||||
.. deprecated:: 3.0"""
|
||||
return self._key.can_encrypt()
|
||||
|
||||
def can_decrypt(self):
|
||||
"""Legacy function to check if you can call :meth:`decrypt`.
|
||||
|
||||
.. deprecated:: 3.0"""
|
||||
return self._key.can_decrypt()
|
||||
|
||||
def encrypt(self, message):
|
||||
"""Encrypt a message with PKCS#1 OAEP.
|
||||
|
||||
:param message:
|
||||
The message to encrypt, also known as plaintext. It can be of
|
||||
variable length, but not longer than the RSA modulus (in bytes)
|
||||
minus 2, minus twice the hash output size.
|
||||
For instance, if you use RSA 2048 and SHA-256, the longest message
|
||||
you can encrypt is 190 byte long.
|
||||
:type message: bytes/bytearray/memoryview
|
||||
|
||||
:returns: The ciphertext, as large as the RSA modulus.
|
||||
:rtype: bytes
|
||||
|
||||
:raises ValueError:
|
||||
if the message is too long.
|
||||
"""
|
||||
|
||||
# See 7.1.1 in RFC3447
|
||||
modBits = Crypto.Util.number.size(self._key.n)
|
||||
k = ceil_div(modBits, 8) # Convert from bits to bytes
|
||||
hLen = self._hashObj.digest_size
|
||||
mLen = len(message)
|
||||
|
||||
# Step 1b
|
||||
ps_len = k - mLen - 2 * hLen - 2
|
||||
if ps_len < 0:
|
||||
raise ValueError("Plaintext is too long.")
|
||||
# Step 2a
|
||||
lHash = self._hashObj.new(self._label).digest()
|
||||
# Step 2b
|
||||
ps = b'\x00' * ps_len
|
||||
# Step 2c
|
||||
db = lHash + ps + b'\x01' + _copy_bytes(None, None, message)
|
||||
# Step 2d
|
||||
ros = self._randfunc(hLen)
|
||||
# Step 2e
|
||||
dbMask = self._mgf(ros, k-hLen-1)
|
||||
# Step 2f
|
||||
maskedDB = strxor(db, dbMask)
|
||||
# Step 2g
|
||||
seedMask = self._mgf(maskedDB, hLen)
|
||||
# Step 2h
|
||||
maskedSeed = strxor(ros, seedMask)
|
||||
# Step 2i
|
||||
em = b'\x00' + maskedSeed + maskedDB
|
||||
# Step 3a (OS2IP)
|
||||
em_int = bytes_to_long(em)
|
||||
# Step 3b (RSAEP)
|
||||
m_int = self._key._encrypt(em_int)
|
||||
# Step 3c (I2OSP)
|
||||
c = long_to_bytes(m_int, k)
|
||||
return c
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""Decrypt a message with PKCS#1 OAEP.
|
||||
|
||||
:param ciphertext: The encrypted message.
|
||||
:type ciphertext: bytes/bytearray/memoryview
|
||||
|
||||
:returns: The original message (plaintext).
|
||||
:rtype: bytes
|
||||
|
||||
:raises ValueError:
|
||||
if the ciphertext has the wrong length, or if decryption
|
||||
fails the integrity check (in which case, the decryption
|
||||
key is probably wrong).
|
||||
:raises TypeError:
|
||||
if the RSA key has no private half (i.e. you are trying
|
||||
to decrypt using a public key).
|
||||
"""
|
||||
|
||||
# See 7.1.2 in RFC3447
|
||||
modBits = Crypto.Util.number.size(self._key.n)
|
||||
k = ceil_div(modBits, 8) # Convert from bits to bytes
|
||||
hLen = self._hashObj.digest_size
|
||||
|
||||
# Step 1b and 1c
|
||||
if len(ciphertext) != k or k < hLen+2:
|
||||
raise ValueError("Ciphertext with incorrect length.")
|
||||
# Step 2a (O2SIP)
|
||||
ct_int = bytes_to_long(ciphertext)
|
||||
# Step 2b (RSADP) and step 2c (I2OSP)
|
||||
em = self._key._decrypt_to_bytes(ct_int)
|
||||
# Step 3a
|
||||
lHash = self._hashObj.new(self._label).digest()
|
||||
# y must be 0, but we MUST NOT check it here in order not to
|
||||
# allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143)
|
||||
maskedSeed = em[1:hLen+1]
|
||||
maskedDB = em[hLen+1:]
|
||||
# Step 3c
|
||||
seedMask = self._mgf(maskedDB, hLen)
|
||||
# Step 3d
|
||||
seed = strxor(maskedSeed, seedMask)
|
||||
# Step 3e
|
||||
dbMask = self._mgf(seed, k-hLen-1)
|
||||
# Step 3f
|
||||
db = strxor(maskedDB, dbMask)
|
||||
# Step 3b + 3g
|
||||
res = oaep_decode(em, lHash, db)
|
||||
if res <= 0:
|
||||
raise ValueError("Incorrect decryption.")
|
||||
# Step 4
|
||||
return db[res:]
|
||||
|
||||
|
||||
def new(key, hashAlgo=None, mgfunc=None, label=b'', randfunc=None):
|
||||
"""Return a cipher object :class:`PKCS1OAEP_Cipher`
|
||||
that can be used to perform PKCS#1 OAEP encryption or decryption.
|
||||
|
||||
:param key:
|
||||
The key object to use to encrypt or decrypt the message.
|
||||
Decryption is only possible with a private RSA key.
|
||||
:type key: RSA key object
|
||||
|
||||
:param hashAlgo:
|
||||
The hash function to use. This can be a module under `Crypto.Hash`
|
||||
or an existing hash object created from any of such modules.
|
||||
If not specified, `Crypto.Hash.SHA1` is used.
|
||||
:type hashAlgo: hash object
|
||||
|
||||
:param mgfunc:
|
||||
A mask generation function that accepts two parameters: a string to
|
||||
use as seed, and the lenth of the mask to generate, in bytes.
|
||||
If not specified, the standard MGF1 consistent with ``hashAlgo`` is used (a safe choice).
|
||||
:type mgfunc: callable
|
||||
|
||||
:param label:
|
||||
A label to apply to this particular encryption. If not specified,
|
||||
an empty string is used. Specifying a label does not improve
|
||||
security.
|
||||
:type label: bytes/bytearray/memoryview
|
||||
|
||||
:param randfunc:
|
||||
A function that returns random bytes.
|
||||
The default is `Random.get_random_bytes`.
|
||||
:type randfunc: callable
|
||||
"""
|
||||
|
||||
if randfunc is None:
|
||||
randfunc = Random.get_random_bytes
|
||||
return PKCS1OAEP_Cipher(key, hashAlgo, mgfunc, label, randfunc)
|
||||
@@ -1,35 +0,0 @@
|
||||
from typing import Optional, Union, Callable, Any, overload
|
||||
from typing_extensions import Protocol
|
||||
|
||||
from Crypto.PublicKey.RSA import RsaKey
|
||||
|
||||
class HashLikeClass(Protocol):
|
||||
digest_size : int
|
||||
def new(self, data: Optional[bytes] = ...) -> Any: ...
|
||||
|
||||
class HashLikeModule(Protocol):
|
||||
digest_size : int
|
||||
@staticmethod
|
||||
def new(data: Optional[bytes] = ...) -> Any: ...
|
||||
|
||||
HashLike = Union[HashLikeClass, HashLikeModule]
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
class PKCS1OAEP_Cipher:
|
||||
def __init__(self,
|
||||
key: RsaKey,
|
||||
hashAlgo: HashLike,
|
||||
mgfunc: Callable[[bytes, int], bytes],
|
||||
label: Buffer,
|
||||
randfunc: Callable[[int], bytes]) -> None: ...
|
||||
def can_encrypt(self) -> bool: ...
|
||||
def can_decrypt(self) -> bool: ...
|
||||
def encrypt(self, message: Buffer) -> bytes: ...
|
||||
def decrypt(self, ciphertext: Buffer) -> bytes: ...
|
||||
|
||||
def new(key: RsaKey,
|
||||
hashAlgo: Optional[HashLike] = ...,
|
||||
mgfunc: Optional[Callable[[bytes, int], bytes]] = ...,
|
||||
label: Optional[Buffer] = ...,
|
||||
randfunc: Optional[Callable[[int], bytes]] = ...) -> PKCS1OAEP_Cipher: ...
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/PKCS1-v1_5.py : PKCS#1 v1.5
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
__all__ = ['new', 'PKCS115_Cipher']
|
||||
|
||||
from Crypto import Random
|
||||
from Crypto.Util.number import bytes_to_long, long_to_bytes
|
||||
from Crypto.Util.py3compat import bord, is_bytes, _copy_bytes
|
||||
from ._pkcs1_oaep_decode import pkcs1_decode
|
||||
|
||||
|
||||
class PKCS115_Cipher:
|
||||
"""This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.
|
||||
Do not instantiate directly. Use :func:`Crypto.Cipher.PKCS1_v1_5.new` instead."""
|
||||
|
||||
def __init__(self, key, randfunc):
|
||||
"""Initialize this PKCS#1 v1.5 cipher object.
|
||||
|
||||
:Parameters:
|
||||
key : an RSA key object
|
||||
If a private half is given, both encryption and decryption are possible.
|
||||
If a public half is given, only encryption is possible.
|
||||
randfunc : callable
|
||||
Function that returns random bytes.
|
||||
"""
|
||||
|
||||
self._key = key
|
||||
self._randfunc = randfunc
|
||||
|
||||
def can_encrypt(self):
|
||||
"""Return True if this cipher object can be used for encryption."""
|
||||
return self._key.can_encrypt()
|
||||
|
||||
def can_decrypt(self):
|
||||
"""Return True if this cipher object can be used for decryption."""
|
||||
return self._key.can_decrypt()
|
||||
|
||||
def encrypt(self, message):
|
||||
"""Produce the PKCS#1 v1.5 encryption of a message.
|
||||
|
||||
This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and it is specified in
|
||||
`section 7.2.1 of RFC8017
|
||||
<https://tools.ietf.org/html/rfc8017#page-28>`_.
|
||||
|
||||
:param message:
|
||||
The message to encrypt, also known as plaintext. It can be of
|
||||
variable length, but not longer than the RSA modulus (in bytes) minus 11.
|
||||
:type message: bytes/bytearray/memoryview
|
||||
|
||||
:Returns: A byte string, the ciphertext in which the message is encrypted.
|
||||
It is as long as the RSA modulus (in bytes).
|
||||
|
||||
:Raises ValueError:
|
||||
If the RSA key length is not sufficiently long to deal with the given
|
||||
message.
|
||||
"""
|
||||
|
||||
# See 7.2.1 in RFC8017
|
||||
k = self._key.size_in_bytes()
|
||||
mLen = len(message)
|
||||
|
||||
# Step 1
|
||||
if mLen > k - 11:
|
||||
raise ValueError("Plaintext is too long.")
|
||||
# Step 2a
|
||||
ps = []
|
||||
while len(ps) != k - mLen - 3:
|
||||
new_byte = self._randfunc(1)
|
||||
if bord(new_byte[0]) == 0x00:
|
||||
continue
|
||||
ps.append(new_byte)
|
||||
ps = b"".join(ps)
|
||||
# Step 2b
|
||||
em = b'\x00\x02' + ps + b'\x00' + _copy_bytes(None, None, message)
|
||||
# Step 3a (OS2IP)
|
||||
em_int = bytes_to_long(em)
|
||||
# Step 3b (RSAEP)
|
||||
m_int = self._key._encrypt(em_int)
|
||||
# Step 3c (I2OSP)
|
||||
c = long_to_bytes(m_int, k)
|
||||
return c
|
||||
|
||||
def decrypt(self, ciphertext, sentinel, expected_pt_len=0):
|
||||
r"""Decrypt a PKCS#1 v1.5 ciphertext.
|
||||
|
||||
This is the function ``RSAES-PKCS1-V1_5-DECRYPT`` specified in
|
||||
`section 7.2.2 of RFC8017
|
||||
<https://tools.ietf.org/html/rfc8017#page-29>`_.
|
||||
|
||||
Args:
|
||||
ciphertext (bytes/bytearray/memoryview):
|
||||
The ciphertext that contains the message to recover.
|
||||
sentinel (any type):
|
||||
The object to return whenever an error is detected.
|
||||
expected_pt_len (integer):
|
||||
The length the plaintext is known to have, or 0 if unknown.
|
||||
|
||||
Returns (byte string):
|
||||
It is either the original message or the ``sentinel`` (in case of an error).
|
||||
|
||||
.. warning::
|
||||
PKCS#1 v1.5 decryption is intrinsically vulnerable to timing
|
||||
attacks (see `Bleichenbacher's`__ attack).
|
||||
**Use PKCS#1 OAEP instead**.
|
||||
|
||||
This implementation attempts to mitigate the risk
|
||||
with some constant-time constructs.
|
||||
However, they are not sufficient by themselves: the type of protocol you
|
||||
implement and the way you handle errors make a big difference.
|
||||
|
||||
Specifically, you should make it very hard for the (malicious)
|
||||
party that submitted the ciphertext to quickly understand if decryption
|
||||
succeeded or not.
|
||||
|
||||
To this end, it is recommended that your protocol only encrypts
|
||||
plaintexts of fixed length (``expected_pt_len``),
|
||||
that ``sentinel`` is a random byte string of the same length,
|
||||
and that processing continues for as long
|
||||
as possible even if ``sentinel`` is returned (i.e. in case of
|
||||
incorrect decryption).
|
||||
|
||||
.. __: https://dx.doi.org/10.1007/BFb0055716
|
||||
"""
|
||||
|
||||
# See 7.2.2 in RFC8017
|
||||
k = self._key.size_in_bytes()
|
||||
|
||||
# Step 1
|
||||
if len(ciphertext) != k:
|
||||
raise ValueError("Ciphertext with incorrect length (not %d bytes)" % k)
|
||||
|
||||
# Step 2a (O2SIP)
|
||||
ct_int = bytes_to_long(ciphertext)
|
||||
|
||||
# Step 2b (RSADP) and Step 2c (I2OSP)
|
||||
em = self._key._decrypt_to_bytes(ct_int)
|
||||
|
||||
# Step 3 (not constant time when the sentinel is not a byte string)
|
||||
output = bytes(bytearray(k))
|
||||
if not is_bytes(sentinel) or len(sentinel) > k:
|
||||
size = pkcs1_decode(em, b'', expected_pt_len, output)
|
||||
if size < 0:
|
||||
return sentinel
|
||||
else:
|
||||
return output[size:]
|
||||
|
||||
# Step 3 (somewhat constant time)
|
||||
size = pkcs1_decode(em, sentinel, expected_pt_len, output)
|
||||
return output[size:]
|
||||
|
||||
|
||||
def new(key, randfunc=None):
|
||||
"""Create a cipher for performing PKCS#1 v1.5 encryption or decryption.
|
||||
|
||||
:param key:
|
||||
The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object.
|
||||
Decryption is only possible if *key* is a private RSA key.
|
||||
:type key: RSA key object
|
||||
|
||||
:param randfunc:
|
||||
Function that return random bytes.
|
||||
The default is :func:`Crypto.Random.get_random_bytes`.
|
||||
:type randfunc: callable
|
||||
|
||||
:returns: A cipher object `PKCS115_Cipher`.
|
||||
"""
|
||||
|
||||
if randfunc is None:
|
||||
randfunc = Random.get_random_bytes
|
||||
return PKCS115_Cipher(key, randfunc)
|
||||
@@ -1,20 +0,0 @@
|
||||
from typing import Callable, Union, Any, Optional, TypeVar
|
||||
|
||||
from Crypto.PublicKey.RSA import RsaKey
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
T = TypeVar('T')
|
||||
|
||||
class PKCS115_Cipher:
|
||||
def __init__(self,
|
||||
key: RsaKey,
|
||||
randfunc: Callable[[int], bytes]) -> None: ...
|
||||
def can_encrypt(self) -> bool: ...
|
||||
def can_decrypt(self) -> bool: ...
|
||||
def encrypt(self, message: Buffer) -> bytes: ...
|
||||
def decrypt(self, ciphertext: Buffer,
|
||||
sentinel: T,
|
||||
expected_pt_len: Optional[int] = ...) -> Union[bytes, T]: ...
|
||||
|
||||
def new(key: RsaKey,
|
||||
randfunc: Optional[Callable[[int], bytes]] = ...) -> PKCS115_Cipher: ...
|
||||
@@ -1,167 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/Salsa20.py : Salsa20 stream cipher (http://cr.yp.to/snuffle.html)
|
||||
#
|
||||
# Contributed by Fabrizio Tarizzo <fabrizio@fabriziotarizzo.org>.
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
create_string_buffer,
|
||||
get_raw_buffer, VoidPointer,
|
||||
SmartPointer, c_size_t,
|
||||
c_uint8_ptr, is_writeable_buffer)
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
_raw_salsa20_lib = load_pycryptodome_raw_lib("Crypto.Cipher._Salsa20",
|
||||
"""
|
||||
int Salsa20_stream_init(uint8_t *key, size_t keylen,
|
||||
uint8_t *nonce, size_t nonce_len,
|
||||
void **pSalsaState);
|
||||
int Salsa20_stream_destroy(void *salsaState);
|
||||
int Salsa20_stream_encrypt(void *salsaState,
|
||||
const uint8_t in[],
|
||||
uint8_t out[], size_t len);
|
||||
""")
|
||||
|
||||
|
||||
class Salsa20Cipher:
|
||||
"""Salsa20 cipher object. Do not create it directly. Use :py:func:`new`
|
||||
instead.
|
||||
|
||||
:var nonce: The nonce with length 8
|
||||
:vartype nonce: byte string
|
||||
"""
|
||||
|
||||
def __init__(self, key, nonce):
|
||||
"""Initialize a Salsa20 cipher object
|
||||
|
||||
See also `new()` at the module level."""
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect key length for Salsa20 (%d bytes)" % len(key))
|
||||
|
||||
if len(nonce) != 8:
|
||||
raise ValueError("Incorrect nonce length for Salsa20 (%d bytes)" %
|
||||
len(nonce))
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = _raw_salsa20_lib.Salsa20_stream_init(
|
||||
c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
c_uint8_ptr(nonce),
|
||||
c_size_t(len(nonce)),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d instantiating a Salsa20 cipher")
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
_raw_salsa20_lib.Salsa20_stream_destroy)
|
||||
|
||||
self.block_size = 1
|
||||
self.key_size = len(key)
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt a piece of data.
|
||||
|
||||
Args:
|
||||
plaintext(bytes/bytearray/memoryview): The data to encrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the ciphertext
|
||||
is written to. If ``None``, the ciphertext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = _raw_salsa20_lib.Salsa20_stream_encrypt(
|
||||
self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while encrypting with Salsa20" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt a piece of data.
|
||||
|
||||
Args:
|
||||
ciphertext(bytes/bytearray/memoryview): The data to decrypt, of any size.
|
||||
Keyword Args:
|
||||
output(bytes/bytearray/memoryview): The location where the plaintext
|
||||
is written to. If ``None``, the plaintext is returned.
|
||||
Returns:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
try:
|
||||
return self.encrypt(ciphertext, output=output)
|
||||
except ValueError as e:
|
||||
raise ValueError(str(e).replace("enc", "dec"))
|
||||
|
||||
|
||||
def new(key, nonce=None):
|
||||
"""Create a new Salsa20 cipher
|
||||
|
||||
:keyword key: The secret key to use. It must be 16 or 32 bytes long.
|
||||
:type key: bytes/bytearray/memoryview
|
||||
|
||||
:keyword nonce:
|
||||
A value that must never be reused for any other encryption
|
||||
done with this key. It must be 8 bytes long.
|
||||
|
||||
If not provided, a random byte string will be generated (you can read
|
||||
it back via the ``nonce`` attribute of the returned object).
|
||||
:type nonce: bytes/bytearray/memoryview
|
||||
|
||||
:Return: a :class:`Crypto.Cipher.Salsa20.Salsa20Cipher` object
|
||||
"""
|
||||
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(8)
|
||||
|
||||
return Salsa20Cipher(key, nonce)
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 1
|
||||
|
||||
# Size of a key (in bytes)
|
||||
key_size = (16, 32)
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
from typing import Union, Tuple, Optional, overload, Optional
|
||||
|
||||
Buffer = bytes|bytearray|memoryview
|
||||
|
||||
class Salsa20Cipher:
|
||||
nonce: bytes
|
||||
block_size: int
|
||||
key_size: int
|
||||
|
||||
def __init__(self,
|
||||
key: Buffer,
|
||||
nonce: Buffer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
def new(key: Buffer, nonce: Optional[Buffer] = ...) -> Salsa20Cipher: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Tuple[int, int]
|
||||
|
||||
Binary file not shown.
@@ -1,131 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2019, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
import sys
|
||||
|
||||
from Crypto.Cipher import _create_cipher
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, SmartPointer, c_size_t,
|
||||
c_uint8_ptr, c_uint)
|
||||
|
||||
_raw_blowfish_lib = load_pycryptodome_raw_lib(
|
||||
"Crypto.Cipher._raw_eksblowfish",
|
||||
"""
|
||||
int EKSBlowfish_start_operation(const uint8_t key[],
|
||||
size_t key_len,
|
||||
const uint8_t salt[16],
|
||||
size_t salt_len,
|
||||
unsigned cost,
|
||||
unsigned invert,
|
||||
void **pResult);
|
||||
int EKSBlowfish_encrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int EKSBlowfish_decrypt(const void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int EKSBlowfish_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _create_base_cipher(dict_parameters):
|
||||
"""This method instantiates and returns a smart pointer to
|
||||
a low-level base cipher. It will absorb named parameters in
|
||||
the process."""
|
||||
|
||||
try:
|
||||
key = dict_parameters.pop("key")
|
||||
salt = dict_parameters.pop("salt")
|
||||
cost = dict_parameters.pop("cost")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing EKSBlowfish parameter: " + str(e))
|
||||
invert = dict_parameters.pop("invert", True)
|
||||
|
||||
if len(key) not in key_size:
|
||||
raise ValueError("Incorrect EKSBlowfish key length (%d bytes)" % len(key))
|
||||
|
||||
start_operation = _raw_blowfish_lib.EKSBlowfish_start_operation
|
||||
stop_operation = _raw_blowfish_lib.EKSBlowfish_stop_operation
|
||||
|
||||
void_p = VoidPointer()
|
||||
result = start_operation(c_uint8_ptr(key),
|
||||
c_size_t(len(key)),
|
||||
c_uint8_ptr(salt),
|
||||
c_size_t(len(salt)),
|
||||
c_uint(cost),
|
||||
c_uint(int(invert)),
|
||||
void_p.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the EKSBlowfish cipher"
|
||||
% result)
|
||||
return SmartPointer(void_p.get(), stop_operation)
|
||||
|
||||
|
||||
def new(key, mode, salt, cost, invert):
|
||||
"""Create a new EKSBlowfish cipher
|
||||
|
||||
Args:
|
||||
|
||||
key (bytes, bytearray, memoryview):
|
||||
The secret key to use in the symmetric cipher.
|
||||
Its length can vary from 0 to 72 bytes.
|
||||
|
||||
mode (one of the supported ``MODE_*`` constants):
|
||||
The chaining mode to use for encryption or decryption.
|
||||
|
||||
salt (bytes, bytearray, memoryview):
|
||||
The salt that bcrypt uses to thwart rainbow table attacks
|
||||
|
||||
cost (integer):
|
||||
The complexity factor in bcrypt
|
||||
|
||||
invert (bool):
|
||||
If ``False``, in the inner loop use ``ExpandKey`` first over the salt
|
||||
and then over the key, as defined in
|
||||
the `original bcrypt specification <https://www.usenix.org/legacy/events/usenix99/provos/provos_html/node4.html>`_.
|
||||
If ``True``, reverse the order, as in the first implementation of
|
||||
`bcrypt` in OpenBSD.
|
||||
|
||||
:Return: an EKSBlowfish object
|
||||
"""
|
||||
|
||||
kwargs = { 'salt':salt, 'cost':cost, 'invert':invert }
|
||||
return _create_cipher(sys.modules[__name__], key, mode, **kwargs)
|
||||
|
||||
|
||||
MODE_ECB = 1
|
||||
|
||||
# Size of a data block (in bytes)
|
||||
block_size = 8
|
||||
# Size of a key (in bytes)
|
||||
key_size = range(0, 72 + 1)
|
||||
@@ -1,15 +0,0 @@
|
||||
from typing import Union, Iterable
|
||||
|
||||
from Crypto.Cipher._mode_ecb import EcbMode
|
||||
|
||||
MODE_ECB: int
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
def new(key: Buffer,
|
||||
mode: int,
|
||||
salt: Buffer,
|
||||
cost: int) -> EcbMode: ...
|
||||
|
||||
block_size: int
|
||||
key_size: Iterable[int]
|
||||
Binary file not shown.
@@ -1,91 +0,0 @@
|
||||
#
|
||||
# A block cipher is instantiated as a combination of:
|
||||
# 1. A base cipher (such as AES)
|
||||
# 2. A mode of operation (such as CBC)
|
||||
#
|
||||
# Both items are implemented as C modules.
|
||||
#
|
||||
# The API of #1 is (replace "AES" with the name of the actual cipher):
|
||||
# - AES_start_operaion(key) --> base_cipher_state
|
||||
# - AES_encrypt(base_cipher_state, in, out, length)
|
||||
# - AES_decrypt(base_cipher_state, in, out, length)
|
||||
# - AES_stop_operation(base_cipher_state)
|
||||
#
|
||||
# Where base_cipher_state is AES_State, a struct with BlockBase (set of
|
||||
# pointers to encrypt/decrypt/stop) followed by cipher-specific data.
|
||||
#
|
||||
# The API of #2 is (replace "CBC" with the name of the actual mode):
|
||||
# - CBC_start_operation(base_cipher_state) --> mode_state
|
||||
# - CBC_encrypt(mode_state, in, out, length)
|
||||
# - CBC_decrypt(mode_state, in, out, length)
|
||||
# - CBC_stop_operation(mode_state)
|
||||
#
|
||||
# where mode_state is a a pointer to base_cipher_state plus mode-specific data.
|
||||
|
||||
def _create_cipher(factory, key, mode, *args, **kwargs):
|
||||
|
||||
kwargs["key"] = key
|
||||
|
||||
if args:
|
||||
if mode in (8, 9, 10, 11, 12):
|
||||
if len(args) > 1:
|
||||
raise TypeError("Too many arguments for this mode")
|
||||
kwargs["nonce"] = args[0]
|
||||
elif mode in (2, 3, 5, 7):
|
||||
if len(args) > 1:
|
||||
raise TypeError("Too many arguments for this mode")
|
||||
kwargs["IV"] = args[0]
|
||||
elif mode == 6:
|
||||
if len(args) > 0:
|
||||
raise TypeError("Too many arguments for this mode")
|
||||
elif mode == 1:
|
||||
raise TypeError("IV is not meaningful for the ECB mode")
|
||||
|
||||
res = None
|
||||
extra_modes = kwargs.pop("add_aes_modes", False)
|
||||
|
||||
if mode == 1:
|
||||
from Crypto.Cipher._mode_ecb import _create_ecb_cipher
|
||||
res = _create_ecb_cipher(factory, **kwargs)
|
||||
elif mode == 2:
|
||||
from Crypto.Cipher._mode_cbc import _create_cbc_cipher
|
||||
res = _create_cbc_cipher(factory, **kwargs)
|
||||
elif mode == 3:
|
||||
from Crypto.Cipher._mode_cfb import _create_cfb_cipher
|
||||
res = _create_cfb_cipher(factory, **kwargs)
|
||||
elif mode == 5:
|
||||
from Crypto.Cipher._mode_ofb import _create_ofb_cipher
|
||||
res = _create_ofb_cipher(factory, **kwargs)
|
||||
elif mode == 6:
|
||||
from Crypto.Cipher._mode_ctr import _create_ctr_cipher
|
||||
res = _create_ctr_cipher(factory, **kwargs)
|
||||
elif mode == 7:
|
||||
from Crypto.Cipher._mode_openpgp import _create_openpgp_cipher
|
||||
res = _create_openpgp_cipher(factory, **kwargs)
|
||||
elif mode == 9:
|
||||
from Crypto.Cipher._mode_eax import _create_eax_cipher
|
||||
res = _create_eax_cipher(factory, **kwargs)
|
||||
elif extra_modes:
|
||||
if mode == 8:
|
||||
from Crypto.Cipher._mode_ccm import _create_ccm_cipher
|
||||
res = _create_ccm_cipher(factory, **kwargs)
|
||||
elif mode == 10:
|
||||
from Crypto.Cipher._mode_siv import _create_siv_cipher
|
||||
res = _create_siv_cipher(factory, **kwargs)
|
||||
elif mode == 11:
|
||||
from Crypto.Cipher._mode_gcm import _create_gcm_cipher
|
||||
res = _create_gcm_cipher(factory, **kwargs)
|
||||
elif mode == 12:
|
||||
from Crypto.Cipher._mode_ocb import _create_ocb_cipher
|
||||
res = _create_ocb_cipher(factory, **kwargs)
|
||||
elif mode == 13:
|
||||
from Crypto.Cipher._mode_kw import _create_kw_cipher
|
||||
res = _create_kw_cipher(factory, **kwargs)
|
||||
elif mode == 14:
|
||||
from Crypto.Cipher._mode_kwp import _create_kwp_cipher
|
||||
res = _create_kwp_cipher(factory, **kwargs)
|
||||
|
||||
if res is None:
|
||||
raise ValueError("Mode not supported")
|
||||
|
||||
return res
|
||||
Binary file not shown.
@@ -1,293 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Ciphertext Block Chaining (CBC) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['CbcMode']
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr,
|
||||
is_writeable_buffer)
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
raw_cbc_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_cbc", """
|
||||
int CBC_start_operation(void *cipher,
|
||||
const uint8_t iv[],
|
||||
size_t iv_len,
|
||||
void **pResult);
|
||||
int CBC_encrypt(void *cbcState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CBC_decrypt(void *cbcState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CBC_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class CbcMode(object):
|
||||
"""*Cipher-Block Chaining (CBC)*.
|
||||
|
||||
Each of the ciphertext blocks depends on the current
|
||||
and all previous plaintext blocks.
|
||||
|
||||
An Initialization Vector (*IV*) is required.
|
||||
|
||||
See `NIST SP800-38A`_ , Section 6.2 .
|
||||
|
||||
.. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, block_cipher, iv):
|
||||
"""Create a new block cipher, configured in CBC mode.
|
||||
|
||||
:Parameters:
|
||||
block_cipher : C pointer
|
||||
A smart pointer to the low-level block cipher instance.
|
||||
|
||||
iv : bytes/bytearray/memoryview
|
||||
The initialization vector to use for encryption or decryption.
|
||||
It is as long as the cipher block.
|
||||
|
||||
**The IV must be unpredictable**. Ideally it is picked randomly.
|
||||
|
||||
Reusing the *IV* for encryptions performed with the same key
|
||||
compromises confidentiality.
|
||||
"""
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = raw_cbc_lib.CBC_start_operation(block_cipher.get(),
|
||||
c_uint8_ptr(iv),
|
||||
c_size_t(len(iv)),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while instantiating the CBC mode"
|
||||
% result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
raw_cbc_lib.CBC_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owed
|
||||
# by the cipher mode
|
||||
block_cipher.release()
|
||||
|
||||
self.block_size = len(iv)
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.iv = _copy_bytes(None, None, iv)
|
||||
"""The Initialization Vector originally used to create the object.
|
||||
The value does not change."""
|
||||
|
||||
self.IV = self.iv
|
||||
"""Alias for `iv`"""
|
||||
|
||||
self._next = ["encrypt", "decrypt"]
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
That also means that you cannot reuse an object for encrypting
|
||||
or decrypting other data with the same key.
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
Its lenght must be multiple of the cipher block size.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() cannot be called after decrypt()")
|
||||
self._next = ["encrypt"]
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_cbc_lib.CBC_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
if result == 3:
|
||||
raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
|
||||
raise ValueError("Error %d while encrypting in CBC mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
Its length must be multiple of the cipher block size.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() cannot be called after encrypt()")
|
||||
self._next = ["decrypt"]
|
||||
|
||||
if output is None:
|
||||
plaintext = create_string_buffer(len(ciphertext))
|
||||
else:
|
||||
plaintext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(ciphertext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_cbc_lib.CBC_decrypt(self._state.get(),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_size_t(len(ciphertext)))
|
||||
if result:
|
||||
if result == 3:
|
||||
raise ValueError("Data must be padded to %d byte boundary in CBC mode" % self.block_size)
|
||||
raise ValueError("Error %d while decrypting in CBC mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(plaintext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _create_cbc_cipher(factory, **kwargs):
|
||||
"""Instantiate a cipher object that performs CBC encryption/decryption.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The underlying block cipher, a module from ``Crypto.Cipher``.
|
||||
|
||||
:Keywords:
|
||||
iv : bytes/bytearray/memoryview
|
||||
The IV to use for CBC.
|
||||
|
||||
IV : bytes/bytearray/memoryview
|
||||
Alias for ``iv``.
|
||||
|
||||
Any other keyword will be passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present).
|
||||
"""
|
||||
|
||||
cipher_state = factory._create_base_cipher(kwargs)
|
||||
iv = kwargs.pop("IV", None)
|
||||
IV = kwargs.pop("iv", None)
|
||||
|
||||
if (None, None) == (iv, IV):
|
||||
iv = get_random_bytes(factory.block_size)
|
||||
if iv is not None:
|
||||
if IV is not None:
|
||||
raise TypeError("You must either use 'iv' or 'IV', not both")
|
||||
else:
|
||||
iv = IV
|
||||
|
||||
if len(iv) != factory.block_size:
|
||||
raise ValueError("Incorrect IV length (it must be %d bytes long)" %
|
||||
factory.block_size)
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters for CBC: %s" % str(kwargs))
|
||||
|
||||
return CbcMode(cipher_state, iv)
|
||||
@@ -1,25 +0,0 @@
|
||||
from typing import Union, overload
|
||||
|
||||
from Crypto.Util._raw_api import SmartPointer
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['CbcMode']
|
||||
|
||||
class CbcMode(object):
|
||||
block_size: int
|
||||
iv: Buffer
|
||||
IV: Buffer
|
||||
|
||||
def __init__(self,
|
||||
block_cipher: SmartPointer,
|
||||
iv: Buffer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
@@ -1,671 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Counter with CBC-MAC (CCM) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['CcmMode']
|
||||
|
||||
import struct
|
||||
from binascii import unhexlify
|
||||
|
||||
from Crypto.Util.py3compat import (byte_string, bord,
|
||||
_copy_bytes)
|
||||
from Crypto.Util._raw_api import is_writeable_buffer
|
||||
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Util.number import long_to_bytes
|
||||
|
||||
from Crypto.Hash import BLAKE2s
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
|
||||
def enum(**enums):
|
||||
return type('Enum', (), enums)
|
||||
|
||||
MacStatus = enum(NOT_STARTED=0, PROCESSING_AUTH_DATA=1, PROCESSING_PLAINTEXT=2)
|
||||
|
||||
|
||||
class CCMMessageTooLongError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class CcmMode(object):
|
||||
"""Counter with CBC-MAC (CCM).
|
||||
|
||||
This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
|
||||
It provides both confidentiality and authenticity.
|
||||
|
||||
The header of the message may be left in the clear, if needed, and it will
|
||||
still be subject to authentication. The decryption step tells the receiver
|
||||
if the message comes from a source that really knowns the secret key.
|
||||
Additionally, decryption detects if any part of the message - including the
|
||||
header - has been modified or corrupted.
|
||||
|
||||
This mode requires a nonce. The nonce shall never repeat for two
|
||||
different messages encrypted with the same key, but it does not need
|
||||
to be random.
|
||||
Note that there is a trade-off between the size of the nonce and the
|
||||
maximum size of a single message you can encrypt.
|
||||
|
||||
It is important to use a large nonce if the key is reused across several
|
||||
messages and the nonce is chosen randomly.
|
||||
|
||||
It is acceptable to us a short nonce if the key is only used a few times or
|
||||
if the nonce is taken from a counter.
|
||||
|
||||
The following table shows the trade-off when the nonce is chosen at
|
||||
random. The column on the left shows how many messages it takes
|
||||
for the keystream to repeat **on average**. In practice, you will want to
|
||||
stop using the key way before that.
|
||||
|
||||
+--------------------+---------------+-------------------+
|
||||
| Avg. # of messages | nonce | Max. message |
|
||||
| before keystream | size | size |
|
||||
| repeats | (bytes) | (bytes) |
|
||||
+====================+===============+===================+
|
||||
| 2^52 | 13 | 64K |
|
||||
+--------------------+---------------+-------------------+
|
||||
| 2^48 | 12 | 16M |
|
||||
+--------------------+---------------+-------------------+
|
||||
| 2^44 | 11 | 4G |
|
||||
+--------------------+---------------+-------------------+
|
||||
| 2^40 | 10 | 1T |
|
||||
+--------------------+---------------+-------------------+
|
||||
| 2^36 | 9 | 64P |
|
||||
+--------------------+---------------+-------------------+
|
||||
| 2^32 | 8 | 16E |
|
||||
+--------------------+---------------+-------------------+
|
||||
|
||||
This mode is only available for ciphers that operate on 128 bits blocks
|
||||
(e.g. AES but not TDES).
|
||||
|
||||
See `NIST SP800-38C`_ or RFC3610_.
|
||||
|
||||
.. _`NIST SP800-38C`: http://csrc.nist.gov/publications/nistpubs/800-38C/SP800-38C.pdf
|
||||
.. _RFC3610: https://tools.ietf.org/html/rfc3610
|
||||
.. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, key, nonce, mac_len, msg_len, assoc_len,
|
||||
cipher_params):
|
||||
|
||||
self.block_size = factory.block_size
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
"""The nonce used for this cipher instance"""
|
||||
|
||||
self._factory = factory
|
||||
self._key = _copy_bytes(None, None, key)
|
||||
self._mac_len = mac_len
|
||||
self._msg_len = msg_len
|
||||
self._assoc_len = assoc_len
|
||||
self._cipher_params = cipher_params
|
||||
|
||||
self._mac_tag = None # Cache for MAC tag
|
||||
|
||||
if self.block_size != 16:
|
||||
raise ValueError("CCM mode is only available for ciphers"
|
||||
" that operate on 128 bits blocks")
|
||||
|
||||
# MAC tag length (Tlen)
|
||||
if mac_len not in (4, 6, 8, 10, 12, 14, 16):
|
||||
raise ValueError("Parameter 'mac_len' must be even"
|
||||
" and in the range 4..16 (not %d)" % mac_len)
|
||||
|
||||
# Nonce value
|
||||
if not (7 <= len(nonce) <= 13):
|
||||
raise ValueError("Length of parameter 'nonce' must be"
|
||||
" in the range 7..13 bytes")
|
||||
|
||||
# Message length (if known already)
|
||||
q = 15 - len(nonce) # length of Q, the encoded message length
|
||||
if msg_len and len(long_to_bytes(msg_len)) > q:
|
||||
raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(nonce))
|
||||
|
||||
# Create MAC object (the tag will be the last block
|
||||
# bytes worth of ciphertext)
|
||||
self._mac = self._factory.new(key,
|
||||
factory.MODE_CBC,
|
||||
iv=b'\x00' * 16,
|
||||
**cipher_params)
|
||||
self._mac_status = MacStatus.NOT_STARTED
|
||||
self._t = None
|
||||
|
||||
# Allowed transitions after initialization
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
# Cumulative lengths
|
||||
self._cumul_assoc_len = 0
|
||||
self._cumul_msg_len = 0
|
||||
|
||||
# Cache for unaligned associated data/plaintext.
|
||||
# This is a list with byte strings, but when the MAC starts,
|
||||
# it will become a binary string no longer than the block size.
|
||||
self._cache = []
|
||||
|
||||
# Start CTR cipher, by formatting the counter (A.3)
|
||||
self._cipher = self._factory.new(key,
|
||||
self._factory.MODE_CTR,
|
||||
nonce=struct.pack("B", q - 1) + self.nonce,
|
||||
**cipher_params)
|
||||
|
||||
# S_0, step 6 in 6.1 for j=0
|
||||
self._s_0 = self._cipher.encrypt(b'\x00' * 16)
|
||||
|
||||
# Try to start the MAC
|
||||
if None not in (assoc_len, msg_len):
|
||||
self._start_mac()
|
||||
|
||||
def _start_mac(self):
|
||||
|
||||
assert(self._mac_status == MacStatus.NOT_STARTED)
|
||||
assert(None not in (self._assoc_len, self._msg_len))
|
||||
assert(isinstance(self._cache, list))
|
||||
|
||||
# Formatting control information and nonce (A.2.1)
|
||||
q = 15 - len(self.nonce) # length of Q, the encoded message length (2..8)
|
||||
flags = (self._assoc_len > 0) << 6
|
||||
flags |= ((self._mac_len - 2) // 2) << 3
|
||||
flags |= q - 1
|
||||
b_0 = struct.pack("B", flags) + self.nonce + long_to_bytes(self._msg_len, q)
|
||||
|
||||
# Formatting associated data (A.2.2)
|
||||
# Encoded 'a' is concatenated with the associated data 'A'
|
||||
assoc_len_encoded = b''
|
||||
if self._assoc_len > 0:
|
||||
if self._assoc_len < (2 ** 16 - 2 ** 8):
|
||||
enc_size = 2
|
||||
elif self._assoc_len < (2 ** 32):
|
||||
assoc_len_encoded = b'\xFF\xFE'
|
||||
enc_size = 4
|
||||
else:
|
||||
assoc_len_encoded = b'\xFF\xFF'
|
||||
enc_size = 8
|
||||
assoc_len_encoded += long_to_bytes(self._assoc_len, enc_size)
|
||||
|
||||
# b_0 and assoc_len_encoded must be processed first
|
||||
self._cache.insert(0, b_0)
|
||||
self._cache.insert(1, assoc_len_encoded)
|
||||
|
||||
# Process all the data cached so far
|
||||
first_data_to_mac = b"".join(self._cache)
|
||||
self._cache = b""
|
||||
self._mac_status = MacStatus.PROCESSING_AUTH_DATA
|
||||
self._update(first_data_to_mac)
|
||||
|
||||
def _pad_cache_and_update(self):
|
||||
|
||||
assert(self._mac_status != MacStatus.NOT_STARTED)
|
||||
assert(len(self._cache) < self.block_size)
|
||||
|
||||
# Associated data is concatenated with the least number
|
||||
# of zero bytes (possibly none) to reach alignment to
|
||||
# the 16 byte boundary (A.2.3)
|
||||
len_cache = len(self._cache)
|
||||
if len_cache > 0:
|
||||
self._update(b'\x00' * (self.block_size - len_cache))
|
||||
|
||||
def update(self, assoc_data):
|
||||
"""Protect associated data
|
||||
|
||||
If there is any associated data, the caller has to invoke
|
||||
this function one or more times, before using
|
||||
``decrypt`` or ``encrypt``.
|
||||
|
||||
By *associated data* it is meant any data (e.g. packet headers) that
|
||||
will not be encrypted and will be transmitted in the clear.
|
||||
However, the receiver is still able to detect any modification to it.
|
||||
In CCM, the *associated data* is also called
|
||||
*additional authenticated data* (AAD).
|
||||
|
||||
If there is no associated data, this method must not be called.
|
||||
|
||||
The caller may split associated data in segments of any size, and
|
||||
invoke this method multiple times, each time with the next segment.
|
||||
|
||||
:Parameters:
|
||||
assoc_data : bytes/bytearray/memoryview
|
||||
A piece of associated data. There are no restrictions on its size.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() can only be called"
|
||||
" immediately after initialization")
|
||||
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
self._cumul_assoc_len += len(assoc_data)
|
||||
if self._assoc_len is not None and \
|
||||
self._cumul_assoc_len > self._assoc_len:
|
||||
raise ValueError("Associated data is too long")
|
||||
|
||||
self._update(assoc_data)
|
||||
return self
|
||||
|
||||
def _update(self, assoc_data_pt=b""):
|
||||
"""Update the MAC with associated data or plaintext
|
||||
(without FSM checks)"""
|
||||
|
||||
# If MAC has not started yet, we just park the data into a list.
|
||||
# If the data is mutable, we create a copy and store that instead.
|
||||
if self._mac_status == MacStatus.NOT_STARTED:
|
||||
if is_writeable_buffer(assoc_data_pt):
|
||||
assoc_data_pt = _copy_bytes(None, None, assoc_data_pt)
|
||||
self._cache.append(assoc_data_pt)
|
||||
return
|
||||
|
||||
assert(len(self._cache) < self.block_size)
|
||||
|
||||
if len(self._cache) > 0:
|
||||
filler = min(self.block_size - len(self._cache),
|
||||
len(assoc_data_pt))
|
||||
self._cache += _copy_bytes(None, filler, assoc_data_pt)
|
||||
assoc_data_pt = _copy_bytes(filler, None, assoc_data_pt)
|
||||
|
||||
if len(self._cache) < self.block_size:
|
||||
return
|
||||
|
||||
# The cache is exactly one block
|
||||
self._t = self._mac.encrypt(self._cache)
|
||||
self._cache = b""
|
||||
|
||||
update_len = len(assoc_data_pt) // self.block_size * self.block_size
|
||||
self._cache = _copy_bytes(update_len, None, assoc_data_pt)
|
||||
if update_len > 0:
|
||||
self._t = self._mac.encrypt(assoc_data_pt[:update_len])[-16:]
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
This method can be called only **once** if ``msg_len`` was
|
||||
not passed at initialization.
|
||||
|
||||
If ``msg_len`` was given, the data to encrypt can be broken
|
||||
up in two or more pieces and `encrypt` can be called
|
||||
multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
self._next = ["encrypt", "digest"]
|
||||
|
||||
# No more associated data allowed from now
|
||||
if self._assoc_len is None:
|
||||
assert(isinstance(self._cache, list))
|
||||
self._assoc_len = sum([len(x) for x in self._cache])
|
||||
if self._msg_len is not None:
|
||||
self._start_mac()
|
||||
else:
|
||||
if self._cumul_assoc_len < self._assoc_len:
|
||||
raise ValueError("Associated data is too short")
|
||||
|
||||
# Only once piece of plaintext accepted if message length was
|
||||
# not declared in advance
|
||||
if self._msg_len is None:
|
||||
q = 15 - len(self.nonce)
|
||||
if len(long_to_bytes(len(plaintext))) > q:
|
||||
raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(self.nonce))
|
||||
|
||||
self._msg_len = len(plaintext)
|
||||
self._start_mac()
|
||||
self._next = ["digest"]
|
||||
|
||||
self._cumul_msg_len += len(plaintext)
|
||||
if self._cumul_msg_len > self._msg_len:
|
||||
msg = "Message longer than declared for (%u bytes vs %u bytes" % \
|
||||
(self._cumul_msg_len, self._msg_len)
|
||||
raise CCMMessageTooLongError(msg)
|
||||
|
||||
if self._mac_status == MacStatus.PROCESSING_AUTH_DATA:
|
||||
# Associated data is concatenated with the least number
|
||||
# of zero bytes (possibly none) to reach alignment to
|
||||
# the 16 byte boundary (A.2.3)
|
||||
self._pad_cache_and_update()
|
||||
self._mac_status = MacStatus.PROCESSING_PLAINTEXT
|
||||
|
||||
self._update(plaintext)
|
||||
return self._cipher.encrypt(plaintext, output=output)
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
This method can be called only **once** if ``msg_len`` was
|
||||
not passed at initialization.
|
||||
|
||||
If ``msg_len`` was given, the data to decrypt can be
|
||||
broken up in two or more pieces and `decrypt` can be
|
||||
called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() can only be called"
|
||||
" after initialization or an update()")
|
||||
self._next = ["decrypt", "verify"]
|
||||
|
||||
# No more associated data allowed from now
|
||||
if self._assoc_len is None:
|
||||
assert(isinstance(self._cache, list))
|
||||
self._assoc_len = sum([len(x) for x in self._cache])
|
||||
if self._msg_len is not None:
|
||||
self._start_mac()
|
||||
else:
|
||||
if self._cumul_assoc_len < self._assoc_len:
|
||||
raise ValueError("Associated data is too short")
|
||||
|
||||
# Only once piece of ciphertext accepted if message length was
|
||||
# not declared in advance
|
||||
if self._msg_len is None:
|
||||
q = 15 - len(self.nonce)
|
||||
if len(long_to_bytes(len(ciphertext))) > q:
|
||||
raise CCMMessageTooLongError("Message too long for a %u-byte nonce" % len(self.nonce))
|
||||
|
||||
self._msg_len = len(ciphertext)
|
||||
self._start_mac()
|
||||
self._next = ["verify"]
|
||||
|
||||
self._cumul_msg_len += len(ciphertext)
|
||||
if self._cumul_msg_len > self._msg_len:
|
||||
msg = "Message longer than declared for (%u bytes vs %u bytes" % \
|
||||
(self._cumul_msg_len, self._msg_len)
|
||||
raise CCMMessageTooLongError(msg)
|
||||
|
||||
if self._mac_status == MacStatus.PROCESSING_AUTH_DATA:
|
||||
# Associated data is concatenated with the least number
|
||||
# of zero bytes (possibly none) to reach alignment to
|
||||
# the 16 byte boundary (A.2.3)
|
||||
self._pad_cache_and_update()
|
||||
self._mac_status = MacStatus.PROCESSING_PLAINTEXT
|
||||
|
||||
# Encrypt is equivalent to decrypt with the CTR mode
|
||||
plaintext = self._cipher.encrypt(ciphertext, output=output)
|
||||
if output is None:
|
||||
self._update(plaintext)
|
||||
else:
|
||||
self._update(output)
|
||||
return plaintext
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method returns the MAC that shall be sent to the receiver,
|
||||
together with the ciphertext.
|
||||
|
||||
:Return: the MAC, as a byte string.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() cannot be called when decrypting"
|
||||
" or validating a message")
|
||||
self._next = ["digest"]
|
||||
return self._digest()
|
||||
|
||||
def _digest(self):
|
||||
if self._mac_tag:
|
||||
return self._mac_tag
|
||||
|
||||
if self._assoc_len is None:
|
||||
assert(isinstance(self._cache, list))
|
||||
self._assoc_len = sum([len(x) for x in self._cache])
|
||||
if self._msg_len is not None:
|
||||
self._start_mac()
|
||||
else:
|
||||
if self._cumul_assoc_len < self._assoc_len:
|
||||
raise ValueError("Associated data is too short")
|
||||
|
||||
if self._msg_len is None:
|
||||
self._msg_len = 0
|
||||
self._start_mac()
|
||||
|
||||
if self._cumul_msg_len != self._msg_len:
|
||||
raise ValueError("Message is too short")
|
||||
|
||||
# Both associated data and payload are concatenated with the least
|
||||
# number of zero bytes (possibly none) that align it to the
|
||||
# 16 byte boundary (A.2.2 and A.2.3)
|
||||
self._pad_cache_and_update()
|
||||
|
||||
# Step 8 in 6.1 (T xor MSB_Tlen(S_0))
|
||||
self._mac_tag = strxor(self._t, self._s_0)[:self._mac_len]
|
||||
|
||||
return self._mac_tag
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* MAC tag.
|
||||
|
||||
This method is like `digest`.
|
||||
|
||||
:Return: the MAC, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method checks if the decrypted message is indeed valid
|
||||
(that is, if the key is correct) and it has not been
|
||||
tampered with while in transit.
|
||||
|
||||
:Parameters:
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called"
|
||||
" when encrypting a message")
|
||||
self._next = ["verify"]
|
||||
|
||||
self._digest()
|
||||
secret = get_random_bytes(16)
|
||||
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* MAC tag.
|
||||
|
||||
This method is like `verify`.
|
||||
|
||||
:Parameters:
|
||||
hex_mac_tag : string
|
||||
This is the *printable* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext, output=None):
|
||||
"""Perform encrypt() and digest() in one step.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
a tuple with two items:
|
||||
|
||||
- the ciphertext, as ``bytes``
|
||||
- the MAC tag, as ``bytes``
|
||||
|
||||
The first item becomes ``None`` when the ``output`` parameter
|
||||
specified a location for the result.
|
||||
"""
|
||||
|
||||
return self.encrypt(plaintext, output=output), self.digest()
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
|
||||
"""Perform decrypt() and verify() in one step.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return: the plaintext as ``bytes`` or ``None`` when the ``output``
|
||||
parameter specified a location for the result.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
plaintext = self.decrypt(ciphertext, output=output)
|
||||
self.verify(received_mac_tag)
|
||||
return plaintext
|
||||
|
||||
|
||||
def _create_ccm_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in CCM mode.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
A symmetric cipher module from `Crypto.Cipher` (like
|
||||
`Crypto.Cipher.AES`).
|
||||
|
||||
:Keywords:
|
||||
key : bytes/bytearray/memoryview
|
||||
The secret key to use in the symmetric cipher.
|
||||
|
||||
nonce : bytes/bytearray/memoryview
|
||||
A value that must never be reused for any other encryption.
|
||||
|
||||
Its length must be in the range ``[7..13]``.
|
||||
11 or 12 bytes are reasonable values in general. Bear in
|
||||
mind that with CCM there is a trade-off between nonce length and
|
||||
maximum message size.
|
||||
|
||||
If not specified, a 11 byte long random string is used.
|
||||
|
||||
mac_len : integer
|
||||
Length of the MAC, in bytes. It must be even and in
|
||||
the range ``[4..16]``. The default is 16.
|
||||
|
||||
msg_len : integer
|
||||
Length of the message to (de)cipher.
|
||||
If not specified, ``encrypt`` or ``decrypt`` may only be called once.
|
||||
|
||||
assoc_len : integer
|
||||
Length of the associated data.
|
||||
If not specified, all data is internally buffered.
|
||||
"""
|
||||
|
||||
try:
|
||||
key = key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter: " + str(e))
|
||||
|
||||
nonce = kwargs.pop("nonce", None) # N
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(11)
|
||||
mac_len = kwargs.pop("mac_len", factory.block_size)
|
||||
msg_len = kwargs.pop("msg_len", None) # p
|
||||
assoc_len = kwargs.pop("assoc_len", None) # a
|
||||
cipher_params = dict(kwargs)
|
||||
|
||||
return CcmMode(factory, key, nonce, mac_len, msg_len,
|
||||
assoc_len, cipher_params)
|
||||
@@ -1,52 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, overload, Dict, Tuple, Optional
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['CcmMode']
|
||||
|
||||
|
||||
class CCMMessageTooLongError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class CcmMode(object):
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
mac_len: int,
|
||||
msg_len: Optional[int],
|
||||
assoc_len: Optional[int],
|
||||
cipher_params: Dict) -> None: ...
|
||||
|
||||
def update(self, assoc_data: Buffer) -> CcmMode: ...
|
||||
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, hex_mac_tag: str) -> None: ...
|
||||
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer,
|
||||
output: Buffer) -> Tuple[None, bytes]: ...
|
||||
def decrypt_and_verify(self,
|
||||
ciphertext: Buffer,
|
||||
received_mac_tag: Buffer,
|
||||
output: Optional[Union[bytearray, memoryview]] = ...) -> bytes: ...
|
||||
@@ -1,293 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/mode_cfb.py : CFB mode
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Counter Feedback (CFB) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['CfbMode']
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr,
|
||||
is_writeable_buffer)
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
raw_cfb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_cfb","""
|
||||
int CFB_start_operation(void *cipher,
|
||||
const uint8_t iv[],
|
||||
size_t iv_len,
|
||||
size_t segment_len, /* In bytes */
|
||||
void **pResult);
|
||||
int CFB_encrypt(void *cfbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CFB_decrypt(void *cfbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CFB_stop_operation(void *state);"""
|
||||
)
|
||||
|
||||
|
||||
class CfbMode(object):
|
||||
"""*Cipher FeedBack (CFB)*.
|
||||
|
||||
This mode is similar to CFB, but it transforms
|
||||
the underlying block cipher into a stream cipher.
|
||||
|
||||
Plaintext and ciphertext are processed in *segments*
|
||||
of **s** bits. The mode is therefore sometimes
|
||||
labelled **s**-bit CFB.
|
||||
|
||||
An Initialization Vector (*IV*) is required.
|
||||
|
||||
See `NIST SP800-38A`_ , Section 6.3.
|
||||
|
||||
.. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, block_cipher, iv, segment_size):
|
||||
"""Create a new block cipher, configured in CFB mode.
|
||||
|
||||
:Parameters:
|
||||
block_cipher : C pointer
|
||||
A smart pointer to the low-level block cipher instance.
|
||||
|
||||
iv : bytes/bytearray/memoryview
|
||||
The initialization vector to use for encryption or decryption.
|
||||
It is as long as the cipher block.
|
||||
|
||||
**The IV must be unpredictable**. Ideally it is picked randomly.
|
||||
|
||||
Reusing the *IV* for encryptions performed with the same key
|
||||
compromises confidentiality.
|
||||
|
||||
segment_size : integer
|
||||
The number of bytes the plaintext and ciphertext are segmented in.
|
||||
"""
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = raw_cfb_lib.CFB_start_operation(block_cipher.get(),
|
||||
c_uint8_ptr(iv),
|
||||
c_size_t(len(iv)),
|
||||
c_size_t(segment_size),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while instantiating the CFB mode" % result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
raw_cfb_lib.CFB_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owed
|
||||
# by the cipher mode
|
||||
block_cipher.release()
|
||||
|
||||
self.block_size = len(iv)
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.iv = _copy_bytes(None, None, iv)
|
||||
"""The Initialization Vector originally used to create the object.
|
||||
The value does not change."""
|
||||
|
||||
self.IV = self.iv
|
||||
"""Alias for `iv`"""
|
||||
|
||||
self._next = ["encrypt", "decrypt"]
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() cannot be called after decrypt()")
|
||||
self._next = ["encrypt"]
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_cfb_lib.CFB_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while encrypting in CFB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() cannot be called after encrypt()")
|
||||
self._next = ["decrypt"]
|
||||
|
||||
if output is None:
|
||||
plaintext = create_string_buffer(len(ciphertext))
|
||||
else:
|
||||
plaintext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(ciphertext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_cfb_lib.CFB_decrypt(self._state.get(),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_size_t(len(ciphertext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while decrypting in CFB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(plaintext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _create_cfb_cipher(factory, **kwargs):
|
||||
"""Instantiate a cipher object that performs CFB encryption/decryption.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The underlying block cipher, a module from ``Crypto.Cipher``.
|
||||
|
||||
:Keywords:
|
||||
iv : bytes/bytearray/memoryview
|
||||
The IV to use for CFB.
|
||||
|
||||
IV : bytes/bytearray/memoryview
|
||||
Alias for ``iv``.
|
||||
|
||||
segment_size : integer
|
||||
The number of bit the plaintext and ciphertext are segmented in.
|
||||
If not present, the default is 8.
|
||||
|
||||
Any other keyword will be passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present).
|
||||
"""
|
||||
|
||||
cipher_state = factory._create_base_cipher(kwargs)
|
||||
|
||||
iv = kwargs.pop("IV", None)
|
||||
IV = kwargs.pop("iv", None)
|
||||
|
||||
if (None, None) == (iv, IV):
|
||||
iv = get_random_bytes(factory.block_size)
|
||||
if iv is not None:
|
||||
if IV is not None:
|
||||
raise TypeError("You must either use 'iv' or 'IV', not both")
|
||||
else:
|
||||
iv = IV
|
||||
|
||||
if len(iv) != factory.block_size:
|
||||
raise ValueError("Incorrect IV length (it must be %d bytes long)" %
|
||||
factory.block_size)
|
||||
|
||||
segment_size_bytes, rem = divmod(kwargs.pop("segment_size", 8), 8)
|
||||
if segment_size_bytes == 0 or rem != 0:
|
||||
raise ValueError("'segment_size' must be positive and multiple of 8 bits")
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters for CFB: %s" % str(kwargs))
|
||||
return CfbMode(cipher_state, iv, segment_size_bytes)
|
||||
@@ -1,26 +0,0 @@
|
||||
from typing import Union, overload
|
||||
|
||||
from Crypto.Util._raw_api import SmartPointer
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['CfbMode']
|
||||
|
||||
|
||||
class CfbMode(object):
|
||||
block_size: int
|
||||
iv: Buffer
|
||||
IV: Buffer
|
||||
|
||||
def __init__(self,
|
||||
block_cipher: SmartPointer,
|
||||
iv: Buffer,
|
||||
segment_size: int) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@@ -1,393 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/mode_ctr.py : CTR mode
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Counter (CTR) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['CtrMode']
|
||||
|
||||
import struct
|
||||
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr,
|
||||
is_writeable_buffer)
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
from Crypto.Util.py3compat import _copy_bytes, is_native_int
|
||||
from Crypto.Util.number import long_to_bytes
|
||||
|
||||
raw_ctr_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ctr", """
|
||||
int CTR_start_operation(void *cipher,
|
||||
uint8_t initialCounterBlock[],
|
||||
size_t initialCounterBlock_len,
|
||||
size_t prefix_len,
|
||||
unsigned counter_len,
|
||||
unsigned littleEndian,
|
||||
void **pResult);
|
||||
int CTR_encrypt(void *ctrState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CTR_decrypt(void *ctrState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int CTR_stop_operation(void *ctrState);"""
|
||||
)
|
||||
|
||||
|
||||
class CtrMode(object):
|
||||
"""*CounTeR (CTR)* mode.
|
||||
|
||||
This mode is very similar to ECB, in that
|
||||
encryption of one block is done independently of all other blocks.
|
||||
|
||||
Unlike ECB, the block *position* contributes to the encryption
|
||||
and no information leaks about symbol frequency.
|
||||
|
||||
Each message block is associated to a *counter* which
|
||||
must be unique across all messages that get encrypted
|
||||
with the same key (not just within the same message).
|
||||
The counter is as big as the block size.
|
||||
|
||||
Counters can be generated in several ways. The most
|
||||
straightword one is to choose an *initial counter block*
|
||||
(which can be made public, similarly to the *IV* for the
|
||||
other modes) and increment its lowest **m** bits by one
|
||||
(modulo *2^m*) for each block. In most cases, **m** is
|
||||
chosen to be half the block size.
|
||||
|
||||
See `NIST SP800-38A`_, Section 6.5 (for the mode) and
|
||||
Appendix B (for how to manage the *initial counter block*).
|
||||
|
||||
.. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, block_cipher, initial_counter_block,
|
||||
prefix_len, counter_len, little_endian):
|
||||
"""Create a new block cipher, configured in CTR mode.
|
||||
|
||||
:Parameters:
|
||||
block_cipher : C pointer
|
||||
A smart pointer to the low-level block cipher instance.
|
||||
|
||||
initial_counter_block : bytes/bytearray/memoryview
|
||||
The initial plaintext to use to generate the key stream.
|
||||
|
||||
It is as large as the cipher block, and it embeds
|
||||
the initial value of the counter.
|
||||
|
||||
This value must not be reused.
|
||||
It shall contain a nonce or a random component.
|
||||
Reusing the *initial counter block* for encryptions
|
||||
performed with the same key compromises confidentiality.
|
||||
|
||||
prefix_len : integer
|
||||
The amount of bytes at the beginning of the counter block
|
||||
that never change.
|
||||
|
||||
counter_len : integer
|
||||
The length in bytes of the counter embedded in the counter
|
||||
block.
|
||||
|
||||
little_endian : boolean
|
||||
True if the counter in the counter block is an integer encoded
|
||||
in little endian mode. If False, it is big endian.
|
||||
"""
|
||||
|
||||
if len(initial_counter_block) == prefix_len + counter_len:
|
||||
self.nonce = _copy_bytes(None, prefix_len, initial_counter_block)
|
||||
"""Nonce; not available if there is a fixed suffix"""
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = raw_ctr_lib.CTR_start_operation(block_cipher.get(),
|
||||
c_uint8_ptr(initial_counter_block),
|
||||
c_size_t(len(initial_counter_block)),
|
||||
c_size_t(prefix_len),
|
||||
counter_len,
|
||||
little_endian,
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %X while instantiating the CTR mode"
|
||||
% result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
raw_ctr_lib.CTR_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owed
|
||||
# by the cipher mode
|
||||
block_cipher.release()
|
||||
|
||||
self.block_size = len(initial_counter_block)
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self._next = ["encrypt", "decrypt"]
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() cannot be called after decrypt()")
|
||||
self._next = ["encrypt"]
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ctr_lib.CTR_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
if result == 0x60002:
|
||||
raise OverflowError("The counter has wrapped around in"
|
||||
" CTR mode")
|
||||
raise ValueError("Error %X while encrypting in CTR mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() cannot be called after encrypt()")
|
||||
self._next = ["decrypt"]
|
||||
|
||||
if output is None:
|
||||
plaintext = create_string_buffer(len(ciphertext))
|
||||
else:
|
||||
plaintext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(ciphertext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ctr_lib.CTR_decrypt(self._state.get(),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_size_t(len(ciphertext)))
|
||||
if result:
|
||||
if result == 0x60002:
|
||||
raise OverflowError("The counter has wrapped around in"
|
||||
" CTR mode")
|
||||
raise ValueError("Error %X while decrypting in CTR mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(plaintext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _create_ctr_cipher(factory, **kwargs):
|
||||
"""Instantiate a cipher object that performs CTR encryption/decryption.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The underlying block cipher, a module from ``Crypto.Cipher``.
|
||||
|
||||
:Keywords:
|
||||
nonce : bytes/bytearray/memoryview
|
||||
The fixed part at the beginning of the counter block - the rest is
|
||||
the counter number that gets increased when processing the next block.
|
||||
The nonce must be such that no two messages are encrypted under the
|
||||
same key and the same nonce.
|
||||
|
||||
The nonce must be shorter than the block size (it can have
|
||||
zero length; the counter is then as long as the block).
|
||||
|
||||
If this parameter is not present, a random nonce will be created with
|
||||
length equal to half the block size. No random nonce shorter than
|
||||
64 bits will be created though - you must really think through all
|
||||
security consequences of using such a short block size.
|
||||
|
||||
initial_value : posive integer or bytes/bytearray/memoryview
|
||||
The initial value for the counter. If not present, the cipher will
|
||||
start counting from 0. The value is incremented by one for each block.
|
||||
The counter number is encoded in big endian mode.
|
||||
|
||||
counter : object
|
||||
Instance of ``Crypto.Util.Counter``, which allows full customization
|
||||
of the counter block. This parameter is incompatible to both ``nonce``
|
||||
and ``initial_value``.
|
||||
|
||||
Any other keyword will be passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present).
|
||||
"""
|
||||
|
||||
cipher_state = factory._create_base_cipher(kwargs)
|
||||
|
||||
counter = kwargs.pop("counter", None)
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
initial_value = kwargs.pop("initial_value", None)
|
||||
if kwargs:
|
||||
raise TypeError("Invalid parameters for CTR mode: %s" % str(kwargs))
|
||||
|
||||
if counter is not None and (nonce, initial_value) != (None, None):
|
||||
raise TypeError("'counter' and 'nonce'/'initial_value'"
|
||||
" are mutually exclusive")
|
||||
|
||||
if counter is None:
|
||||
# Crypto.Util.Counter is not used
|
||||
if nonce is None:
|
||||
if factory.block_size < 16:
|
||||
raise TypeError("Impossible to create a safe nonce for short"
|
||||
" block sizes")
|
||||
nonce = get_random_bytes(factory.block_size // 2)
|
||||
else:
|
||||
if len(nonce) >= factory.block_size:
|
||||
raise ValueError("Nonce is too long")
|
||||
|
||||
# What is not nonce is counter
|
||||
counter_len = factory.block_size - len(nonce)
|
||||
|
||||
if initial_value is None:
|
||||
initial_value = 0
|
||||
|
||||
if is_native_int(initial_value):
|
||||
if (1 << (counter_len * 8)) - 1 < initial_value:
|
||||
raise ValueError("Initial counter value is too large")
|
||||
initial_counter_block = nonce + long_to_bytes(initial_value, counter_len)
|
||||
else:
|
||||
if len(initial_value) != counter_len:
|
||||
raise ValueError("Incorrect length for counter byte string (%d bytes, expected %d)" %
|
||||
(len(initial_value), counter_len))
|
||||
initial_counter_block = nonce + initial_value
|
||||
|
||||
return CtrMode(cipher_state,
|
||||
initial_counter_block,
|
||||
len(nonce), # prefix
|
||||
counter_len,
|
||||
False) # little_endian
|
||||
|
||||
# Crypto.Util.Counter is used
|
||||
|
||||
# 'counter' used to be a callable object, but now it is
|
||||
# just a dictionary for backward compatibility.
|
||||
_counter = dict(counter)
|
||||
try:
|
||||
counter_len = _counter.pop("counter_len")
|
||||
prefix = _counter.pop("prefix")
|
||||
suffix = _counter.pop("suffix")
|
||||
initial_value = _counter.pop("initial_value")
|
||||
little_endian = _counter.pop("little_endian")
|
||||
except KeyError:
|
||||
raise TypeError("Incorrect counter object"
|
||||
" (use Crypto.Util.Counter.new)")
|
||||
|
||||
# Compute initial counter block
|
||||
words = []
|
||||
while initial_value > 0:
|
||||
words.append(struct.pack('B', initial_value & 255))
|
||||
initial_value >>= 8
|
||||
words += [b'\x00'] * max(0, counter_len - len(words))
|
||||
if not little_endian:
|
||||
words.reverse()
|
||||
initial_counter_block = prefix + b"".join(words) + suffix
|
||||
|
||||
if len(initial_counter_block) != factory.block_size:
|
||||
raise ValueError("Size of the counter block (%d bytes) must match"
|
||||
" block size (%d)" % (len(initial_counter_block),
|
||||
factory.block_size))
|
||||
|
||||
return CtrMode(cipher_state, initial_counter_block,
|
||||
len(prefix), counter_len, little_endian)
|
||||
@@ -1,27 +0,0 @@
|
||||
from typing import Union, overload
|
||||
|
||||
from Crypto.Util._raw_api import SmartPointer
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['CtrMode']
|
||||
|
||||
class CtrMode(object):
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self,
|
||||
block_cipher: SmartPointer,
|
||||
initial_counter_block: Buffer,
|
||||
prefix_len: int,
|
||||
counter_len: int,
|
||||
little_endian: bool) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
@@ -1,408 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
EAX mode.
|
||||
"""
|
||||
|
||||
__all__ = ['EaxMode']
|
||||
|
||||
import struct
|
||||
from binascii import unhexlify
|
||||
|
||||
from Crypto.Util.py3compat import byte_string, bord, _copy_bytes
|
||||
|
||||
from Crypto.Util._raw_api import is_buffer
|
||||
|
||||
from Crypto.Util.strxor import strxor
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
|
||||
from Crypto.Hash import CMAC, BLAKE2s
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
|
||||
class EaxMode(object):
|
||||
"""*EAX* mode.
|
||||
|
||||
This is an Authenticated Encryption with Associated Data
|
||||
(`AEAD`_) mode. It provides both confidentiality and authenticity.
|
||||
|
||||
The header of the message may be left in the clear, if needed,
|
||||
and it will still be subject to authentication.
|
||||
|
||||
The decryption step tells the receiver if the message comes
|
||||
from a source that really knowns the secret key.
|
||||
Additionally, decryption detects if any part of the message -
|
||||
including the header - has been modified or corrupted.
|
||||
|
||||
This mode requires a *nonce*.
|
||||
|
||||
This mode is only available for ciphers that operate on 64 or
|
||||
128 bits blocks.
|
||||
|
||||
There are no official standards defining EAX.
|
||||
The implementation is based on `a proposal`__ that
|
||||
was presented to NIST.
|
||||
|
||||
.. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
|
||||
.. __: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/eax/eax-spec.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, key, nonce, mac_len, cipher_params):
|
||||
"""EAX cipher mode"""
|
||||
|
||||
self.block_size = factory.block_size
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
"""The nonce originally used to create the object."""
|
||||
|
||||
self._mac_len = mac_len
|
||||
self._mac_tag = None # Cache for MAC tag
|
||||
|
||||
# Allowed transitions after initialization
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
# MAC tag length
|
||||
if not (2 <= self._mac_len <= self.block_size):
|
||||
raise ValueError("'mac_len' must be at least 2 and not larger than %d"
|
||||
% self.block_size)
|
||||
|
||||
# Nonce cannot be empty and must be a byte string
|
||||
if len(self.nonce) == 0:
|
||||
raise ValueError("Nonce cannot be empty in EAX mode")
|
||||
if not is_buffer(nonce):
|
||||
raise TypeError("nonce must be bytes, bytearray or memoryview")
|
||||
|
||||
self._omac = [
|
||||
CMAC.new(key,
|
||||
b'\x00' * (self.block_size - 1) + struct.pack('B', i),
|
||||
ciphermod=factory,
|
||||
cipher_params=cipher_params)
|
||||
for i in range(0, 3)
|
||||
]
|
||||
|
||||
# Compute MAC of nonce
|
||||
self._omac[0].update(self.nonce)
|
||||
self._signer = self._omac[1]
|
||||
|
||||
# MAC of the nonce is also the initial counter for CTR encryption
|
||||
counter_int = bytes_to_long(self._omac[0].digest())
|
||||
self._cipher = factory.new(key,
|
||||
factory.MODE_CTR,
|
||||
initial_value=counter_int,
|
||||
nonce=b"",
|
||||
**cipher_params)
|
||||
|
||||
def update(self, assoc_data):
|
||||
"""Protect associated data
|
||||
|
||||
If there is any associated data, the caller has to invoke
|
||||
this function one or more times, before using
|
||||
``decrypt`` or ``encrypt``.
|
||||
|
||||
By *associated data* it is meant any data (e.g. packet headers) that
|
||||
will not be encrypted and will be transmitted in the clear.
|
||||
However, the receiver is still able to detect any modification to it.
|
||||
|
||||
If there is no associated data, this method must not be called.
|
||||
|
||||
The caller may split associated data in segments of any size, and
|
||||
invoke this method multiple times, each time with the next segment.
|
||||
|
||||
:Parameters:
|
||||
assoc_data : bytes/bytearray/memoryview
|
||||
A piece of associated data. There are no restrictions on its size.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() can only be called"
|
||||
" immediately after initialization")
|
||||
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
self._signer.update(assoc_data)
|
||||
return self
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
self._next = ["encrypt", "digest"]
|
||||
ct = self._cipher.encrypt(plaintext, output=output)
|
||||
if output is None:
|
||||
self._omac[2].update(ct)
|
||||
else:
|
||||
self._omac[2].update(output)
|
||||
return ct
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() can only be called"
|
||||
" after initialization or an update()")
|
||||
self._next = ["decrypt", "verify"]
|
||||
self._omac[2].update(ciphertext)
|
||||
return self._cipher.decrypt(ciphertext, output=output)
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method returns the MAC that shall be sent to the receiver,
|
||||
together with the ciphertext.
|
||||
|
||||
:Return: the MAC, as a byte string.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() cannot be called when decrypting"
|
||||
" or validating a message")
|
||||
self._next = ["digest"]
|
||||
|
||||
if not self._mac_tag:
|
||||
tag = b'\x00' * self.block_size
|
||||
for i in range(3):
|
||||
tag = strxor(tag, self._omac[i].digest())
|
||||
self._mac_tag = tag[:self._mac_len]
|
||||
|
||||
return self._mac_tag
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* MAC tag.
|
||||
|
||||
This method is like `digest`.
|
||||
|
||||
:Return: the MAC, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method checks if the decrypted message is indeed valid
|
||||
(that is, if the key is correct) and it has not been
|
||||
tampered with while in transit.
|
||||
|
||||
:Parameters:
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Raises MacMismatchError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called"
|
||||
" when encrypting a message")
|
||||
self._next = ["verify"]
|
||||
|
||||
if not self._mac_tag:
|
||||
tag = b'\x00' * self.block_size
|
||||
for i in range(3):
|
||||
tag = strxor(tag, self._omac[i].digest())
|
||||
self._mac_tag = tag[:self._mac_len]
|
||||
|
||||
secret = get_random_bytes(16)
|
||||
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* MAC tag.
|
||||
|
||||
This method is like `verify`.
|
||||
|
||||
:Parameters:
|
||||
hex_mac_tag : string
|
||||
This is the *printable* MAC, as received from the sender.
|
||||
:Raises MacMismatchError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext, output=None):
|
||||
"""Perform encrypt() and digest() in one step.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
a tuple with two items:
|
||||
|
||||
- the ciphertext, as ``bytes``
|
||||
- the MAC tag, as ``bytes``
|
||||
|
||||
The first item becomes ``None`` when the ``output`` parameter
|
||||
specified a location for the result.
|
||||
"""
|
||||
|
||||
return self.encrypt(plaintext, output=output), self.digest()
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
|
||||
"""Perform decrypt() and verify() in one step.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return: the plaintext as ``bytes`` or ``None`` when the ``output``
|
||||
parameter specified a location for the result.
|
||||
:Raises MacMismatchError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
pt = self.decrypt(ciphertext, output=output)
|
||||
self.verify(received_mac_tag)
|
||||
return pt
|
||||
|
||||
|
||||
def _create_eax_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in EAX mode.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
A symmetric cipher module from `Crypto.Cipher` (like
|
||||
`Crypto.Cipher.AES`).
|
||||
|
||||
:Keywords:
|
||||
key : bytes/bytearray/memoryview
|
||||
The secret key to use in the symmetric cipher.
|
||||
|
||||
nonce : bytes/bytearray/memoryview
|
||||
A value that must never be reused for any other encryption.
|
||||
There are no restrictions on its length, but it is recommended to use
|
||||
at least 16 bytes.
|
||||
|
||||
The nonce shall never repeat for two different messages encrypted with
|
||||
the same key, but it does not need to be random.
|
||||
|
||||
If not specified, a 16 byte long random string is used.
|
||||
|
||||
mac_len : integer
|
||||
Length of the MAC, in bytes. It must be no larger than the cipher
|
||||
block bytes (which is the default).
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(16)
|
||||
mac_len = kwargs.pop("mac_len", factory.block_size)
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter: " + str(e))
|
||||
|
||||
return EaxMode(factory, key, nonce, mac_len, kwargs)
|
||||
@@ -1,45 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Any, Union, Tuple, Dict, overload, Optional
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['EaxMode']
|
||||
|
||||
class EaxMode(object):
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
mac_len: int,
|
||||
cipher_params: Dict) -> None: ...
|
||||
|
||||
def update(self, assoc_data: Buffer) -> EaxMode: ...
|
||||
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, hex_mac_tag: str) -> None: ...
|
||||
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer,
|
||||
output: Buffer) -> Tuple[None, bytes]: ...
|
||||
def decrypt_and_verify(self,
|
||||
ciphertext: Buffer,
|
||||
received_mac_tag: Buffer,
|
||||
output: Optional[Union[bytearray, memoryview]] = ...) -> bytes: ...
|
||||
@@ -1,220 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/mode_ecb.py : ECB mode
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Electronic Code Book (ECB) mode.
|
||||
"""
|
||||
|
||||
__all__ = [ 'EcbMode' ]
|
||||
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib,
|
||||
VoidPointer, create_string_buffer,
|
||||
get_raw_buffer, SmartPointer,
|
||||
c_size_t, c_uint8_ptr,
|
||||
is_writeable_buffer)
|
||||
|
||||
raw_ecb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ecb", """
|
||||
int ECB_start_operation(void *cipher,
|
||||
void **pResult);
|
||||
int ECB_encrypt(void *ecbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int ECB_decrypt(void *ecbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int ECB_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class EcbMode(object):
|
||||
"""*Electronic Code Book (ECB)*.
|
||||
|
||||
This is the simplest encryption mode. Each of the plaintext blocks
|
||||
is directly encrypted into a ciphertext block, independently of
|
||||
any other block.
|
||||
|
||||
This mode is dangerous because it exposes frequency of symbols
|
||||
in your plaintext. Other modes (e.g. *CBC*) should be used instead.
|
||||
|
||||
See `NIST SP800-38A`_ , Section 6.1.
|
||||
|
||||
.. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, block_cipher):
|
||||
"""Create a new block cipher, configured in ECB mode.
|
||||
|
||||
:Parameters:
|
||||
block_cipher : C pointer
|
||||
A smart pointer to the low-level block cipher instance.
|
||||
"""
|
||||
self.block_size = block_cipher.block_size
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = raw_ecb_lib.ECB_start_operation(block_cipher.get(),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while instantiating the ECB mode"
|
||||
% result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher
|
||||
# mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
raw_ecb_lib.ECB_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owned
|
||||
# by the cipher mode
|
||||
block_cipher.release()
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key set at initialization.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
The length must be multiple of the cipher block length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ecb_lib.ECB_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
if result == 3:
|
||||
raise ValueError("Data must be aligned to block boundary in ECB mode")
|
||||
raise ValueError("Error %d while encrypting in ECB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key set at initialization.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
The length must be multiple of the cipher block length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if output is None:
|
||||
plaintext = create_string_buffer(len(ciphertext))
|
||||
else:
|
||||
plaintext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(ciphertext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ecb_lib.ECB_decrypt(self._state.get(),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_size_t(len(ciphertext)))
|
||||
if result:
|
||||
if result == 3:
|
||||
raise ValueError("Data must be aligned to block boundary in ECB mode")
|
||||
raise ValueError("Error %d while decrypting in ECB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(plaintext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _create_ecb_cipher(factory, **kwargs):
|
||||
"""Instantiate a cipher object that performs ECB encryption/decryption.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The underlying block cipher, a module from ``Crypto.Cipher``.
|
||||
|
||||
All keywords are passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present"""
|
||||
|
||||
cipher_state = factory._create_base_cipher(kwargs)
|
||||
cipher_state.block_size = factory.block_size
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters for ECB: %s" % str(kwargs))
|
||||
return EcbMode(cipher_state)
|
||||
@@ -1,19 +0,0 @@
|
||||
from typing import Union, overload
|
||||
|
||||
from Crypto.Util._raw_api import SmartPointer
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = [ 'EcbMode' ]
|
||||
|
||||
class EcbMode(object):
|
||||
def __init__(self, block_cipher: SmartPointer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
@@ -1,620 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Galois/Counter Mode (GCM).
|
||||
"""
|
||||
|
||||
__all__ = ['GcmMode']
|
||||
|
||||
from binascii import unhexlify
|
||||
|
||||
from Crypto.Util.py3compat import bord, _copy_bytes
|
||||
|
||||
from Crypto.Util._raw_api import is_buffer
|
||||
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
from Crypto.Hash import BLAKE2s
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr)
|
||||
|
||||
from Crypto.Util import _cpu_features
|
||||
|
||||
|
||||
# C API by module implementing GHASH
|
||||
_ghash_api_template = """
|
||||
int ghash_%imp%(uint8_t y_out[16],
|
||||
const uint8_t block_data[],
|
||||
size_t len,
|
||||
const uint8_t y_in[16],
|
||||
const void *exp_key);
|
||||
int ghash_expand_%imp%(const uint8_t h[16],
|
||||
void **ghash_tables);
|
||||
int ghash_destroy_%imp%(void *ghash_tables);
|
||||
"""
|
||||
|
||||
def _build_impl(lib, postfix):
|
||||
from collections import namedtuple
|
||||
|
||||
funcs = ( "ghash", "ghash_expand", "ghash_destroy" )
|
||||
GHASH_Imp = namedtuple('_GHash_Imp', funcs)
|
||||
try:
|
||||
imp_funcs = [ getattr(lib, x + "_" + postfix) for x in funcs ]
|
||||
except AttributeError: # Make sphinx stop complaining with its mocklib
|
||||
imp_funcs = [ None ] * 3
|
||||
params = dict(zip(funcs, imp_funcs))
|
||||
return GHASH_Imp(**params)
|
||||
|
||||
|
||||
def _get_ghash_portable():
|
||||
api = _ghash_api_template.replace("%imp%", "portable")
|
||||
lib = load_pycryptodome_raw_lib("Crypto.Hash._ghash_portable", api)
|
||||
result = _build_impl(lib, "portable")
|
||||
return result
|
||||
_ghash_portable = _get_ghash_portable()
|
||||
|
||||
|
||||
def _get_ghash_clmul():
|
||||
"""Return None if CLMUL implementation is not available"""
|
||||
|
||||
if not _cpu_features.have_clmul():
|
||||
return None
|
||||
try:
|
||||
api = _ghash_api_template.replace("%imp%", "clmul")
|
||||
lib = load_pycryptodome_raw_lib("Crypto.Hash._ghash_clmul", api)
|
||||
result = _build_impl(lib, "clmul")
|
||||
except OSError:
|
||||
result = None
|
||||
return result
|
||||
_ghash_clmul = _get_ghash_clmul()
|
||||
|
||||
|
||||
class _GHASH(object):
|
||||
"""GHASH function defined in NIST SP 800-38D, Algorithm 2.
|
||||
|
||||
If X_1, X_2, .. X_m are the blocks of input data, the function
|
||||
computes:
|
||||
|
||||
X_1*H^{m} + X_2*H^{m-1} + ... + X_m*H
|
||||
|
||||
in the Galois field GF(2^256) using the reducing polynomial
|
||||
(x^128 + x^7 + x^2 + x + 1).
|
||||
"""
|
||||
|
||||
def __init__(self, subkey, ghash_c):
|
||||
assert len(subkey) == 16
|
||||
|
||||
self.ghash_c = ghash_c
|
||||
|
||||
self._exp_key = VoidPointer()
|
||||
result = ghash_c.ghash_expand(c_uint8_ptr(subkey),
|
||||
self._exp_key.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while expanding the GHASH key" % result)
|
||||
|
||||
self._exp_key = SmartPointer(self._exp_key.get(),
|
||||
ghash_c.ghash_destroy)
|
||||
|
||||
# create_string_buffer always returns a string of zeroes
|
||||
self._last_y = create_string_buffer(16)
|
||||
|
||||
def update(self, block_data):
|
||||
assert len(block_data) % 16 == 0
|
||||
|
||||
result = self.ghash_c.ghash(self._last_y,
|
||||
c_uint8_ptr(block_data),
|
||||
c_size_t(len(block_data)),
|
||||
self._last_y,
|
||||
self._exp_key.get())
|
||||
if result:
|
||||
raise ValueError("Error %d while updating GHASH" % result)
|
||||
|
||||
return self
|
||||
|
||||
def digest(self):
|
||||
return get_raw_buffer(self._last_y)
|
||||
|
||||
|
||||
def enum(**enums):
|
||||
return type('Enum', (), enums)
|
||||
|
||||
|
||||
MacStatus = enum(PROCESSING_AUTH_DATA=1, PROCESSING_CIPHERTEXT=2)
|
||||
|
||||
|
||||
class GcmMode(object):
|
||||
"""Galois Counter Mode (GCM).
|
||||
|
||||
This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
|
||||
It provides both confidentiality and authenticity.
|
||||
|
||||
The header of the message may be left in the clear, if needed, and it will
|
||||
still be subject to authentication. The decryption step tells the receiver
|
||||
if the message comes from a source that really knowns the secret key.
|
||||
Additionally, decryption detects if any part of the message - including the
|
||||
header - has been modified or corrupted.
|
||||
|
||||
This mode requires a *nonce*.
|
||||
|
||||
This mode is only available for ciphers that operate on 128 bits blocks
|
||||
(e.g. AES but not TDES).
|
||||
|
||||
See `NIST SP800-38D`_.
|
||||
|
||||
.. _`NIST SP800-38D`: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf
|
||||
.. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, key, nonce, mac_len, cipher_params, ghash_c):
|
||||
|
||||
self.block_size = factory.block_size
|
||||
if self.block_size != 16:
|
||||
raise ValueError("GCM mode is only available for ciphers"
|
||||
" that operate on 128 bits blocks")
|
||||
|
||||
if len(nonce) == 0:
|
||||
raise ValueError("Nonce cannot be empty")
|
||||
|
||||
if not is_buffer(nonce):
|
||||
raise TypeError("Nonce must be bytes, bytearray or memoryview")
|
||||
|
||||
# See NIST SP 800 38D, 5.2.1.1
|
||||
if len(nonce) > 2**64 - 1:
|
||||
raise ValueError("Nonce exceeds maximum length")
|
||||
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
"""Nonce"""
|
||||
|
||||
self._factory = factory
|
||||
self._key = _copy_bytes(None, None, key)
|
||||
self._tag = None # Cache for MAC tag
|
||||
|
||||
self._mac_len = mac_len
|
||||
if not (4 <= mac_len <= 16):
|
||||
raise ValueError("Parameter 'mac_len' must be in the range 4..16")
|
||||
|
||||
# Allowed transitions after initialization
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
self._no_more_assoc_data = False
|
||||
|
||||
# Length of associated data
|
||||
self._auth_len = 0
|
||||
|
||||
# Length of the ciphertext or plaintext
|
||||
self._msg_len = 0
|
||||
|
||||
# Step 1 in SP800-38D, Algorithm 4 (encryption) - Compute H
|
||||
# See also Algorithm 5 (decryption)
|
||||
hash_subkey = factory.new(key,
|
||||
self._factory.MODE_ECB,
|
||||
**cipher_params
|
||||
).encrypt(b'\x00' * 16)
|
||||
|
||||
# Step 2 - Compute J0
|
||||
if len(self.nonce) == 12:
|
||||
j0 = self.nonce + b"\x00\x00\x00\x01"
|
||||
else:
|
||||
fill = (16 - (len(self.nonce) % 16)) % 16 + 8
|
||||
ghash_in = (self.nonce +
|
||||
b'\x00' * fill +
|
||||
long_to_bytes(8 * len(self.nonce), 8))
|
||||
j0 = _GHASH(hash_subkey, ghash_c).update(ghash_in).digest()
|
||||
|
||||
# Step 3 - Prepare GCTR cipher for encryption/decryption
|
||||
nonce_ctr = j0[:12]
|
||||
iv_ctr = (bytes_to_long(j0) + 1) & 0xFFFFFFFF
|
||||
self._cipher = factory.new(key,
|
||||
self._factory.MODE_CTR,
|
||||
initial_value=iv_ctr,
|
||||
nonce=nonce_ctr,
|
||||
**cipher_params)
|
||||
|
||||
# Step 5 - Bootstrat GHASH
|
||||
self._signer = _GHASH(hash_subkey, ghash_c)
|
||||
|
||||
# Step 6 - Prepare GCTR cipher for GMAC
|
||||
self._tag_cipher = factory.new(key,
|
||||
self._factory.MODE_CTR,
|
||||
initial_value=j0,
|
||||
nonce=b"",
|
||||
**cipher_params)
|
||||
|
||||
# Cache for data to authenticate
|
||||
self._cache = b""
|
||||
|
||||
self._status = MacStatus.PROCESSING_AUTH_DATA
|
||||
|
||||
def update(self, assoc_data):
|
||||
"""Protect associated data
|
||||
|
||||
If there is any associated data, the caller has to invoke
|
||||
this function one or more times, before using
|
||||
``decrypt`` or ``encrypt``.
|
||||
|
||||
By *associated data* it is meant any data (e.g. packet headers) that
|
||||
will not be encrypted and will be transmitted in the clear.
|
||||
However, the receiver is still able to detect any modification to it.
|
||||
In GCM, the *associated data* is also called
|
||||
*additional authenticated data* (AAD).
|
||||
|
||||
If there is no associated data, this method must not be called.
|
||||
|
||||
The caller may split associated data in segments of any size, and
|
||||
invoke this method multiple times, each time with the next segment.
|
||||
|
||||
:Parameters:
|
||||
assoc_data : bytes/bytearray/memoryview
|
||||
A piece of associated data. There are no restrictions on its size.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() can only be called"
|
||||
" immediately after initialization")
|
||||
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
self._update(assoc_data)
|
||||
self._auth_len += len(assoc_data)
|
||||
|
||||
# See NIST SP 800 38D, 5.2.1.1
|
||||
if self._auth_len > 2**64 - 1:
|
||||
raise ValueError("Additional Authenticated Data exceeds maximum length")
|
||||
|
||||
return self
|
||||
|
||||
def _update(self, data):
|
||||
assert(len(self._cache) < 16)
|
||||
|
||||
if len(self._cache) > 0:
|
||||
filler = min(16 - len(self._cache), len(data))
|
||||
self._cache += _copy_bytes(None, filler, data)
|
||||
data = data[filler:]
|
||||
|
||||
if len(self._cache) < 16:
|
||||
return
|
||||
|
||||
# The cache is exactly one block
|
||||
self._signer.update(self._cache)
|
||||
self._cache = b""
|
||||
|
||||
update_len = len(data) // 16 * 16
|
||||
self._cache = _copy_bytes(update_len, None, data)
|
||||
if update_len > 0:
|
||||
self._signer.update(data[:update_len])
|
||||
|
||||
def _pad_cache_and_update(self):
|
||||
assert(len(self._cache) < 16)
|
||||
|
||||
# The authenticated data A is concatenated to the minimum
|
||||
# number of zero bytes (possibly none) such that the
|
||||
# - ciphertext C is aligned to the 16 byte boundary.
|
||||
# See step 5 in section 7.1
|
||||
# - ciphertext C is aligned to the 16 byte boundary.
|
||||
# See step 6 in section 7.2
|
||||
len_cache = len(self._cache)
|
||||
if len_cache > 0:
|
||||
self._update(b'\x00' * (16 - len_cache))
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
self._next = ["encrypt", "digest"]
|
||||
|
||||
ciphertext = self._cipher.encrypt(plaintext, output=output)
|
||||
|
||||
if self._status == MacStatus.PROCESSING_AUTH_DATA:
|
||||
self._pad_cache_and_update()
|
||||
self._status = MacStatus.PROCESSING_CIPHERTEXT
|
||||
|
||||
self._update(ciphertext if output is None else output)
|
||||
self._msg_len += len(plaintext)
|
||||
|
||||
# See NIST SP 800 38D, 5.2.1.1
|
||||
if self._msg_len > 2**39 - 256:
|
||||
raise ValueError("Plaintext exceeds maximum length")
|
||||
|
||||
return ciphertext
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() can only be called"
|
||||
" after initialization or an update()")
|
||||
self._next = ["decrypt", "verify"]
|
||||
|
||||
if self._status == MacStatus.PROCESSING_AUTH_DATA:
|
||||
self._pad_cache_and_update()
|
||||
self._status = MacStatus.PROCESSING_CIPHERTEXT
|
||||
|
||||
self._update(ciphertext)
|
||||
self._msg_len += len(ciphertext)
|
||||
|
||||
return self._cipher.decrypt(ciphertext, output=output)
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* MAC tag in an AEAD mode.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method returns the MAC that shall be sent to the receiver,
|
||||
together with the ciphertext.
|
||||
|
||||
:Return: the MAC, as a byte string.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() cannot be called when decrypting"
|
||||
" or validating a message")
|
||||
self._next = ["digest"]
|
||||
|
||||
return self._compute_mac()
|
||||
|
||||
def _compute_mac(self):
|
||||
"""Compute MAC without any FSM checks."""
|
||||
|
||||
if self._tag:
|
||||
return self._tag
|
||||
|
||||
# Step 5 in NIST SP 800-38D, Algorithm 4 - Compute S
|
||||
self._pad_cache_and_update()
|
||||
self._update(long_to_bytes(8 * self._auth_len, 8))
|
||||
self._update(long_to_bytes(8 * self._msg_len, 8))
|
||||
s_tag = self._signer.digest()
|
||||
|
||||
# Step 6 - Compute T
|
||||
self._tag = self._tag_cipher.encrypt(s_tag)[:self._mac_len]
|
||||
|
||||
return self._tag
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* MAC tag.
|
||||
|
||||
This method is like `digest`.
|
||||
|
||||
:Return: the MAC, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method checks if the decrypted message is indeed valid
|
||||
(that is, if the key is correct) and it has not been
|
||||
tampered with while in transit.
|
||||
|
||||
:Parameters:
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called"
|
||||
" when encrypting a message")
|
||||
self._next = ["verify"]
|
||||
|
||||
secret = get_random_bytes(16)
|
||||
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret,
|
||||
data=self._compute_mac())
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret,
|
||||
data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* MAC tag.
|
||||
|
||||
This method is like `verify`.
|
||||
|
||||
:Parameters:
|
||||
hex_mac_tag : string
|
||||
This is the *printable* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext, output=None):
|
||||
"""Perform encrypt() and digest() in one step.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
a tuple with two items:
|
||||
|
||||
- the ciphertext, as ``bytes``
|
||||
- the MAC tag, as ``bytes``
|
||||
|
||||
The first item becomes ``None`` when the ``output`` parameter
|
||||
specified a location for the result.
|
||||
"""
|
||||
|
||||
return self.encrypt(plaintext, output=output), self.digest()
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, received_mac_tag, output=None):
|
||||
"""Perform decrypt() and verify() in one step.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
received_mac_tag : byte string
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return: the plaintext as ``bytes`` or ``None`` when the ``output``
|
||||
parameter specified a location for the result.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
plaintext = self.decrypt(ciphertext, output=output)
|
||||
self.verify(received_mac_tag)
|
||||
return plaintext
|
||||
|
||||
|
||||
def _create_gcm_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in Galois Counter Mode (GCM).
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
A block cipher module, taken from `Crypto.Cipher`.
|
||||
The cipher must have block length of 16 bytes.
|
||||
GCM has been only defined for `Crypto.Cipher.AES`.
|
||||
|
||||
:Keywords:
|
||||
key : bytes/bytearray/memoryview
|
||||
The secret key to use in the symmetric cipher.
|
||||
It must be 16 (e.g. *AES-128*), 24 (e.g. *AES-192*)
|
||||
or 32 (e.g. *AES-256*) bytes long.
|
||||
|
||||
nonce : bytes/bytearray/memoryview
|
||||
A value that must never be reused for any other encryption.
|
||||
|
||||
There are no restrictions on its length,
|
||||
but it is recommended to use at least 16 bytes.
|
||||
|
||||
The nonce shall never repeat for two
|
||||
different messages encrypted with the same key,
|
||||
but it does not need to be random.
|
||||
|
||||
If not provided, a 16 byte nonce will be randomly created.
|
||||
|
||||
mac_len : integer
|
||||
Length of the MAC, in bytes.
|
||||
It must be no larger than 16 bytes (which is the default).
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter:" + str(e))
|
||||
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(16)
|
||||
mac_len = kwargs.pop("mac_len", 16)
|
||||
|
||||
# Not documented - only used for testing
|
||||
use_clmul = kwargs.pop("use_clmul", True)
|
||||
if use_clmul and _ghash_clmul:
|
||||
ghash_c = _ghash_clmul
|
||||
else:
|
||||
ghash_c = _ghash_portable
|
||||
|
||||
return GcmMode(factory, key, nonce, mac_len, kwargs, ghash_c)
|
||||
@@ -1,45 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, Tuple, Dict, overload, Optional
|
||||
|
||||
__all__ = ['GcmMode']
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
class GcmMode(object):
|
||||
block_size: int
|
||||
nonce: Buffer
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
mac_len: int,
|
||||
cipher_params: Dict) -> None: ...
|
||||
|
||||
def update(self, assoc_data: Buffer) -> GcmMode: ...
|
||||
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, hex_mac_tag: str) -> None: ...
|
||||
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer,
|
||||
output: Buffer) -> Tuple[None, bytes]: ...
|
||||
def decrypt_and_verify(self,
|
||||
ciphertext: Buffer,
|
||||
received_mac_tag: Buffer,
|
||||
output: Optional[Union[bytearray, memoryview]] = ...) -> bytes: ...
|
||||
@@ -1,158 +0,0 @@
|
||||
import struct
|
||||
from collections import deque
|
||||
|
||||
from types import ModuleType
|
||||
from typing import Union
|
||||
|
||||
from Crypto.Util.strxor import strxor
|
||||
|
||||
|
||||
def W(cipher: ModuleType,
|
||||
plaintext: Union[bytes, bytearray]) -> bytes:
|
||||
|
||||
S = [plaintext[i:i+8] for i in range(0, len(plaintext), 8)]
|
||||
n = len(S)
|
||||
s = 6 * (n - 1)
|
||||
A = S[0]
|
||||
R = deque(S[1:])
|
||||
|
||||
for t in range(1, s + 1):
|
||||
t_64 = struct.pack('>Q', t)
|
||||
ct = cipher.encrypt(A + R.popleft())
|
||||
A = strxor(ct[:8], t_64)
|
||||
R.append(ct[8:])
|
||||
|
||||
return A + b''.join(R)
|
||||
|
||||
|
||||
def W_inverse(cipher: ModuleType,
|
||||
ciphertext: Union[bytes, bytearray]) -> bytes:
|
||||
|
||||
C = [ciphertext[i:i+8] for i in range(0, len(ciphertext), 8)]
|
||||
n = len(C)
|
||||
s = 6 * (n - 1)
|
||||
A = C[0]
|
||||
R = deque(C[1:])
|
||||
|
||||
for t in range(s, 0, -1):
|
||||
t_64 = struct.pack('>Q', t)
|
||||
pt = cipher.decrypt(strxor(A, t_64) + R.pop())
|
||||
A = pt[:8]
|
||||
R.appendleft(pt[8:])
|
||||
|
||||
return A + b''.join(R)
|
||||
|
||||
|
||||
class KWMode(object):
|
||||
"""Key Wrap (KW) mode.
|
||||
|
||||
This is a deterministic Authenticated Encryption (AE) mode
|
||||
for protecting cryptographic keys. See `NIST SP800-38F`_.
|
||||
|
||||
It provides both confidentiality and authenticity, and it designed
|
||||
so that any bit of the ciphertext depends on all bits of the plaintext.
|
||||
|
||||
This mode is only available for ciphers that operate on 128 bits blocks
|
||||
(e.g., AES).
|
||||
|
||||
.. _`NIST SP800-38F`: http://csrc.nist.gov/publications/nistpubs/800-38F/SP-800-38F.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Union[bytes, bytearray]):
|
||||
|
||||
self.block_size = factory.block_size
|
||||
if self.block_size != 16:
|
||||
raise ValueError("Key Wrap mode is only available for ciphers"
|
||||
" that operate on 128 bits blocks")
|
||||
|
||||
self._factory = factory
|
||||
self._cipher = factory.new(key, factory.MODE_ECB)
|
||||
self._done = False
|
||||
|
||||
def seal(self, plaintext: Union[bytes, bytearray]) -> bytes:
|
||||
"""Encrypt and authenticate (wrap) a cryptographic key.
|
||||
|
||||
Args:
|
||||
plaintext:
|
||||
The cryptographic key to wrap.
|
||||
It must be at least 16 bytes long, and its length
|
||||
must be a multiple of 8.
|
||||
|
||||
Returns:
|
||||
The wrapped key.
|
||||
"""
|
||||
|
||||
if self._done:
|
||||
raise ValueError("The cipher cannot be used more than once")
|
||||
|
||||
if len(plaintext) % 8:
|
||||
raise ValueError("The plaintext must have length multiple of 8 bytes")
|
||||
|
||||
if len(plaintext) < 16:
|
||||
raise ValueError("The plaintext must be at least 16 bytes long")
|
||||
|
||||
if len(plaintext) >= 2**32:
|
||||
raise ValueError("The plaintext is too long")
|
||||
|
||||
res = W(self._cipher, b'\xA6\xA6\xA6\xA6\xA6\xA6\xA6\xA6' + plaintext)
|
||||
self._done = True
|
||||
return res
|
||||
|
||||
def unseal(self, ciphertext: Union[bytes, bytearray]) -> bytes:
|
||||
"""Decrypt and authenticate (unwrap) a cryptographic key.
|
||||
|
||||
Args:
|
||||
ciphertext:
|
||||
The cryptographic key to unwrap.
|
||||
It must be at least 24 bytes long, and its length
|
||||
must be a multiple of 8.
|
||||
|
||||
Returns:
|
||||
The original key.
|
||||
|
||||
Raises: ValueError
|
||||
If the ciphertext or the key are not valid.
|
||||
"""
|
||||
|
||||
if self._done:
|
||||
raise ValueError("The cipher cannot be used more than once")
|
||||
|
||||
if len(ciphertext) % 8:
|
||||
raise ValueError("The ciphertext must have length multiple of 8 bytes")
|
||||
|
||||
if len(ciphertext) < 24:
|
||||
raise ValueError("The ciphertext must be at least 24 bytes long")
|
||||
|
||||
pt = W_inverse(self._cipher, ciphertext)
|
||||
|
||||
if pt[:8] != b'\xA6\xA6\xA6\xA6\xA6\xA6\xA6\xA6':
|
||||
raise ValueError("Incorrect integrity check value")
|
||||
self._done = True
|
||||
|
||||
return pt[8:]
|
||||
|
||||
|
||||
def _create_kw_cipher(factory: ModuleType,
|
||||
**kwargs: Union[bytes, bytearray]) -> KWMode:
|
||||
"""Create a new block cipher in Key Wrap mode.
|
||||
|
||||
Args:
|
||||
factory:
|
||||
A block cipher module, taken from `Crypto.Cipher`.
|
||||
The cipher must have block length of 16 bytes, such as AES.
|
||||
|
||||
Keywords:
|
||||
key:
|
||||
The secret key to use to seal or unseal.
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs["key"]
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter:" + str(e))
|
||||
|
||||
return KWMode(factory, key)
|
||||
@@ -1,135 +0,0 @@
|
||||
import struct
|
||||
|
||||
from types import ModuleType
|
||||
from typing import Union
|
||||
|
||||
from ._mode_kw import W, W_inverse
|
||||
|
||||
|
||||
class KWPMode(object):
|
||||
"""Key Wrap with Padding (KWP) mode.
|
||||
|
||||
This is a deterministic Authenticated Encryption (AE) mode
|
||||
for protecting cryptographic keys. See `NIST SP800-38F`_.
|
||||
|
||||
It provides both confidentiality and authenticity, and it designed
|
||||
so that any bit of the ciphertext depends on all bits of the plaintext.
|
||||
|
||||
This mode is only available for ciphers that operate on 128 bits blocks
|
||||
(e.g., AES).
|
||||
|
||||
.. _`NIST SP800-38F`: http://csrc.nist.gov/publications/nistpubs/800-38F/SP-800-38F.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Union[bytes, bytearray]):
|
||||
|
||||
self.block_size = factory.block_size
|
||||
if self.block_size != 16:
|
||||
raise ValueError("Key Wrap with Padding mode is only available for ciphers"
|
||||
" that operate on 128 bits blocks")
|
||||
|
||||
self._factory = factory
|
||||
self._cipher = factory.new(key, factory.MODE_ECB)
|
||||
self._done = False
|
||||
|
||||
def seal(self, plaintext: Union[bytes, bytearray]) -> bytes:
|
||||
"""Encrypt and authenticate (wrap) a cryptographic key.
|
||||
|
||||
Args:
|
||||
plaintext:
|
||||
The cryptographic key to wrap.
|
||||
|
||||
Returns:
|
||||
The wrapped key.
|
||||
"""
|
||||
|
||||
if self._done:
|
||||
raise ValueError("The cipher cannot be used more than once")
|
||||
|
||||
if len(plaintext) == 0:
|
||||
raise ValueError("The plaintext must be at least 1 byte")
|
||||
|
||||
if len(plaintext) >= 2 ** 32:
|
||||
raise ValueError("The plaintext is too long")
|
||||
|
||||
padlen = (8 - len(plaintext)) % 8
|
||||
padded = plaintext + b'\x00' * padlen
|
||||
|
||||
AIV = b'\xA6\x59\x59\xA6' + struct.pack('>I', len(plaintext))
|
||||
|
||||
if len(padded) == 8:
|
||||
res = self._cipher.encrypt(AIV + padded)
|
||||
else:
|
||||
res = W(self._cipher, AIV + padded)
|
||||
|
||||
return res
|
||||
|
||||
def unseal(self, ciphertext: Union[bytes, bytearray]) -> bytes:
|
||||
"""Decrypt and authenticate (unwrap) a cryptographic key.
|
||||
|
||||
Args:
|
||||
ciphertext:
|
||||
The cryptographic key to unwrap.
|
||||
It must be at least 16 bytes long, and its length
|
||||
must be a multiple of 8.
|
||||
|
||||
Returns:
|
||||
The original key.
|
||||
|
||||
Raises: ValueError
|
||||
If the ciphertext or the key are not valid.
|
||||
"""
|
||||
|
||||
if self._done:
|
||||
raise ValueError("The cipher cannot be used more than once")
|
||||
|
||||
if len(ciphertext) % 8:
|
||||
raise ValueError("The ciphertext must have length multiple of 8 bytes")
|
||||
|
||||
if len(ciphertext) < 16:
|
||||
raise ValueError("The ciphertext must be at least 24 bytes long")
|
||||
|
||||
if len(ciphertext) == 16:
|
||||
S = self._cipher.decrypt(ciphertext)
|
||||
else:
|
||||
S = W_inverse(self._cipher, ciphertext)
|
||||
|
||||
if S[:4] != b'\xA6\x59\x59\xA6':
|
||||
raise ValueError("Incorrect decryption")
|
||||
|
||||
Plen = struct.unpack('>I', S[4:8])[0]
|
||||
|
||||
padlen = len(S) - 8 - Plen
|
||||
if padlen < 0 or padlen > 7:
|
||||
raise ValueError("Incorrect decryption")
|
||||
|
||||
if S[len(S) - padlen:] != b'\x00' * padlen:
|
||||
raise ValueError("Incorrect decryption")
|
||||
|
||||
return S[8:len(S) - padlen]
|
||||
|
||||
|
||||
def _create_kwp_cipher(factory: ModuleType,
|
||||
**kwargs: Union[bytes, bytearray]) -> KWPMode:
|
||||
"""Create a new block cipher in Key Wrap with Padding mode.
|
||||
|
||||
Args:
|
||||
factory:
|
||||
A block cipher module, taken from `Crypto.Cipher`.
|
||||
The cipher must have block length of 16 bytes, such as AES.
|
||||
|
||||
Keywords:
|
||||
key:
|
||||
The secret key to use to seal or unseal.
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs["key"]
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter:" + str(e))
|
||||
|
||||
return KWPMode(factory, key)
|
||||
@@ -1,532 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Offset Codebook (OCB) mode.
|
||||
|
||||
OCB is Authenticated Encryption with Associated Data (AEAD) cipher mode
|
||||
designed by Prof. Phillip Rogaway and specified in `RFC7253`_.
|
||||
|
||||
The algorithm provides both authenticity and privacy, it is very efficient,
|
||||
it uses only one key and it can be used in online mode (so that encryption
|
||||
or decryption can start before the end of the message is available).
|
||||
|
||||
This module implements the third and last variant of OCB (OCB3) and it only
|
||||
works in combination with a 128-bit block symmetric cipher, like AES.
|
||||
|
||||
OCB is patented in US but `free licenses`_ exist for software implementations
|
||||
meant for non-military purposes.
|
||||
|
||||
Example:
|
||||
>>> from Crypto.Cipher import AES
|
||||
>>> from Crypto.Random import get_random_bytes
|
||||
>>>
|
||||
>>> key = get_random_bytes(32)
|
||||
>>> cipher = AES.new(key, AES.MODE_OCB)
|
||||
>>> plaintext = b"Attack at dawn"
|
||||
>>> ciphertext, mac = cipher.encrypt_and_digest(plaintext)
|
||||
>>> # Deliver cipher.nonce, ciphertext and mac
|
||||
...
|
||||
>>> cipher = AES.new(key, AES.MODE_OCB, nonce=nonce)
|
||||
>>> try:
|
||||
>>> plaintext = cipher.decrypt_and_verify(ciphertext, mac)
|
||||
>>> except ValueError:
|
||||
>>> print "Invalid message"
|
||||
>>> else:
|
||||
>>> print plaintext
|
||||
|
||||
:undocumented: __package__
|
||||
|
||||
.. _RFC7253: http://www.rfc-editor.org/info/rfc7253
|
||||
.. _free licenses: http://web.cs.ucdavis.edu/~rogaway/ocb/license.htm
|
||||
"""
|
||||
|
||||
import struct
|
||||
from binascii import unhexlify
|
||||
|
||||
from Crypto.Util.py3compat import bord, _copy_bytes, bchr
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
from Crypto.Util.strxor import strxor
|
||||
|
||||
from Crypto.Hash import BLAKE2s
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr,
|
||||
is_buffer)
|
||||
|
||||
_raw_ocb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ocb", """
|
||||
int OCB_start_operation(void *cipher,
|
||||
const uint8_t *offset_0,
|
||||
size_t offset_0_len,
|
||||
void **pState);
|
||||
int OCB_encrypt(void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int OCB_decrypt(void *state,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int OCB_update(void *state,
|
||||
const uint8_t *in,
|
||||
size_t data_len);
|
||||
int OCB_digest(void *state,
|
||||
uint8_t *tag,
|
||||
size_t tag_len);
|
||||
int OCB_stop_operation(void *state);
|
||||
""")
|
||||
|
||||
|
||||
class OcbMode(object):
|
||||
"""Offset Codebook (OCB) mode.
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, nonce, mac_len, cipher_params):
|
||||
|
||||
if factory.block_size != 16:
|
||||
raise ValueError("OCB mode is only available for ciphers"
|
||||
" that operate on 128 bits blocks")
|
||||
|
||||
self.block_size = 16
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
"""Nonce used for this session."""
|
||||
if len(nonce) not in range(1, 16):
|
||||
raise ValueError("Nonce must be at most 15 bytes long")
|
||||
if not is_buffer(nonce):
|
||||
raise TypeError("Nonce must be bytes, bytearray or memoryview")
|
||||
|
||||
self._mac_len = mac_len
|
||||
if not 8 <= mac_len <= 16:
|
||||
raise ValueError("MAC tag must be between 8 and 16 bytes long")
|
||||
|
||||
# Cache for MAC tag
|
||||
self._mac_tag = None
|
||||
|
||||
# Cache for unaligned associated data
|
||||
self._cache_A = b""
|
||||
|
||||
# Cache for unaligned ciphertext/plaintext
|
||||
self._cache_P = b""
|
||||
|
||||
# Allowed transitions after initialization
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
# Compute Offset_0
|
||||
params_without_key = dict(cipher_params)
|
||||
key = params_without_key.pop("key")
|
||||
|
||||
taglen_mod128 = (self._mac_len * 8) % 128
|
||||
if len(self.nonce) < 15:
|
||||
nonce = bchr(taglen_mod128 << 1) +\
|
||||
b'\x00' * (14 - len(nonce)) +\
|
||||
b'\x01' +\
|
||||
self.nonce
|
||||
else:
|
||||
nonce = bchr((taglen_mod128 << 1) | 0x01) +\
|
||||
self.nonce
|
||||
|
||||
bottom_bits = bord(nonce[15]) & 0x3F # 6 bits, 0..63
|
||||
top_bits = bord(nonce[15]) & 0xC0 # 2 bits
|
||||
|
||||
ktop_cipher = factory.new(key,
|
||||
factory.MODE_ECB,
|
||||
**params_without_key)
|
||||
ktop = ktop_cipher.encrypt(struct.pack('15sB',
|
||||
nonce[:15],
|
||||
top_bits))
|
||||
|
||||
stretch = ktop + strxor(ktop[:8], ktop[1:9]) # 192 bits
|
||||
offset_0 = long_to_bytes(bytes_to_long(stretch) >>
|
||||
(64 - bottom_bits), 24)[8:]
|
||||
|
||||
# Create low-level cipher instance
|
||||
raw_cipher = factory._create_base_cipher(cipher_params)
|
||||
if cipher_params:
|
||||
raise TypeError("Unknown keywords: " + str(cipher_params))
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = _raw_ocb_lib.OCB_start_operation(raw_cipher.get(),
|
||||
offset_0,
|
||||
c_size_t(len(offset_0)),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while instantiating the OCB mode"
|
||||
% result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
_raw_ocb_lib.OCB_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owed
|
||||
# by the cipher mode
|
||||
raw_cipher.release()
|
||||
|
||||
def _update(self, assoc_data, assoc_data_len):
|
||||
result = _raw_ocb_lib.OCB_update(self._state.get(),
|
||||
c_uint8_ptr(assoc_data),
|
||||
c_size_t(assoc_data_len))
|
||||
if result:
|
||||
raise ValueError("Error %d while computing MAC in OCB mode" % result)
|
||||
|
||||
def update(self, assoc_data):
|
||||
"""Process the associated data.
|
||||
|
||||
If there is any associated data, the caller has to invoke
|
||||
this method one or more times, before using
|
||||
``decrypt`` or ``encrypt``.
|
||||
|
||||
By *associated data* it is meant any data (e.g. packet headers) that
|
||||
will not be encrypted and will be transmitted in the clear.
|
||||
However, the receiver shall still able to detect modifications.
|
||||
|
||||
If there is no associated data, this method must not be called.
|
||||
|
||||
The caller may split associated data in segments of any size, and
|
||||
invoke this method multiple times, each time with the next segment.
|
||||
|
||||
:Parameters:
|
||||
assoc_data : bytes/bytearray/memoryview
|
||||
A piece of associated data.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() can only be called"
|
||||
" immediately after initialization")
|
||||
|
||||
self._next = ["encrypt", "decrypt", "digest",
|
||||
"verify", "update"]
|
||||
|
||||
if len(self._cache_A) > 0:
|
||||
filler = min(16 - len(self._cache_A), len(assoc_data))
|
||||
self._cache_A += _copy_bytes(None, filler, assoc_data)
|
||||
assoc_data = assoc_data[filler:]
|
||||
|
||||
if len(self._cache_A) < 16:
|
||||
return self
|
||||
|
||||
# Clear the cache, and proceeding with any other aligned data
|
||||
self._cache_A, seg = b"", self._cache_A
|
||||
self.update(seg)
|
||||
|
||||
update_len = len(assoc_data) // 16 * 16
|
||||
self._cache_A = _copy_bytes(update_len, None, assoc_data)
|
||||
self._update(assoc_data, update_len)
|
||||
return self
|
||||
|
||||
def _transcrypt_aligned(self, in_data, in_data_len,
|
||||
trans_func, trans_desc):
|
||||
|
||||
out_data = create_string_buffer(in_data_len)
|
||||
result = trans_func(self._state.get(),
|
||||
in_data,
|
||||
out_data,
|
||||
c_size_t(in_data_len))
|
||||
if result:
|
||||
raise ValueError("Error %d while %sing in OCB mode"
|
||||
% (result, trans_desc))
|
||||
return get_raw_buffer(out_data)
|
||||
|
||||
def _transcrypt(self, in_data, trans_func, trans_desc):
|
||||
# Last piece to encrypt/decrypt
|
||||
if in_data is None:
|
||||
out_data = self._transcrypt_aligned(self._cache_P,
|
||||
len(self._cache_P),
|
||||
trans_func,
|
||||
trans_desc)
|
||||
self._cache_P = b""
|
||||
return out_data
|
||||
|
||||
# Try to fill up the cache, if it already contains something
|
||||
prefix = b""
|
||||
if len(self._cache_P) > 0:
|
||||
filler = min(16 - len(self._cache_P), len(in_data))
|
||||
self._cache_P += _copy_bytes(None, filler, in_data)
|
||||
in_data = in_data[filler:]
|
||||
|
||||
if len(self._cache_P) < 16:
|
||||
# We could not manage to fill the cache, so there is certainly
|
||||
# no output yet.
|
||||
return b""
|
||||
|
||||
# Clear the cache, and proceeding with any other aligned data
|
||||
prefix = self._transcrypt_aligned(self._cache_P,
|
||||
len(self._cache_P),
|
||||
trans_func,
|
||||
trans_desc)
|
||||
self._cache_P = b""
|
||||
|
||||
# Process data in multiples of the block size
|
||||
trans_len = len(in_data) // 16 * 16
|
||||
result = self._transcrypt_aligned(c_uint8_ptr(in_data),
|
||||
trans_len,
|
||||
trans_func,
|
||||
trans_desc)
|
||||
if prefix:
|
||||
result = prefix + result
|
||||
|
||||
# Left-over
|
||||
self._cache_P = _copy_bytes(trans_len, None, in_data)
|
||||
|
||||
return result
|
||||
|
||||
def encrypt(self, plaintext=None):
|
||||
"""Encrypt the next piece of plaintext.
|
||||
|
||||
After the entire plaintext has been passed (but before `digest`),
|
||||
you **must** call this method one last time with no arguments to collect
|
||||
the final piece of ciphertext.
|
||||
|
||||
If possible, use the method `encrypt_and_digest` instead.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The next piece of data to encrypt or ``None`` to signify
|
||||
that encryption has finished and that any remaining ciphertext
|
||||
has to be produced.
|
||||
:Return:
|
||||
the ciphertext, as a byte string.
|
||||
Its length may not match the length of the *plaintext*.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
|
||||
if plaintext is None:
|
||||
self._next = ["digest"]
|
||||
else:
|
||||
self._next = ["encrypt"]
|
||||
return self._transcrypt(plaintext, _raw_ocb_lib.OCB_encrypt, "encrypt")
|
||||
|
||||
def decrypt(self, ciphertext=None):
|
||||
"""Decrypt the next piece of ciphertext.
|
||||
|
||||
After the entire ciphertext has been passed (but before `verify`),
|
||||
you **must** call this method one last time with no arguments to collect
|
||||
the remaining piece of plaintext.
|
||||
|
||||
If possible, use the method `decrypt_and_verify` instead.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The next piece of data to decrypt or ``None`` to signify
|
||||
that decryption has finished and that any remaining plaintext
|
||||
has to be produced.
|
||||
:Return:
|
||||
the plaintext, as a byte string.
|
||||
Its length may not match the length of the *ciphertext*.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
|
||||
if ciphertext is None:
|
||||
self._next = ["verify"]
|
||||
else:
|
||||
self._next = ["decrypt"]
|
||||
return self._transcrypt(ciphertext,
|
||||
_raw_ocb_lib.OCB_decrypt,
|
||||
"decrypt")
|
||||
|
||||
def _compute_mac_tag(self):
|
||||
|
||||
if self._mac_tag is not None:
|
||||
return
|
||||
|
||||
if self._cache_A:
|
||||
self._update(self._cache_A, len(self._cache_A))
|
||||
self._cache_A = b""
|
||||
|
||||
mac_tag = create_string_buffer(16)
|
||||
result = _raw_ocb_lib.OCB_digest(self._state.get(),
|
||||
mac_tag,
|
||||
c_size_t(len(mac_tag))
|
||||
)
|
||||
if result:
|
||||
raise ValueError("Error %d while computing digest in OCB mode"
|
||||
% result)
|
||||
self._mac_tag = get_raw_buffer(mac_tag)[:self._mac_len]
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* MAC tag.
|
||||
|
||||
Call this method after the final `encrypt` (the one with no arguments)
|
||||
to obtain the MAC tag.
|
||||
|
||||
The MAC tag is needed by the receiver to determine authenticity
|
||||
of the message.
|
||||
|
||||
:Return: the MAC, as a byte string.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() cannot be called now for this cipher")
|
||||
|
||||
assert(len(self._cache_P) == 0)
|
||||
|
||||
self._next = ["digest"]
|
||||
|
||||
if self._mac_tag is None:
|
||||
self._compute_mac_tag()
|
||||
|
||||
return self._mac_tag
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* MAC tag.
|
||||
|
||||
This method is like `digest`.
|
||||
|
||||
:Return: the MAC, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* MAC tag.
|
||||
|
||||
Call this method after the final `decrypt` (the one with no arguments)
|
||||
to check if the message is authentic and valid.
|
||||
|
||||
:Parameters:
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called now for this cipher")
|
||||
|
||||
assert(len(self._cache_P) == 0)
|
||||
|
||||
self._next = ["verify"]
|
||||
|
||||
if self._mac_tag is None:
|
||||
self._compute_mac_tag()
|
||||
|
||||
secret = get_random_bytes(16)
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* MAC tag.
|
||||
|
||||
This method is like `verify`.
|
||||
|
||||
:Parameters:
|
||||
hex_mac_tag : string
|
||||
This is the *printable* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext):
|
||||
"""Encrypt the message and create the MAC tag in one step.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The entire message to encrypt.
|
||||
:Return:
|
||||
a tuple with two byte strings:
|
||||
|
||||
- the encrypted data
|
||||
- the MAC
|
||||
"""
|
||||
|
||||
return self.encrypt(plaintext) + self.encrypt(), self.digest()
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, received_mac_tag):
|
||||
"""Decrypted the message and verify its authenticity in one step.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The entire message to decrypt.
|
||||
received_mac_tag : byte string
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
|
||||
:Return: the decrypted data (byte string).
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
plaintext = self.decrypt(ciphertext) + self.decrypt()
|
||||
self.verify(received_mac_tag)
|
||||
return plaintext
|
||||
|
||||
|
||||
def _create_ocb_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in OCB mode.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
A symmetric cipher module from `Crypto.Cipher`
|
||||
(like `Crypto.Cipher.AES`).
|
||||
|
||||
:Keywords:
|
||||
nonce : bytes/bytearray/memoryview
|
||||
A value that must never be reused for any other encryption.
|
||||
Its length can vary from 1 to 15 bytes.
|
||||
If not specified, a random 15 bytes long nonce is generated.
|
||||
|
||||
mac_len : integer
|
||||
Length of the MAC, in bytes.
|
||||
It must be in the range ``[8..16]``.
|
||||
The default is 16 (128 bits).
|
||||
|
||||
Any other keyword will be passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present).
|
||||
"""
|
||||
|
||||
try:
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
if nonce is None:
|
||||
nonce = get_random_bytes(15)
|
||||
mac_len = kwargs.pop("mac_len", 16)
|
||||
except KeyError as e:
|
||||
raise TypeError("Keyword missing: " + str(e))
|
||||
|
||||
return OcbMode(factory, nonce, mac_len, kwargs)
|
||||
@@ -1,36 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, Any, Optional, Tuple, Dict, overload
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
class OcbMode(object):
|
||||
block_size: int
|
||||
nonce: Buffer
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
nonce: Buffer,
|
||||
mac_len: int,
|
||||
cipher_params: Dict) -> None: ...
|
||||
|
||||
def update(self, assoc_data: Buffer) -> OcbMode: ...
|
||||
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, hex_mac_tag: str) -> None: ...
|
||||
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
def decrypt_and_verify(self,
|
||||
ciphertext: Buffer,
|
||||
received_mac_tag: Buffer) -> bytes: ...
|
||||
@@ -1,282 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Cipher/mode_ofb.py : OFB mode
|
||||
#
|
||||
# ===================================================================
|
||||
# The contents of this file are dedicated to the public domain. To
|
||||
# the extent that dedication to the public domain is not available,
|
||||
# everyone is granted a worldwide, perpetual, royalty-free,
|
||||
# non-exclusive license to exercise all rights associated with the
|
||||
# contents of this file for any purpose whatsoever.
|
||||
# No rights are reserved.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Output Feedback (CFB) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['OfbMode']
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
|
||||
create_string_buffer, get_raw_buffer,
|
||||
SmartPointer, c_size_t, c_uint8_ptr,
|
||||
is_writeable_buffer)
|
||||
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
raw_ofb_lib = load_pycryptodome_raw_lib("Crypto.Cipher._raw_ofb", """
|
||||
int OFB_start_operation(void *cipher,
|
||||
const uint8_t iv[],
|
||||
size_t iv_len,
|
||||
void **pResult);
|
||||
int OFB_encrypt(void *ofbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int OFB_decrypt(void *ofbState,
|
||||
const uint8_t *in,
|
||||
uint8_t *out,
|
||||
size_t data_len);
|
||||
int OFB_stop_operation(void *state);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class OfbMode(object):
|
||||
"""*Output FeedBack (OFB)*.
|
||||
|
||||
This mode is very similar to CBC, but it
|
||||
transforms the underlying block cipher into a stream cipher.
|
||||
|
||||
The keystream is the iterated block encryption of the
|
||||
previous ciphertext block.
|
||||
|
||||
An Initialization Vector (*IV*) is required.
|
||||
|
||||
See `NIST SP800-38A`_ , Section 6.4.
|
||||
|
||||
.. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, block_cipher, iv):
|
||||
"""Create a new block cipher, configured in OFB mode.
|
||||
|
||||
:Parameters:
|
||||
block_cipher : C pointer
|
||||
A smart pointer to the low-level block cipher instance.
|
||||
|
||||
iv : bytes/bytearray/memoryview
|
||||
The initialization vector to use for encryption or decryption.
|
||||
It is as long as the cipher block.
|
||||
|
||||
**The IV must be a nonce, to to be reused for any other
|
||||
message**. It shall be a nonce or a random value.
|
||||
|
||||
Reusing the *IV* for encryptions performed with the same key
|
||||
compromises confidentiality.
|
||||
"""
|
||||
|
||||
self._state = VoidPointer()
|
||||
result = raw_ofb_lib.OFB_start_operation(block_cipher.get(),
|
||||
c_uint8_ptr(iv),
|
||||
c_size_t(len(iv)),
|
||||
self._state.address_of())
|
||||
if result:
|
||||
raise ValueError("Error %d while instantiating the OFB mode"
|
||||
% result)
|
||||
|
||||
# Ensure that object disposal of this Python object will (eventually)
|
||||
# free the memory allocated by the raw library for the cipher mode
|
||||
self._state = SmartPointer(self._state.get(),
|
||||
raw_ofb_lib.OFB_stop_operation)
|
||||
|
||||
# Memory allocated for the underlying block cipher is now owed
|
||||
# by the cipher mode
|
||||
block_cipher.release()
|
||||
|
||||
self.block_size = len(iv)
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self.iv = _copy_bytes(None, None, iv)
|
||||
"""The Initialization Vector originally used to create the object.
|
||||
The value does not change."""
|
||||
|
||||
self.IV = self.iv
|
||||
"""Alias for `iv`"""
|
||||
|
||||
self._next = ["encrypt", "decrypt"]
|
||||
|
||||
def encrypt(self, plaintext, output=None):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the ciphertext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() cannot be called after decrypt()")
|
||||
self._next = ["encrypt"]
|
||||
|
||||
if output is None:
|
||||
ciphertext = create_string_buffer(len(plaintext))
|
||||
else:
|
||||
ciphertext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(plaintext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ofb_lib.OFB_encrypt(self._state.get(),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_size_t(len(plaintext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while encrypting in OFB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(ciphertext)
|
||||
else:
|
||||
return None
|
||||
|
||||
def decrypt(self, ciphertext, output=None):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext is written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return:
|
||||
If ``output`` is ``None``, the plaintext is returned as ``bytes``.
|
||||
Otherwise, ``None``.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() cannot be called after encrypt()")
|
||||
self._next = ["decrypt"]
|
||||
|
||||
if output is None:
|
||||
plaintext = create_string_buffer(len(ciphertext))
|
||||
else:
|
||||
plaintext = output
|
||||
|
||||
if not is_writeable_buffer(output):
|
||||
raise TypeError("output must be a bytearray or a writeable memoryview")
|
||||
|
||||
if len(ciphertext) != len(output):
|
||||
raise ValueError("output must have the same length as the input"
|
||||
" (%d bytes)" % len(plaintext))
|
||||
|
||||
result = raw_ofb_lib.OFB_decrypt(self._state.get(),
|
||||
c_uint8_ptr(ciphertext),
|
||||
c_uint8_ptr(plaintext),
|
||||
c_size_t(len(ciphertext)))
|
||||
if result:
|
||||
raise ValueError("Error %d while decrypting in OFB mode" % result)
|
||||
|
||||
if output is None:
|
||||
return get_raw_buffer(plaintext)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def _create_ofb_cipher(factory, **kwargs):
|
||||
"""Instantiate a cipher object that performs OFB encryption/decryption.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The underlying block cipher, a module from ``Crypto.Cipher``.
|
||||
|
||||
:Keywords:
|
||||
iv : bytes/bytearray/memoryview
|
||||
The IV to use for OFB.
|
||||
|
||||
IV : bytes/bytearray/memoryview
|
||||
Alias for ``iv``.
|
||||
|
||||
Any other keyword will be passed to the underlying block cipher.
|
||||
See the relevant documentation for details (at least ``key`` will need
|
||||
to be present).
|
||||
"""
|
||||
|
||||
cipher_state = factory._create_base_cipher(kwargs)
|
||||
iv = kwargs.pop("IV", None)
|
||||
IV = kwargs.pop("iv", None)
|
||||
|
||||
if (None, None) == (iv, IV):
|
||||
iv = get_random_bytes(factory.block_size)
|
||||
if iv is not None:
|
||||
if IV is not None:
|
||||
raise TypeError("You must either use 'iv' or 'IV', not both")
|
||||
else:
|
||||
iv = IV
|
||||
|
||||
if len(iv) != factory.block_size:
|
||||
raise ValueError("Incorrect IV length (it must be %d bytes long)" %
|
||||
factory.block_size)
|
||||
|
||||
if kwargs:
|
||||
raise TypeError("Unknown parameters for OFB: %s" % str(kwargs))
|
||||
|
||||
return OfbMode(cipher_state, iv)
|
||||
@@ -1,25 +0,0 @@
|
||||
from typing import Union, overload
|
||||
|
||||
from Crypto.Util._raw_api import SmartPointer
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['OfbMode']
|
||||
|
||||
class OfbMode(object):
|
||||
block_size: int
|
||||
iv: Buffer
|
||||
IV: Buffer
|
||||
|
||||
def __init__(self,
|
||||
block_cipher: SmartPointer,
|
||||
iv: Buffer) -> None: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def encrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
@overload
|
||||
def decrypt(self, plaintext: Buffer, output: Union[bytearray, memoryview]) -> None: ...
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
OpenPGP mode.
|
||||
"""
|
||||
|
||||
__all__ = ['OpenPgpMode']
|
||||
|
||||
from Crypto.Util.py3compat import _copy_bytes
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
class OpenPgpMode(object):
|
||||
"""OpenPGP mode.
|
||||
|
||||
This mode is a variant of CFB, and it is only used in PGP and
|
||||
OpenPGP_ applications. If in doubt, use another mode.
|
||||
|
||||
An Initialization Vector (*IV*) is required.
|
||||
|
||||
Unlike CFB, the *encrypted* IV (not the IV itself) is
|
||||
transmitted to the receiver.
|
||||
|
||||
The IV is a random data block. For legacy reasons, two of its bytes are
|
||||
duplicated to act as a checksum for the correctness of the key, which is now
|
||||
known to be insecure and is ignored. The encrypted IV is therefore 2 bytes
|
||||
longer than the clean IV.
|
||||
|
||||
.. _OpenPGP: http://tools.ietf.org/html/rfc4880
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, key, iv, cipher_params):
|
||||
|
||||
#: The block size of the underlying cipher, in bytes.
|
||||
self.block_size = factory.block_size
|
||||
|
||||
self._done_first_block = False # True after the first encryption
|
||||
|
||||
# Instantiate a temporary cipher to process the IV
|
||||
IV_cipher = factory.new(
|
||||
key,
|
||||
factory.MODE_CFB,
|
||||
IV=b'\x00' * self.block_size,
|
||||
segment_size=self.block_size * 8,
|
||||
**cipher_params)
|
||||
|
||||
iv = _copy_bytes(None, None, iv)
|
||||
|
||||
# The cipher will be used for...
|
||||
if len(iv) == self.block_size:
|
||||
# ... encryption
|
||||
self._encrypted_IV = IV_cipher.encrypt(iv + iv[-2:])
|
||||
elif len(iv) == self.block_size + 2:
|
||||
# ... decryption
|
||||
self._encrypted_IV = iv
|
||||
# Last two bytes are for a deprecated "quick check" feature that
|
||||
# should not be used. (https://eprint.iacr.org/2005/033)
|
||||
iv = IV_cipher.decrypt(iv)[:-2]
|
||||
else:
|
||||
raise ValueError("Length of IV must be %d or %d bytes"
|
||||
" for MODE_OPENPGP"
|
||||
% (self.block_size, self.block_size + 2))
|
||||
|
||||
self.iv = self.IV = iv
|
||||
|
||||
# Instantiate the cipher for the real PGP data
|
||||
self._cipher = factory.new(
|
||||
key,
|
||||
factory.MODE_CFB,
|
||||
IV=self._encrypted_IV[-self.block_size:],
|
||||
segment_size=self.block_size * 8,
|
||||
**cipher_params)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""Encrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have encrypted a message
|
||||
you cannot encrypt (or decrypt) another message using the same
|
||||
object.
|
||||
|
||||
The data to encrypt can be broken up in two or
|
||||
more pieces and `encrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.encrypt(a) + c.encrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.encrypt(a+b)
|
||||
|
||||
This function does not add any padding to the plaintext.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
|
||||
:Return:
|
||||
the encrypted data, as a byte string.
|
||||
It is as long as *plaintext* with one exception:
|
||||
when encrypting the first message chunk,
|
||||
the encypted IV is prepended to the returned ciphertext.
|
||||
"""
|
||||
|
||||
res = self._cipher.encrypt(plaintext)
|
||||
if not self._done_first_block:
|
||||
res = self._encrypted_IV + res
|
||||
self._done_first_block = True
|
||||
return res
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""Decrypt data with the key and the parameters set at initialization.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
The data to decrypt can be broken up in two or
|
||||
more pieces and `decrypt` can be called multiple times.
|
||||
|
||||
That is, the statement:
|
||||
|
||||
>>> c.decrypt(a) + c.decrypt(b)
|
||||
|
||||
is equivalent to:
|
||||
|
||||
>>> c.decrypt(a+b)
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
|
||||
:Return: the decrypted data (byte string).
|
||||
"""
|
||||
|
||||
return self._cipher.decrypt(ciphertext)
|
||||
|
||||
|
||||
def _create_openpgp_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in OpenPGP mode.
|
||||
|
||||
:Parameters:
|
||||
factory : module
|
||||
The module.
|
||||
|
||||
:Keywords:
|
||||
key : bytes/bytearray/memoryview
|
||||
The secret key to use in the symmetric cipher.
|
||||
|
||||
IV : bytes/bytearray/memoryview
|
||||
The initialization vector to use for encryption or decryption.
|
||||
|
||||
For encryption, the IV must be as long as the cipher block size.
|
||||
|
||||
For decryption, it must be 2 bytes longer (it is actually the
|
||||
*encrypted* IV which was prefixed to the ciphertext).
|
||||
"""
|
||||
|
||||
iv = kwargs.pop("IV", None)
|
||||
IV = kwargs.pop("iv", None)
|
||||
|
||||
if (None, None) == (iv, IV):
|
||||
iv = get_random_bytes(factory.block_size)
|
||||
if iv is not None:
|
||||
if IV is not None:
|
||||
raise TypeError("You must either use 'iv' or 'IV', not both")
|
||||
else:
|
||||
iv = IV
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing component: " + str(e))
|
||||
|
||||
return OpenPgpMode(factory, key, iv, kwargs)
|
||||
@@ -1,20 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, Dict
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['OpenPgpMode']
|
||||
|
||||
class OpenPgpMode(object):
|
||||
block_size: int
|
||||
iv: Union[bytes, bytearray, memoryview]
|
||||
IV: Union[bytes, bytearray, memoryview]
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
iv: Buffer,
|
||||
cipher_params: Dict) -> None: ...
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# Copyright (c) 2014, Legrandin <helderijs@gmail.com>
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in
|
||||
# the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
# ===================================================================
|
||||
|
||||
"""
|
||||
Synthetic Initialization Vector (SIV) mode.
|
||||
"""
|
||||
|
||||
__all__ = ['SivMode']
|
||||
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from Crypto.Util.py3compat import bord, _copy_bytes
|
||||
|
||||
from Crypto.Util._raw_api import is_buffer
|
||||
|
||||
from Crypto.Util.number import long_to_bytes, bytes_to_long
|
||||
from Crypto.Protocol.KDF import _S2V
|
||||
from Crypto.Hash import BLAKE2s
|
||||
from Crypto.Random import get_random_bytes
|
||||
|
||||
|
||||
class SivMode(object):
|
||||
"""Synthetic Initialization Vector (SIV).
|
||||
|
||||
This is an Authenticated Encryption with Associated Data (`AEAD`_) mode.
|
||||
It provides both confidentiality and authenticity.
|
||||
|
||||
The header of the message may be left in the clear, if needed, and it will
|
||||
still be subject to authentication. The decryption step tells the receiver
|
||||
if the message comes from a source that really knowns the secret key.
|
||||
Additionally, decryption detects if any part of the message - including the
|
||||
header - has been modified or corrupted.
|
||||
|
||||
Unlike other AEAD modes such as CCM, EAX or GCM, accidental reuse of a
|
||||
nonce is not catastrophic for the confidentiality of the message. The only
|
||||
effect is that an attacker can tell when the same plaintext (and same
|
||||
associated data) is protected with the same key.
|
||||
|
||||
The length of the MAC is fixed to the block size of the underlying cipher.
|
||||
The key size is twice the length of the key of the underlying cipher.
|
||||
|
||||
This mode is only available for AES ciphers.
|
||||
|
||||
+--------------------+---------------+-------------------+
|
||||
| Cipher | SIV MAC size | SIV key length |
|
||||
| | (bytes) | (bytes) |
|
||||
+====================+===============+===================+
|
||||
| AES-128 | 16 | 32 |
|
||||
+--------------------+---------------+-------------------+
|
||||
| AES-192 | 16 | 48 |
|
||||
+--------------------+---------------+-------------------+
|
||||
| AES-256 | 16 | 64 |
|
||||
+--------------------+---------------+-------------------+
|
||||
|
||||
See `RFC5297`_ and the `original paper`__.
|
||||
|
||||
.. _RFC5297: https://tools.ietf.org/html/rfc5297
|
||||
.. _AEAD: http://blog.cryptographyengineering.com/2012/05/how-to-choose-authenticated-encryption.html
|
||||
.. __: http://www.cs.ucdavis.edu/~rogaway/papers/keywrap.pdf
|
||||
|
||||
:undocumented: __init__
|
||||
"""
|
||||
|
||||
def __init__(self, factory, key, nonce, kwargs):
|
||||
|
||||
self.block_size = factory.block_size
|
||||
"""The block size of the underlying cipher, in bytes."""
|
||||
|
||||
self._factory = factory
|
||||
|
||||
self._cipher_params = kwargs
|
||||
|
||||
if len(key) not in (32, 48, 64):
|
||||
raise ValueError("Incorrect key length (%d bytes)" % len(key))
|
||||
|
||||
if nonce is not None:
|
||||
if not is_buffer(nonce):
|
||||
raise TypeError("When provided, the nonce must be bytes, bytearray or memoryview")
|
||||
|
||||
if len(nonce) == 0:
|
||||
raise ValueError("When provided, the nonce must be non-empty")
|
||||
|
||||
self.nonce = _copy_bytes(None, None, nonce)
|
||||
"""Public attribute is only available in case of non-deterministic
|
||||
encryption."""
|
||||
|
||||
subkey_size = len(key) // 2
|
||||
|
||||
self._mac_tag = None # Cache for MAC tag
|
||||
self._kdf = _S2V(key[:subkey_size],
|
||||
ciphermod=factory,
|
||||
cipher_params=self._cipher_params)
|
||||
self._subkey_cipher = key[subkey_size:]
|
||||
|
||||
# Purely for the purpose of verifying that cipher_params are OK
|
||||
factory.new(key[:subkey_size], factory.MODE_ECB, **kwargs)
|
||||
|
||||
# Allowed transitions after initialization
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
def _create_ctr_cipher(self, v):
|
||||
"""Create a new CTR cipher from V in SIV mode"""
|
||||
|
||||
v_int = bytes_to_long(v)
|
||||
q = v_int & 0xFFFFFFFFFFFFFFFF7FFFFFFF7FFFFFFF
|
||||
return self._factory.new(
|
||||
self._subkey_cipher,
|
||||
self._factory.MODE_CTR,
|
||||
initial_value=q,
|
||||
nonce=b"",
|
||||
**self._cipher_params)
|
||||
|
||||
def update(self, component):
|
||||
"""Protect one associated data component
|
||||
|
||||
For SIV, the associated data is a sequence (*vector*) of non-empty
|
||||
byte strings (*components*).
|
||||
|
||||
This method consumes the next component. It must be called
|
||||
once for each of the components that constitue the associated data.
|
||||
|
||||
Note that the components have clear boundaries, so that:
|
||||
|
||||
>>> cipher.update(b"builtin")
|
||||
>>> cipher.update(b"securely")
|
||||
|
||||
is not equivalent to:
|
||||
|
||||
>>> cipher.update(b"built")
|
||||
>>> cipher.update(b"insecurely")
|
||||
|
||||
If there is no associated data, this method must not be called.
|
||||
|
||||
:Parameters:
|
||||
component : bytes/bytearray/memoryview
|
||||
The next associated data component.
|
||||
"""
|
||||
|
||||
if "update" not in self._next:
|
||||
raise TypeError("update() can only be called"
|
||||
" immediately after initialization")
|
||||
|
||||
self._next = ["update", "encrypt", "decrypt",
|
||||
"digest", "verify"]
|
||||
|
||||
return self._kdf.update(component)
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
For SIV, encryption and MAC authentication must take place at the same
|
||||
point. This method shall not be used.
|
||||
|
||||
Use `encrypt_and_digest` instead.
|
||||
"""
|
||||
|
||||
raise TypeError("encrypt() not allowed for SIV mode."
|
||||
" Use encrypt_and_digest() instead.")
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""
|
||||
For SIV, decryption and verification must take place at the same
|
||||
point. This method shall not be used.
|
||||
|
||||
Use `decrypt_and_verify` instead.
|
||||
"""
|
||||
|
||||
raise TypeError("decrypt() not allowed for SIV mode."
|
||||
" Use decrypt_and_verify() instead.")
|
||||
|
||||
def digest(self):
|
||||
"""Compute the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method returns the MAC that shall be sent to the receiver,
|
||||
together with the ciphertext.
|
||||
|
||||
:Return: the MAC, as a byte string.
|
||||
"""
|
||||
|
||||
if "digest" not in self._next:
|
||||
raise TypeError("digest() cannot be called when decrypting"
|
||||
" or validating a message")
|
||||
self._next = ["digest"]
|
||||
if self._mac_tag is None:
|
||||
self._mac_tag = self._kdf.derive()
|
||||
return self._mac_tag
|
||||
|
||||
def hexdigest(self):
|
||||
"""Compute the *printable* MAC tag.
|
||||
|
||||
This method is like `digest`.
|
||||
|
||||
:Return: the MAC, as a hexadecimal string.
|
||||
"""
|
||||
return "".join(["%02x" % bord(x) for x in self.digest()])
|
||||
|
||||
def verify(self, received_mac_tag):
|
||||
"""Validate the *binary* MAC tag.
|
||||
|
||||
The caller invokes this function at the very end.
|
||||
|
||||
This method checks if the decrypted message is indeed valid
|
||||
(that is, if the key is correct) and it has not been
|
||||
tampered with while in transit.
|
||||
|
||||
:Parameters:
|
||||
received_mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "verify" not in self._next:
|
||||
raise TypeError("verify() cannot be called"
|
||||
" when encrypting a message")
|
||||
self._next = ["verify"]
|
||||
|
||||
if self._mac_tag is None:
|
||||
self._mac_tag = self._kdf.derive()
|
||||
|
||||
secret = get_random_bytes(16)
|
||||
|
||||
mac1 = BLAKE2s.new(digest_bits=160, key=secret, data=self._mac_tag)
|
||||
mac2 = BLAKE2s.new(digest_bits=160, key=secret, data=received_mac_tag)
|
||||
|
||||
if mac1.digest() != mac2.digest():
|
||||
raise ValueError("MAC check failed")
|
||||
|
||||
def hexverify(self, hex_mac_tag):
|
||||
"""Validate the *printable* MAC tag.
|
||||
|
||||
This method is like `verify`.
|
||||
|
||||
:Parameters:
|
||||
hex_mac_tag : string
|
||||
This is the *printable* MAC, as received from the sender.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
self.verify(unhexlify(hex_mac_tag))
|
||||
|
||||
def encrypt_and_digest(self, plaintext, output=None):
|
||||
"""Perform encrypt() and digest() in one step.
|
||||
|
||||
:Parameters:
|
||||
plaintext : bytes/bytearray/memoryview
|
||||
The piece of data to encrypt.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the ciphertext must be written to.
|
||||
If ``None``, the ciphertext is returned.
|
||||
:Return:
|
||||
a tuple with two items:
|
||||
|
||||
- the ciphertext, as ``bytes``
|
||||
- the MAC tag, as ``bytes``
|
||||
|
||||
The first item becomes ``None`` when the ``output`` parameter
|
||||
specified a location for the result.
|
||||
"""
|
||||
|
||||
if "encrypt" not in self._next:
|
||||
raise TypeError("encrypt() can only be called after"
|
||||
" initialization or an update()")
|
||||
|
||||
self._next = ["digest"]
|
||||
|
||||
# Compute V (MAC)
|
||||
if hasattr(self, 'nonce'):
|
||||
self._kdf.update(self.nonce)
|
||||
self._kdf.update(plaintext)
|
||||
self._mac_tag = self._kdf.derive()
|
||||
|
||||
cipher = self._create_ctr_cipher(self._mac_tag)
|
||||
|
||||
return cipher.encrypt(plaintext, output=output), self._mac_tag
|
||||
|
||||
def decrypt_and_verify(self, ciphertext, mac_tag, output=None):
|
||||
"""Perform decryption and verification in one step.
|
||||
|
||||
A cipher object is stateful: once you have decrypted a message
|
||||
you cannot decrypt (or encrypt) another message with the same
|
||||
object.
|
||||
|
||||
You cannot reuse an object for encrypting
|
||||
or decrypting other data with the same key.
|
||||
|
||||
This function does not remove any padding from the plaintext.
|
||||
|
||||
:Parameters:
|
||||
ciphertext : bytes/bytearray/memoryview
|
||||
The piece of data to decrypt.
|
||||
It can be of any length.
|
||||
mac_tag : bytes/bytearray/memoryview
|
||||
This is the *binary* MAC, as received from the sender.
|
||||
:Keywords:
|
||||
output : bytearray/memoryview
|
||||
The location where the plaintext must be written to.
|
||||
If ``None``, the plaintext is returned.
|
||||
:Return: the plaintext as ``bytes`` or ``None`` when the ``output``
|
||||
parameter specified a location for the result.
|
||||
:Raises ValueError:
|
||||
if the MAC does not match. The message has been tampered with
|
||||
or the key is incorrect.
|
||||
"""
|
||||
|
||||
if "decrypt" not in self._next:
|
||||
raise TypeError("decrypt() can only be called"
|
||||
" after initialization or an update()")
|
||||
self._next = ["verify"]
|
||||
|
||||
# Take the MAC and start the cipher for decryption
|
||||
self._cipher = self._create_ctr_cipher(mac_tag)
|
||||
|
||||
plaintext = self._cipher.decrypt(ciphertext, output=output)
|
||||
|
||||
if hasattr(self, 'nonce'):
|
||||
self._kdf.update(self.nonce)
|
||||
self._kdf.update(plaintext if output is None else output)
|
||||
self.verify(mac_tag)
|
||||
|
||||
return plaintext
|
||||
|
||||
|
||||
def _create_siv_cipher(factory, **kwargs):
|
||||
"""Create a new block cipher, configured in
|
||||
Synthetic Initializaton Vector (SIV) mode.
|
||||
|
||||
:Parameters:
|
||||
|
||||
factory : object
|
||||
A symmetric cipher module from `Crypto.Cipher`
|
||||
(like `Crypto.Cipher.AES`).
|
||||
|
||||
:Keywords:
|
||||
|
||||
key : bytes/bytearray/memoryview
|
||||
The secret key to use in the symmetric cipher.
|
||||
It must be 32, 48 or 64 bytes long.
|
||||
If AES is the chosen cipher, the variants *AES-128*,
|
||||
*AES-192* and or *AES-256* will be used internally.
|
||||
|
||||
nonce : bytes/bytearray/memoryview
|
||||
For deterministic encryption, it is not present.
|
||||
|
||||
Otherwise, it is a value that must never be reused
|
||||
for encrypting message under this key.
|
||||
|
||||
There are no restrictions on its length,
|
||||
but it is recommended to use at least 16 bytes.
|
||||
"""
|
||||
|
||||
try:
|
||||
key = kwargs.pop("key")
|
||||
except KeyError as e:
|
||||
raise TypeError("Missing parameter: " + str(e))
|
||||
|
||||
nonce = kwargs.pop("nonce", None)
|
||||
|
||||
return SivMode(factory, key, nonce, kwargs)
|
||||
@@ -1,38 +0,0 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, Tuple, Dict, Optional, overload
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['SivMode']
|
||||
|
||||
class SivMode(object):
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
kwargs: Dict) -> None: ...
|
||||
|
||||
def update(self, component: Buffer) -> SivMode: ...
|
||||
|
||||
def encrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
def decrypt(self, plaintext: Buffer) -> bytes: ...
|
||||
|
||||
def digest(self) -> bytes: ...
|
||||
def hexdigest(self) -> str: ...
|
||||
def verify(self, received_mac_tag: Buffer) -> None: ...
|
||||
def hexverify(self, hex_mac_tag: str) -> None: ...
|
||||
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer) -> Tuple[bytes, bytes]: ...
|
||||
@overload
|
||||
def encrypt_and_digest(self,
|
||||
plaintext: Buffer,
|
||||
output: Buffer) -> Tuple[None, bytes]: ...
|
||||
def decrypt_and_verify(self,
|
||||
ciphertext: Buffer,
|
||||
received_mac_tag: Buffer,
|
||||
output: Optional[Union[bytearray, memoryview]] = ...) -> bytes: ...
|
||||
Binary file not shown.
@@ -1,41 +0,0 @@
|
||||
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, c_size_t,
|
||||
c_uint8_ptr)
|
||||
|
||||
|
||||
_raw_pkcs1_decode = load_pycryptodome_raw_lib("Crypto.Cipher._pkcs1_decode",
|
||||
"""
|
||||
int pkcs1_decode(const uint8_t *em, size_t len_em,
|
||||
const uint8_t *sentinel, size_t len_sentinel,
|
||||
size_t expected_pt_len,
|
||||
uint8_t *output);
|
||||
|
||||
int oaep_decode(const uint8_t *em,
|
||||
size_t em_len,
|
||||
const uint8_t *lHash,
|
||||
size_t hLen,
|
||||
const uint8_t *db,
|
||||
size_t db_len);
|
||||
""")
|
||||
|
||||
|
||||
def pkcs1_decode(em, sentinel, expected_pt_len, output):
|
||||
if len(em) != len(output):
|
||||
raise ValueError("Incorrect output length")
|
||||
|
||||
ret = _raw_pkcs1_decode.pkcs1_decode(c_uint8_ptr(em),
|
||||
c_size_t(len(em)),
|
||||
c_uint8_ptr(sentinel),
|
||||
c_size_t(len(sentinel)),
|
||||
c_size_t(expected_pt_len),
|
||||
c_uint8_ptr(output))
|
||||
return ret
|
||||
|
||||
|
||||
def oaep_decode(em, lHash, db):
|
||||
ret = _raw_pkcs1_decode.oaep_decode(c_uint8_ptr(em),
|
||||
c_size_t(len(em)),
|
||||
c_uint8_ptr(lHash),
|
||||
c_size_t(len(lHash)),
|
||||
c_uint8_ptr(db),
|
||||
c_size_t(len(db)))
|
||||
return ret
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user