扫码登录,获取cookies
This commit is contained in:
@@ -122,18 +122,18 @@
|
||||
- **Property 25: 签到日志状态过滤**
|
||||
- **Validates: Requirements 8.1-8.3**
|
||||
|
||||
- [ ] 7. Checkpoint - 确保 API_Service 所有测试通过
|
||||
- [x] 7. Checkpoint - 确保 API_Service 所有测试通过
|
||||
- 运行所有测试,确认账号管理、任务配置、日志查询功能正常
|
||||
- 如有问题请向用户确认
|
||||
|
||||
- [ ] 8. 重构 Task_Scheduler(真实数据库交互)
|
||||
- [ ] 8.1 重构 Task_Scheduler 使用 shared 模块
|
||||
- [x] 8. 重构 Task_Scheduler(真实数据库交互)
|
||||
- [x] 8.1 重构 Task_Scheduler 使用 shared 模块
|
||||
- 修改 `task_scheduler/app/celery_app.py` 导入 shared models
|
||||
- 实现 `load_scheduled_tasks()`:从 DB 查询 `is_enabled=True` 的 Task,动态注册到 Celery Beat
|
||||
- 实现 Redis pub/sub 监听:接收任务变更通知,动态更新调度
|
||||
- 替换 `signin_tasks.py` 中的 mock 账号列表为真实 DB 查询
|
||||
- _Requirements: 5.1, 5.2, 5.3_
|
||||
- [ ] 8.2 实现分布式锁和重试机制
|
||||
- [x] 8.2 实现分布式锁和重试机制
|
||||
- 使用 Redis SETNX 实现分布式锁,防止同一任务重复调度
|
||||
- 配置 Celery 任务重试:`max_retries=3`、`default_retry_delay=60`
|
||||
- _Requirements: 5.4, 5.5_
|
||||
@@ -142,14 +142,14 @@
|
||||
- **Property 18: 分布式锁防重复调度**
|
||||
- **Validates: Requirements 5.1, 5.5**
|
||||
|
||||
- [ ] 9. 重构 Signin_Executor(真实数据库交互)
|
||||
- [ ] 9.1 重构 Signin_Executor 使用 shared 模块
|
||||
- [x] 9. 重构 Signin_Executor(真实数据库交互)
|
||||
- [x] 9.1 重构 Signin_Executor 使用 shared 模块
|
||||
- 修改 `signin_service.py` 中的 `_get_account_info()` 从 DB 查询真实 Account 数据
|
||||
- 修改 `weibo_client.py` 中的 `_decrypt_cookies()` 使用 `shared/crypto.py`
|
||||
- 实现签到结果写入 `signin_logs` 表(替代 mock)
|
||||
- 实现 Cookie 失效时更新 `account.status = "invalid_cookie"`
|
||||
- _Requirements: 6.1, 6.2, 6.4, 6.5_
|
||||
- [ ] 9.2 实现反爬虫防护模块
|
||||
- [x] 9.2 实现反爬虫防护模块
|
||||
- 实现随机延迟函数:返回 `[min, max]` 范围内的随机值
|
||||
- 实现 User-Agent 轮换:从预定义列表中随机选择
|
||||
- 实现代理池集成:调用 proxy pool 服务获取代理,不可用时降级为直连
|
||||
@@ -161,12 +161,12 @@
|
||||
- **Property 22: User-Agent 来源**
|
||||
- **Validates: Requirements 6.1, 6.4, 6.5, 7.1, 7.2**
|
||||
|
||||
- [ ] 10. 更新 Dockerfile 和集成配置
|
||||
- [ ] 10.1 更新 `backend/Dockerfile`
|
||||
- [x] 10. 更新 Dockerfile 和集成配置
|
||||
- [x] 10.1 更新 `backend/Dockerfile`
|
||||
- 在每个构建阶段添加 `COPY shared/ ./shared/`
|
||||
- 确保 shared 模块在所有服务容器中可用
|
||||
- _Requirements: 10.1, 10.3_
|
||||
- [ ] 10.2 更新 `backend/requirements.txt`
|
||||
- [x] 10.2 更新 `backend/requirements.txt`
|
||||
- 添加 `croniter`(Cron 表达式解析)
|
||||
- 添加 `hypothesis`(属性测试)
|
||||
- 添加 `pytest`、`pytest-asyncio`(测试框架)
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"html.autoClosingTags": false
|
||||
}
|
||||
159
CHECKLIST.md
Normal file
159
CHECKLIST.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# 本地启动前检查清单
|
||||
|
||||
## ✅ 配置检查
|
||||
|
||||
### 1. 数据库配置
|
||||
- [x] SQLite 作为默认数据库
|
||||
- [x] `backend/shared/config.py` 默认值设置为 SQLite
|
||||
- [x] `backend/.env.local` 配置正确
|
||||
- [x] 添加 `aiosqlite` 到 requirements.txt
|
||||
|
||||
### 2. Redis 配置
|
||||
- [x] Redis 设置为可选(`USE_REDIS=false`)
|
||||
- [x] `auth_service` 支持内存存储(不依赖 Redis)
|
||||
- [x] `task_scheduler` 支持内存锁(不依赖 Redis)
|
||||
- [x] 启动脚本移除 Redis 检查
|
||||
|
||||
### 3. 端口配置
|
||||
- [x] Auth Service: 8001
|
||||
- [x] API Service: 8000
|
||||
- [x] Frontend: 5000
|
||||
- [x] 所有服务端口配置一致
|
||||
|
||||
### 4. 环境变量
|
||||
- [x] `backend/.env.local` 创建完成
|
||||
- [x] `frontend/.env` 创建完成
|
||||
- [x] JWT 密钥配置
|
||||
- [x] Cookie 加密密钥配置
|
||||
|
||||
## ✅ 文件检查
|
||||
|
||||
### 必需文件
|
||||
- [x] `create_sqlite_db.py` - 数据库初始化脚本
|
||||
- [x] `init-db-sqlite.sql` - SQLite 建表脚本
|
||||
- [x] `start_all.bat` - Windows 启动脚本
|
||||
- [x] `stop_all.bat` - Windows 停止脚本
|
||||
- [x] `backend/.env.local` - 后端环境配置
|
||||
- [x] `frontend/.env` - 前端环境配置
|
||||
- [x] `LOCAL_SETUP.md` - 本地配置文档
|
||||
- [x] `CHECKLIST.md` - 本检查清单
|
||||
|
||||
### 依赖文件
|
||||
- [x] `backend/requirements.txt` - 包含 aiosqlite
|
||||
- [x] `frontend/requirements.txt` - Flask 依赖
|
||||
|
||||
## ✅ 代码修改
|
||||
|
||||
### backend/shared/config.py
|
||||
- [x] 默认数据库改为 SQLite
|
||||
- [x] Redis 设置为可选
|
||||
- [x] 添加 `USE_REDIS` 配置项
|
||||
|
||||
### backend/auth_service/app/utils/security.py
|
||||
- [x] Refresh Token 支持内存存储
|
||||
- [x] Redis 连接失败时降级到内存
|
||||
- [x] 添加过期 token 清理逻辑
|
||||
|
||||
### backend/task_scheduler/app/tasks/signin_tasks.py
|
||||
- [x] 分布式锁支持内存模式
|
||||
- [x] Redis 不可用时使用内存锁
|
||||
|
||||
### backend/requirements.txt
|
||||
- [x] 添加 `aiosqlite==0.19.0`
|
||||
|
||||
## ✅ 启动流程
|
||||
|
||||
### 自动启动(推荐)
|
||||
```bash
|
||||
start_all.bat
|
||||
```
|
||||
|
||||
### 手动启动步骤
|
||||
1. [x] 创建数据库: `python create_sqlite_db.py`
|
||||
2. [x] 安装后端依赖: `cd backend && pip install -r requirements.txt`
|
||||
3. [x] 安装前端依赖: `cd frontend && pip install -r requirements.txt`
|
||||
4. [x] 启动 Auth Service (端口 8001)
|
||||
5. [x] 启动 API Service (端口 8000)
|
||||
6. [x] 启动 Frontend (端口 5000)
|
||||
|
||||
## ✅ 功能验证
|
||||
|
||||
### 测试账号
|
||||
```
|
||||
用户名: admin
|
||||
邮箱: admin@example.com
|
||||
密码: Admin123!
|
||||
```
|
||||
|
||||
### 基础功能
|
||||
- [ ] 访问前端页面 http://localhost:5000
|
||||
- [ ] 使用测试账号登录
|
||||
- [ ] Token 刷新功能
|
||||
- [ ] 添加微博账号
|
||||
- [ ] 查看账号列表
|
||||
- [ ] 编辑账号信息
|
||||
- [ ] 删除账号
|
||||
- [ ] 创建签到任务
|
||||
- [ ] 查看签到日志
|
||||
- [ ] 用户注册功能(创建新用户)
|
||||
|
||||
### API 文档
|
||||
- [ ] Auth Service API 文档: http://localhost:8001/docs
|
||||
- [ ] API Service API 文档: http://localhost:8000/docs
|
||||
|
||||
## ⚠️ 已知限制
|
||||
|
||||
### 本地开发模式
|
||||
- ✅ 不需要 Redis(使用内存存储)
|
||||
- ✅ 不需要 MySQL(使用 SQLite)
|
||||
- ⚠️ Task Scheduler 和 Signin Executor 需要 Celery(本地测试可跳过)
|
||||
- ⚠️ 内存存储在服务重启后会丢失 Refresh Token
|
||||
|
||||
### 生产环境要求
|
||||
- ❌ 必须使用 Redis(分布式环境)
|
||||
- ❌ 建议使用 MySQL(性能和并发)
|
||||
- ❌ 必须配置 Celery(任务调度)
|
||||
- ❌ 必须修改所有默认密钥
|
||||
|
||||
## 🔧 故障排查
|
||||
|
||||
### 问题:找不到模块
|
||||
**解决方案**: 设置 PYTHONPATH
|
||||
```bash
|
||||
set PYTHONPATH=%CD%
|
||||
```
|
||||
|
||||
### 问题:端口被占用
|
||||
**解决方案**:
|
||||
1. 检查端口占用: `netstat -ano | findstr :8000`
|
||||
2. 关闭占用进程或修改端口配置
|
||||
|
||||
### 问题:数据库文件损坏
|
||||
**解决方案**:
|
||||
1. 删除 `weibo_hotsign.db`
|
||||
2. 重新运行 `python create_sqlite_db.py`
|
||||
|
||||
### 问题:Redis 警告
|
||||
**解决方案**:
|
||||
- 本地开发可以忽略,系统会自动使用内存存储
|
||||
- 如需完整功能,安装并启动 Redis
|
||||
|
||||
## 📝 开发注意事项
|
||||
|
||||
1. **虚拟环境**: 每个服务使用独立的虚拟环境
|
||||
2. **热重载**: 开发模式下代码修改会自动重启
|
||||
3. **日志查看**: 每个服务有独立的命令行窗口显示日志
|
||||
4. **数据持久化**: SQLite 数据库文件在项目根目录
|
||||
5. **Session 存储**: Flask Session 使用文件系统存储
|
||||
|
||||
## ✨ 下一步
|
||||
|
||||
- [ ] 测试所有功能
|
||||
- [ ] 添加测试数据
|
||||
- [ ] 配置 Celery(如需任务调度)
|
||||
- [ ] 部署到生产环境
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2024
|
||||
**维护者**: Weibo-HotSign Team
|
||||
253
LOCAL_SETUP.md
Normal file
253
LOCAL_SETUP.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# Weibo-HotSign 本地开发环境配置指南
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 环境要求
|
||||
|
||||
- Python 3.8+
|
||||
- Windows 操作系统
|
||||
- (可选)Redis(如果需要完整功能)
|
||||
|
||||
### 2. 一键启动
|
||||
|
||||
```bash
|
||||
# 双击运行或在命令行执行
|
||||
start_all.bat
|
||||
```
|
||||
|
||||
这个脚本会自动:
|
||||
- 检查 Python 环境
|
||||
- 创建 SQLite 数据库(包含测试用户)
|
||||
- 安装所有依赖
|
||||
- 启动所有服务
|
||||
|
||||
### 3. 访问应用
|
||||
|
||||
启动成功后,浏览器会自动打开:
|
||||
- **前端界面**: http://localhost:5000
|
||||
- **API Service**: http://localhost:8000
|
||||
- **Auth Service**: http://localhost:8001
|
||||
|
||||
### 4. 测试账号
|
||||
|
||||
数据库初始化时会自动创建一个测试用户:
|
||||
|
||||
```
|
||||
用户名: admin
|
||||
邮箱: admin@example.com
|
||||
密码: Admin123!
|
||||
```
|
||||
|
||||
你也可以通过注册页面创建新用户。
|
||||
|
||||
### 5. 停止服务
|
||||
|
||||
```bash
|
||||
# 双击运行或在命令行执行
|
||||
stop_all.bat
|
||||
```
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 数据库配置
|
||||
|
||||
默认使用 SQLite 数据库,无需额外配置。数据库文件位于项目根目录:
|
||||
```
|
||||
weibo_hotsign.db
|
||||
```
|
||||
|
||||
如需使用 MySQL,修改 `backend/.env.local`:
|
||||
```env
|
||||
DATABASE_URL=mysql+aiomysql://user:password@localhost/weibo_hotsign
|
||||
```
|
||||
|
||||
### Redis 配置(可选)
|
||||
|
||||
本地开发默认**不需要 Redis**,系统会使用内存存储。
|
||||
|
||||
如需启用 Redis(用于生产环境或测试分布式功能),修改 `backend/.env.local`:
|
||||
```env
|
||||
USE_REDIS=true
|
||||
REDIS_URL=redis://localhost:6379
|
||||
```
|
||||
|
||||
### 环境变量
|
||||
|
||||
#### 后端配置 (`backend/.env.local`)
|
||||
|
||||
```env
|
||||
# 数据库
|
||||
DATABASE_URL=sqlite+aiosqlite:///./weibo_hotsign.db
|
||||
|
||||
# Redis(可选)
|
||||
USE_REDIS=false
|
||||
# REDIS_URL=redis://localhost:6379
|
||||
|
||||
# JWT 配置
|
||||
JWT_SECRET_KEY=dev-secret-key-change-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||
|
||||
# Cookie 加密密钥
|
||||
COOKIE_ENCRYPTION_KEY=dev-cookie-encryption-key-32b
|
||||
|
||||
# 服务端口
|
||||
AUTH_SERVICE_PORT=8001
|
||||
API_SERVICE_PORT=8000
|
||||
|
||||
# 环境
|
||||
ENVIRONMENT=development
|
||||
```
|
||||
|
||||
#### 前端配置 (`frontend/.env`)
|
||||
|
||||
```env
|
||||
FLASK_ENV=development
|
||||
FLASK_DEBUG=True
|
||||
SECRET_KEY=dev-flask-secret-key-change-in-production
|
||||
|
||||
# 后端服务地址
|
||||
API_BASE_URL=http://localhost:8000
|
||||
AUTH_BASE_URL=http://localhost:8001
|
||||
|
||||
# Session 配置
|
||||
SESSION_TYPE=filesystem
|
||||
```
|
||||
|
||||
## 手动启动(高级)
|
||||
|
||||
如果需要单独启动某个服务:
|
||||
|
||||
### 1. 创建数据库
|
||||
|
||||
```bash
|
||||
python create_sqlite_db.py
|
||||
```
|
||||
|
||||
### 2. 安装后端依赖
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. 启动 Auth Service
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
venv\Scripts\activate
|
||||
set PYTHONPATH=%CD%
|
||||
python -m uvicorn auth_service.app.main:app --host 0.0.0.0 --port 8001 --reload
|
||||
```
|
||||
|
||||
### 4. 启动 API Service
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
venv\Scripts\activate
|
||||
set PYTHONPATH=%CD%
|
||||
python -m uvicorn api_service.app.main:app --host 0.0.0.0 --port 8000 --reload
|
||||
```
|
||||
|
||||
### 5. 启动 Frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
python -m venv venv
|
||||
venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
python app.py
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 启动失败,提示找不到模块
|
||||
|
||||
A: 确保设置了 PYTHONPATH:
|
||||
```bash
|
||||
set PYTHONPATH=%CD%
|
||||
```
|
||||
|
||||
### Q: 数据库连接失败
|
||||
|
||||
A: 检查 `backend/.env.local` 中的 `DATABASE_URL` 配置是否正确。
|
||||
|
||||
### Q: 端口被占用
|
||||
|
||||
A: 修改 `.env` 文件中的端口配置,或关闭占用端口的程序。
|
||||
|
||||
### Q: Redis 连接失败
|
||||
|
||||
A: 本地开发不需要 Redis。如果看到 Redis 警告,可以忽略,系统会自动使用内存存储。
|
||||
|
||||
### Q: 如何重置数据库
|
||||
|
||||
A: 删除 `weibo_hotsign.db` 文件,然后重新运行 `python create_sqlite_db.py`。
|
||||
|
||||
## 开发建议
|
||||
|
||||
### 1. 使用虚拟环境
|
||||
|
||||
每个服务都应该在独立的虚拟环境中运行,避免依赖冲突。
|
||||
|
||||
### 2. 查看日志
|
||||
|
||||
每个服务启动时会打开独立的命令行窗口,可以在窗口中查看实时日志。
|
||||
|
||||
### 3. 热重载
|
||||
|
||||
开发模式下,修改代码后服务会自动重启(`--reload` 参数)。
|
||||
|
||||
### 4. API 文档
|
||||
|
||||
- Auth Service: http://localhost:8001/docs
|
||||
- API Service: http://localhost:8000/docs
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
weibo-hotsign/
|
||||
├── backend/ # 后端服务
|
||||
│ ├── shared/ # 共享模块
|
||||
│ ├── auth_service/ # 认证服务
|
||||
│ ├── api_service/ # API 服务
|
||||
│ ├── task_scheduler/ # 任务调度(需要 Celery)
|
||||
│ ├── signin_executor/ # 签到执行(需要 Celery)
|
||||
│ └── requirements.txt # 后端依赖
|
||||
├── frontend/ # Flask 前端
|
||||
│ ├── templates/ # HTML 模板
|
||||
│ ├── app.py # Flask 应用
|
||||
│ └── requirements.txt # 前端依赖
|
||||
├── weibo_hotsign.db # SQLite 数据库(自动生成)
|
||||
├── create_sqlite_db.py # 数据库初始化脚本
|
||||
├── init-db-sqlite.sql # SQLite 建表脚本
|
||||
├── start_all.bat # 一键启动脚本
|
||||
├── stop_all.bat # 停止所有服务
|
||||
└── LOCAL_SETUP.md # 本文档
|
||||
```
|
||||
|
||||
## 生产环境部署
|
||||
|
||||
生产环境建议:
|
||||
- 使用 MySQL 数据库
|
||||
- 启用 Redis
|
||||
- 使用 Nginx 反向代理
|
||||
- 使用 Docker Compose 部署
|
||||
- 配置 HTTPS
|
||||
- 修改所有默认密钥
|
||||
|
||||
参考 `docker-compose.yml` 进行容器化部署。
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **后端**: Python 3.11 + FastAPI + SQLAlchemy (async)
|
||||
- **前端**: Flask + Jinja2 + 原生 CSS
|
||||
- **数据库**: SQLite (开发) / MySQL (生产)
|
||||
- **缓存**: 内存 (开发) / Redis (生产)
|
||||
- **任务队列**: Celery + Redis (可选)
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
214
WEIBO_OAUTH_SETUP.md
Normal file
214
WEIBO_OAUTH_SETUP.md
Normal file
@@ -0,0 +1,214 @@
|
||||
# 微博 OAuth2 扫码授权配置指南
|
||||
|
||||
## 功能说明
|
||||
|
||||
实现了微博 OAuth2 扫码授权功能,用户可以通过手机微博 APP 扫码快速添加账号,无需手动复制 Cookie。
|
||||
|
||||
## 配置步骤
|
||||
|
||||
### 1. 注册微博开放平台应用
|
||||
|
||||
1. 访问 [微博开放平台](https://open.weibo.com/)
|
||||
2. 登录你的微博账号
|
||||
3. 进入"微连接" > "网站接入"
|
||||
4. 创建新应用,填写应用信息:
|
||||
- 应用名称:Weibo-HotSign(或自定义)
|
||||
- 应用简介:微博自动签到系统
|
||||
- 应用类型:网站
|
||||
- 应用地址:http://localhost:5000(开发环境)
|
||||
|
||||
5. 提交审核(测试阶段可以使用未审核的应用)
|
||||
|
||||
### 2. 配置回调地址
|
||||
|
||||
在应用管理页面:
|
||||
1. 进入"应用信息" > "高级信息"
|
||||
2. 设置"授权回调页"为:`http://localhost:5000/auth/weibo/callback`
|
||||
3. 保存设置
|
||||
|
||||
### 3. 获取 APP KEY 和 APP SECRET
|
||||
|
||||
在应用管理页面:
|
||||
1. 进入"应用信息" > "基本信息"
|
||||
2. 复制 `App Key` 和 `App Secret`
|
||||
|
||||
### 4. 配置环境变量
|
||||
|
||||
编辑 `backend/.env` 和 `frontend/.env` 文件:
|
||||
|
||||
```env
|
||||
# 微博 OAuth2 配置
|
||||
WEIBO_APP_KEY=你的_App_Key
|
||||
WEIBO_APP_SECRET=你的_App_Secret
|
||||
WEIBO_REDIRECT_URI=http://localhost:5000/auth/weibo/callback
|
||||
```
|
||||
|
||||
### 5. 重启服务
|
||||
|
||||
```bash
|
||||
# 停止所有服务
|
||||
stop_all.bat
|
||||
|
||||
# 启动所有服务
|
||||
start_all.bat
|
||||
```
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 用户端操作
|
||||
|
||||
1. 登录系统后,进入"添加账号"页面
|
||||
2. 切换到"微博授权"标签页
|
||||
3. 点击"生成授权二维码"按钮
|
||||
4. 使用手机微博 APP 扫描二维码
|
||||
5. 在手机上点击"同意授权"
|
||||
6. 等待页面自动完成账号添加
|
||||
7. 自动跳转到 Dashboard
|
||||
|
||||
### 技术流程
|
||||
|
||||
1. **生成授权 URL**
|
||||
- 前端调用 `/auth/weibo/authorize` 接口
|
||||
- 后端生成包含 `state` 参数的授权 URL
|
||||
- 前端使用 QRCode.js 生成二维码
|
||||
|
||||
2. **用户扫码授权**
|
||||
- 用户用手机微博扫码
|
||||
- 跳转到微博授权页面(移动端适配)
|
||||
- 用户点击"同意授权"
|
||||
|
||||
3. **微博回调**
|
||||
- 微博跳转到 `/auth/weibo/callback?code=xxx&state=xxx`
|
||||
- 后端用 `code` 换取 `access_token`
|
||||
- 调用微博 API 获取用户信息
|
||||
- 更新授权状态为成功
|
||||
|
||||
4. **前端轮询**
|
||||
- 前端每 2 秒轮询 `/auth/weibo/check/<state>`
|
||||
- 检测到授权成功后,调用 `/api/weibo/add-account`
|
||||
- 自动添加账号到系统
|
||||
|
||||
5. **完成添加**
|
||||
- 账号添加成功
|
||||
- 跳转到 Dashboard
|
||||
|
||||
## API 接口说明
|
||||
|
||||
### 1. 生成授权 URL
|
||||
|
||||
```
|
||||
GET /auth/weibo/authorize
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"auth_url": "https://api.weibo.com/oauth2/authorize?...",
|
||||
"state": "random_state_string",
|
||||
"expires_in": 180
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 检查授权状态
|
||||
|
||||
```
|
||||
GET /auth/weibo/check/<state>
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"status": "pending|success|error|expired",
|
||||
"account_info": {...} // 仅在 success 时返回
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 微博回调
|
||||
|
||||
```
|
||||
GET /auth/weibo/callback?code=xxx&state=xxx
|
||||
```
|
||||
|
||||
返回 HTML 页面,显示授权结果
|
||||
|
||||
### 4. 添加账号
|
||||
|
||||
```
|
||||
POST /api/weibo/add-account
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"state": "state_string",
|
||||
"remark": "备注(可选)"
|
||||
}
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Account added successfully",
|
||||
"account": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## 安全说明
|
||||
|
||||
1. **State 参数**:用于防止 CSRF 攻击,每次授权生成唯一的 state
|
||||
2. **Session 存储**:授权状态临时存储在 session 中(生产环境建议使用 Redis)
|
||||
3. **Token 加密**:access_token 会被加密存储在数据库中
|
||||
4. **HTTPS**:生产环境必须使用 HTTPS
|
||||
|
||||
## 生产环境配置
|
||||
|
||||
### 1. 更新回调地址
|
||||
|
||||
```env
|
||||
WEIBO_REDIRECT_URI=https://yourdomain.com/auth/weibo/callback
|
||||
```
|
||||
|
||||
### 2. 在微博开放平台更新回调地址
|
||||
|
||||
进入应用管理 > 高级信息 > 授权回调页:
|
||||
```
|
||||
https://yourdomain.com/auth/weibo/callback
|
||||
```
|
||||
|
||||
### 3. 使用 Redis 存储授权状态
|
||||
|
||||
修改 `frontend/app.py`,将 session 存储改为 Redis 存储。
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题1:二维码生成失败
|
||||
|
||||
- 检查 `WEIBO_APP_KEY` 是否配置正确
|
||||
- 检查网络连接
|
||||
|
||||
### 问题2:扫码后提示"回调地址不匹配"
|
||||
|
||||
- 检查 `WEIBO_REDIRECT_URI` 是否与微博开放平台配置一致
|
||||
- 确保包含协议(http:// 或 https://)
|
||||
|
||||
### 问题3:授权成功但添加账号失败
|
||||
|
||||
- 检查后端 API 服务是否正常运行
|
||||
- 查看后端日志排查错误
|
||||
|
||||
### 问题4:二维码过期
|
||||
|
||||
- 默认有效期 3 分钟
|
||||
- 重新生成二维码即可
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 微博开放平台应用需要审核,测试阶段可以使用未审核的应用
|
||||
2. 未审核的应用只能授权给应用创建者和测试账号
|
||||
3. 建议使用小号或测试账号进行测试
|
||||
4. access_token 有效期通常为 30 天,过期后需要重新授权
|
||||
5. 生产环境必须使用 HTTPS
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [微博 OAuth2 文档](https://open.weibo.com/wiki/Oauth2)
|
||||
- [微博 API 文档](https://open.weibo.com/wiki/API)
|
||||
240
WEIBO_QRCODE_LOGIN.md
Normal file
240
WEIBO_QRCODE_LOGIN.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 微博扫码登录功能说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
实现了基于微博网页版扫码登录接口的账号添加功能,无需注册微博开放平台应用,直接使用微博官方的扫码登录 API。
|
||||
|
||||
## 实现原理
|
||||
|
||||
通过逆向微博网页版(weibo.com)的扫码登录流程,调用微博的内部 API:
|
||||
|
||||
1. **生成二维码**:调用 `https://login.sina.com.cn/sso/qrcode/image` 获取二维码图片
|
||||
2. **轮询状态**:调用 `https://login.sina.com.cn/sso/qrcode/check` 检查扫码状态
|
||||
3. **获取 Cookie**:扫码成功后通过跳转 URL 获取登录 Cookie
|
||||
4. **获取用户信息**:使用 Cookie 调用微博 API 获取用户 UID 和昵称
|
||||
5. **自动添加账号**:将获取的信息自动添加到系统
|
||||
|
||||
## 使用流程
|
||||
|
||||
### 用户操作
|
||||
|
||||
1. 登录系统后,进入"添加账号"页面
|
||||
2. 切换到"扫码添加"标签页
|
||||
3. 点击"生成二维码"按钮
|
||||
4. 使用手机微博 APP 扫描二维码
|
||||
5. 在手机上点击"确认登录"
|
||||
6. 等待页面自动完成账号添加
|
||||
7. 自动跳转到 Dashboard
|
||||
|
||||
### 技术流程
|
||||
|
||||
```
|
||||
用户点击生成二维码
|
||||
↓
|
||||
调用 /api/weibo/qrcode/generate
|
||||
↓
|
||||
请求微博 API 获取二维码
|
||||
↓
|
||||
显示二维码图片
|
||||
↓
|
||||
前端开始轮询 /api/weibo/qrcode/check/<qrid>
|
||||
↓
|
||||
后端轮询微博 API 检查状态
|
||||
↓
|
||||
状态变化:waiting → scanned → success
|
||||
↓
|
||||
获取跳转 URL 和 Cookie
|
||||
↓
|
||||
调用微博 API 获取用户信息
|
||||
↓
|
||||
前端调用 /api/weibo/qrcode/add-account
|
||||
↓
|
||||
添加账号到系统
|
||||
↓
|
||||
跳转到 Dashboard
|
||||
```
|
||||
|
||||
## API 接口
|
||||
|
||||
### 1. 生成二维码
|
||||
|
||||
```
|
||||
POST /api/weibo/qrcode/generate
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"qrid": "qr_id_string",
|
||||
"qr_image": "data:image/png;base64,...",
|
||||
"expires_in": 180
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 检查扫码状态
|
||||
|
||||
```
|
||||
GET /api/weibo/qrcode/check/<qrid>
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"status": "waiting|scanned|success|expired|cancelled|error",
|
||||
"weibo_uid": "123456789", // 仅在 success 时返回
|
||||
"screen_name": "用户昵称" // 仅在 success 时返回
|
||||
}
|
||||
```
|
||||
|
||||
状态说明:
|
||||
- `waiting`: 等待扫码
|
||||
- `scanned`: 已扫码,等待确认
|
||||
- `success`: 确认成功
|
||||
- `expired`: 二维码过期
|
||||
- `cancelled`: 取消登录
|
||||
- `error`: 发生错误
|
||||
|
||||
### 3. 添加账号
|
||||
|
||||
```
|
||||
POST /api/weibo/qrcode/add-account
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"qrid": "qr_id_string",
|
||||
"remark": "备注(可选)"
|
||||
}
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Account added successfully",
|
||||
"account": {...}
|
||||
}
|
||||
```
|
||||
|
||||
## 微博 API 说明
|
||||
|
||||
### 生成二维码接口
|
||||
|
||||
```
|
||||
GET https://login.sina.com.cn/sso/qrcode/image?entry=weibo&size=180&callback=STK_xxx
|
||||
```
|
||||
|
||||
返回 JSONP 格式:
|
||||
```javascript
|
||||
STK_xxx({
|
||||
"retcode": 20000000,
|
||||
"qrid": "xxx",
|
||||
"image": "data:image/png;base64,..."
|
||||
})
|
||||
```
|
||||
|
||||
### 检查扫码状态接口
|
||||
|
||||
```
|
||||
GET https://login.sina.com.cn/sso/qrcode/check?entry=weibo&qrid=xxx&callback=STK_xxx
|
||||
```
|
||||
|
||||
返回 JSONP 格式:
|
||||
```javascript
|
||||
STK_xxx({
|
||||
"retcode": 20000000, // 或其他状态码
|
||||
"alt": "跳转URL" // 仅在登录成功时返回
|
||||
})
|
||||
```
|
||||
|
||||
状态码说明:
|
||||
- `20000000`: 等待扫码
|
||||
- `50050001`: 已扫码,等待确认
|
||||
- `20000001`: 确认成功
|
||||
- `50050002`: 二维码过期
|
||||
- `50050004`: 取消授权
|
||||
|
||||
### 获取用户信息接口
|
||||
|
||||
```
|
||||
GET https://weibo.com/ajax/profile/info
|
||||
Cookie: xxx
|
||||
```
|
||||
|
||||
返回:
|
||||
```json
|
||||
{
|
||||
"ok": 1,
|
||||
"data": {
|
||||
"user": {
|
||||
"idstr": "123456789",
|
||||
"screen_name": "用户昵称",
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 优势
|
||||
|
||||
1. **无需注册应用**:不需要在微博开放平台注册应用
|
||||
2. **无需配置**:不需要配置 APP_KEY 和 APP_SECRET
|
||||
3. **真实 Cookie**:获取的是真实的登录 Cookie,可用于签到
|
||||
4. **用户体验好**:扫码即可完成,无需手动复制 Cookie
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **接口稳定性**:使用的是微博内部 API,可能会变化
|
||||
2. **Cookie 有效期**:获取的 Cookie 有效期通常较长,但仍可能过期
|
||||
3. **安全性**:Cookie 会被加密存储在数据库中
|
||||
4. **二维码有效期**:默认 3 分钟,过期后需重新生成
|
||||
5. **轮询频率**:前端每 2 秒轮询一次状态
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 问题1:生成二维码失败
|
||||
|
||||
- 检查网络连接
|
||||
- 检查微博 API 是否可访问
|
||||
- 查看后端日志
|
||||
|
||||
### 问题2:扫码后长时间无响应
|
||||
|
||||
- 检查轮询是否正常
|
||||
- 查看浏览器控制台是否有错误
|
||||
- 刷新页面重试
|
||||
|
||||
### 问题3:添加账号失败
|
||||
|
||||
- 检查后端 API 服务是否正常
|
||||
- 查看后端日志排查错误
|
||||
- 确认 Cookie 是否有效
|
||||
|
||||
### 问题4:二维码过期
|
||||
|
||||
- 默认有效期 3 分钟
|
||||
- 重新生成二维码即可
|
||||
|
||||
## 与 OAuth2 方案对比
|
||||
|
||||
| 特性 | 扫码登录(当前方案) | OAuth2 授权 |
|
||||
|------|---------------------|-------------|
|
||||
| 需要注册应用 | ❌ 不需要 | ✅ 需要 |
|
||||
| 配置复杂度 | 低 | 高 |
|
||||
| 获取的凭证 | 真实 Cookie | Access Token |
|
||||
| 接口稳定性 | 中(内部 API) | 高(官方 API) |
|
||||
| 用户体验 | 好 | 好 |
|
||||
| 适用场景 | 个人项目 | 商业项目 |
|
||||
|
||||
## 未来改进
|
||||
|
||||
1. 添加错误重试机制
|
||||
2. 优化轮询策略(WebSocket)
|
||||
3. 添加二维码刷新功能
|
||||
4. 支持多账号批量添加
|
||||
5. 添加扫码记录和统计
|
||||
|
||||
## 参考资料
|
||||
|
||||
- 微博网页版:https://weibo.com
|
||||
- 微博登录页面:https://login.sina.com.cn
|
||||
19
backend/.env
Normal file
19
backend/.env
Normal file
@@ -0,0 +1,19 @@
|
||||
# 本地开发环境配置 - 使用绝对路径
|
||||
|
||||
# 数据库配置 (SQLite - 绝对路径)
|
||||
# 请根据你的实际路径修改
|
||||
DATABASE_URL=sqlite+aiosqlite:///D:/code/weibo/weibo_hotsign.db
|
||||
|
||||
# Redis 配置 (可选,本地开发可以不启用)
|
||||
USE_REDIS=false
|
||||
|
||||
# JWT 配置
|
||||
JWT_SECRET_KEY=dev-secret-key-change-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_EXPIRATION_HOURS=24
|
||||
|
||||
# Cookie 加密密钥 (32字节)
|
||||
COOKIE_ENCRYPTION_KEY=dev-cookie-encryption-key-32b
|
||||
|
||||
# 环境
|
||||
ENVIRONMENT=development
|
||||
23
backend/.env.local
Normal file
23
backend/.env.local
Normal file
@@ -0,0 +1,23 @@
|
||||
# 本地开发环境配置
|
||||
|
||||
# 数据库配置 (SQLite - 相对于 backend 目录)
|
||||
DATABASE_URL=sqlite+aiosqlite:///../weibo_hotsign.db
|
||||
|
||||
# Redis 配置 (可选,本地开发可以不启用)
|
||||
USE_REDIS=false
|
||||
# REDIS_URL=redis://localhost:6379
|
||||
|
||||
# JWT 配置
|
||||
JWT_SECRET_KEY=dev-secret-key-change-in-production
|
||||
JWT_ALGORITHM=HS256
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES=60
|
||||
|
||||
# Cookie 加密密钥 (32字节)
|
||||
COOKIE_ENCRYPTION_KEY=dev-cookie-encryption-key-32b
|
||||
|
||||
# 服务端口
|
||||
AUTH_SERVICE_PORT=8001
|
||||
API_SERVICE_PORT=8000
|
||||
|
||||
# 环境
|
||||
ENVIRONMENT=development
|
||||
@@ -21,6 +21,9 @@ RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||
# --- API Gateway Service Stage ---
|
||||
FROM base AS api_gateway
|
||||
|
||||
# Copy shared module
|
||||
COPY shared/ ./shared/
|
||||
|
||||
# Copy application code
|
||||
COPY api_service/app/ ./app/
|
||||
|
||||
@@ -41,6 +44,9 @@ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
# --- Auth Service Stage ---
|
||||
FROM base AS auth_service
|
||||
|
||||
# Copy shared module
|
||||
COPY shared/ ./shared/
|
||||
|
||||
# Copy application code
|
||||
COPY auth_service/app/ ./app/
|
||||
|
||||
@@ -61,6 +67,9 @@ CMD ["python", "-m", "app.main"]
|
||||
# --- Task Scheduler Service Stage ---
|
||||
FROM base AS task_scheduler
|
||||
|
||||
# Copy shared module
|
||||
COPY shared/ ./shared/
|
||||
|
||||
# Copy application code
|
||||
COPY task_scheduler/app/ ./app/
|
||||
|
||||
@@ -74,6 +83,9 @@ CMD ["celery", "-A", "app.celery_app", "beat", "--loglevel=info"]
|
||||
# --- Sign-in Executor Service Stage ---
|
||||
FROM base AS signin_executor
|
||||
|
||||
# Copy shared module
|
||||
COPY shared/ ./shared/
|
||||
|
||||
# Copy application code
|
||||
COPY signin_executor/app/ ./app/
|
||||
|
||||
|
||||
Binary file not shown.
@@ -15,7 +15,7 @@ import logging
|
||||
from shared.models import get_db, User
|
||||
from auth_service.app.models.database import create_tables
|
||||
from auth_service.app.schemas.user import (
|
||||
UserCreate, UserLogin, UserResponse, Token, TokenData, RefreshTokenRequest,
|
||||
UserCreate, UserLogin, UserResponse, Token, TokenData, RefreshTokenRequest, AuthResponse,
|
||||
)
|
||||
from auth_service.app.services.auth_service import AuthService
|
||||
from auth_service.app.utils.security import (
|
||||
@@ -92,7 +92,9 @@ async def get_current_user(
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""Initialize database tables on startup"""
|
||||
await create_tables()
|
||||
# 表已通过 create_sqlite_db.py 创建,无需重复创建
|
||||
# await create_tables()
|
||||
pass
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
@@ -106,10 +108,10 @@ async def root():
|
||||
async def health_check():
|
||||
return {"status": "healthy"}
|
||||
|
||||
@app.post("/auth/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
@app.post("/auth/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
Register a new user account
|
||||
Register a new user account and return tokens
|
||||
"""
|
||||
auth_service = AuthService(db)
|
||||
|
||||
@@ -131,17 +133,28 @@ async def register_user(user_data: UserCreate, db: AsyncSession = Depends(get_db
|
||||
# Create new user
|
||||
try:
|
||||
user = await auth_service.create_user(user_data)
|
||||
return UserResponse.from_orm(user)
|
||||
|
||||
# Create tokens for auto-login
|
||||
access_token = create_access_token(data={"sub": str(user.id), "username": user.username})
|
||||
refresh_token = await create_refresh_token(str(user.id))
|
||||
|
||||
return AuthResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
token_type="bearer",
|
||||
expires_in=3600,
|
||||
user=UserResponse.from_orm(user)
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to create user: {str(e)}"
|
||||
)
|
||||
|
||||
@app.post("/auth/login", response_model=Token)
|
||||
@app.post("/auth/login", response_model=AuthResponse)
|
||||
async def login_user(login_data: UserLogin, db: AsyncSession = Depends(get_db)):
|
||||
"""
|
||||
Authenticate user and return JWT token
|
||||
Authenticate user and return JWT token with user info
|
||||
"""
|
||||
auth_service = AuthService(db)
|
||||
|
||||
@@ -173,11 +186,12 @@ async def login_user(login_data: UserLogin, db: AsyncSession = Depends(get_db)):
|
||||
# Create refresh token (stored in Redis)
|
||||
refresh_token = await create_refresh_token(str(user.id))
|
||||
|
||||
return Token(
|
||||
return AuthResponse(
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
token_type="bearer",
|
||||
expires_in=3600 # 1 hour
|
||||
expires_in=3600,
|
||||
user=UserResponse.from_orm(user)
|
||||
)
|
||||
|
||||
@app.post("/auth/refresh", response_model=Token)
|
||||
|
||||
Binary file not shown.
@@ -10,6 +10,10 @@ __all__ = ["Base", "get_db", "engine", "AsyncSessionLocal", "User"]
|
||||
|
||||
|
||||
async def create_tables():
|
||||
"""Create all tables in the database."""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
"""Create all tables in the database if they don't exist."""
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
except Exception as e:
|
||||
# 表已存在或其他错误,忽略
|
||||
print(f"Warning: Could not create tables: {e}")
|
||||
|
||||
Binary file not shown.
@@ -45,6 +45,15 @@ class Token(BaseModel):
|
||||
expires_in: int = Field(..., description="Access token expiration time in seconds")
|
||||
|
||||
|
||||
class AuthResponse(BaseModel):
|
||||
"""Schema for authentication response with user info"""
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_in: int
|
||||
user: UserResponse
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
"""Schema for token refresh request"""
|
||||
refresh_token: str = Field(..., description="The refresh token to exchange")
|
||||
|
||||
Binary file not shown.
@@ -7,9 +7,7 @@ import hashlib
|
||||
import jwt
|
||||
import secrets
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional
|
||||
|
||||
import redis.asyncio as aioredis
|
||||
from typing import Optional, Dict
|
||||
|
||||
from shared.config import shared_settings
|
||||
|
||||
@@ -17,17 +15,28 @@ from shared.config import shared_settings
|
||||
BCRYPT_ROUNDS = 12
|
||||
REFRESH_TOKEN_TTL = 7 * 24 * 3600 # 7 days in seconds
|
||||
|
||||
# Lazy-initialised async Redis client
|
||||
_redis_client: Optional[aioredis.Redis] = None
|
||||
# Lazy-initialised async Redis client (可选)
|
||||
_redis_client: Optional[object] = None
|
||||
|
||||
# 内存存储(本地开发用,不使用 Redis 时)
|
||||
_memory_store: Dict[str, tuple[str, datetime]] = {}
|
||||
|
||||
|
||||
async def get_redis() -> aioredis.Redis:
|
||||
"""Return a shared async Redis connection."""
|
||||
async def get_redis():
|
||||
"""Return a shared async Redis connection if enabled."""
|
||||
if not shared_settings.USE_REDIS:
|
||||
return None
|
||||
|
||||
global _redis_client
|
||||
if _redis_client is None:
|
||||
_redis_client = aioredis.from_url(
|
||||
shared_settings.REDIS_URL, decode_responses=True
|
||||
)
|
||||
try:
|
||||
import redis.asyncio as aioredis
|
||||
_redis_client = aioredis.from_url(
|
||||
shared_settings.REDIS_URL, decode_responses=True
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[警告] Redis 连接失败: {e},将使用内存存储")
|
||||
return None
|
||||
return _redis_client
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
@@ -118,31 +127,68 @@ def _hash_token(token: str) -> str:
|
||||
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def _clean_expired_tokens():
|
||||
"""清理过期的内存 token"""
|
||||
now = datetime.utcnow()
|
||||
expired_keys = [k for k, (_, exp) in _memory_store.items() if exp < now]
|
||||
for k in expired_keys:
|
||||
del _memory_store[k]
|
||||
|
||||
|
||||
async def create_refresh_token(user_id: str) -> str:
|
||||
"""
|
||||
Generate a cryptographically random refresh token, store its hash in Redis
|
||||
with a 7-day TTL, and return the raw token string.
|
||||
(or memory if Redis disabled) with a 7-day TTL, and return the raw token string.
|
||||
"""
|
||||
token = secrets.token_urlsafe(48)
|
||||
token_hash = _hash_token(token)
|
||||
|
||||
r = await get_redis()
|
||||
await r.setex(f"refresh_token:{token_hash}", REFRESH_TOKEN_TTL, user_id)
|
||||
if r:
|
||||
# 使用 Redis
|
||||
await r.setex(f"refresh_token:{token_hash}", REFRESH_TOKEN_TTL, user_id)
|
||||
else:
|
||||
# 使用内存存储
|
||||
_clean_expired_tokens()
|
||||
expire_at = datetime.utcnow() + timedelta(seconds=REFRESH_TOKEN_TTL)
|
||||
_memory_store[f"refresh_token:{token_hash}"] = (user_id, expire_at)
|
||||
|
||||
return token
|
||||
|
||||
|
||||
async def verify_refresh_token(token: str) -> Optional[str]:
|
||||
"""
|
||||
Verify a refresh token by looking up its hash in Redis.
|
||||
Verify a refresh token by looking up its hash in Redis or memory.
|
||||
Returns the associated user_id if valid, None otherwise.
|
||||
"""
|
||||
token_hash = _hash_token(token)
|
||||
key = f"refresh_token:{token_hash}"
|
||||
|
||||
r = await get_redis()
|
||||
user_id = await r.get(f"refresh_token:{token_hash}")
|
||||
return user_id
|
||||
if r:
|
||||
# 使用 Redis
|
||||
user_id = await r.get(key)
|
||||
return user_id
|
||||
else:
|
||||
# 使用内存存储
|
||||
_clean_expired_tokens()
|
||||
if key in _memory_store:
|
||||
user_id, expire_at = _memory_store[key]
|
||||
if expire_at > datetime.utcnow():
|
||||
return user_id
|
||||
return None
|
||||
|
||||
|
||||
async def revoke_refresh_token(token: str) -> None:
|
||||
"""Delete a refresh token from Redis (used during rotation)."""
|
||||
"""Delete a refresh token from Redis or memory (used during rotation)."""
|
||||
token_hash = _hash_token(token)
|
||||
key = f"refresh_token:{token_hash}"
|
||||
|
||||
r = await get_redis()
|
||||
await r.delete(f"refresh_token:{token_hash}")
|
||||
if r:
|
||||
# 使用 Redis
|
||||
await r.delete(key)
|
||||
else:
|
||||
# 使用内存存储
|
||||
if key in _memory_store:
|
||||
del _memory_store[key]
|
||||
|
||||
@@ -12,10 +12,11 @@ redis==5.0.1
|
||||
sqlalchemy==2.0.23
|
||||
aiomysql==0.2.0
|
||||
PyMySQL==1.1.0
|
||||
aiosqlite==0.19.0
|
||||
|
||||
# Configuration, Validation, and Serialization
|
||||
pydantic-settings==2.0.3
|
||||
pydantic==2.5.0
|
||||
pydantic[email]==2.5.0
|
||||
python-multipart==0.0.6
|
||||
|
||||
# Security
|
||||
@@ -31,3 +32,8 @@ croniter==2.0.1
|
||||
|
||||
# Logging and Monitoring
|
||||
structlog==23.2.0
|
||||
|
||||
# Testing
|
||||
pytest==7.4.3
|
||||
pytest-asyncio==0.21.1
|
||||
hypothesis==6.92.1
|
||||
|
||||
Binary file not shown.
@@ -4,16 +4,18 @@ Loads settings from environment variables using pydantic-settings.
|
||||
"""
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class SharedSettings(BaseSettings):
|
||||
"""Shared settings across all backend services."""
|
||||
|
||||
# Database
|
||||
DATABASE_URL: str = "mysql+aiomysql://root:password@localhost/weibo_hotsign"
|
||||
|
||||
# Redis
|
||||
REDIS_URL: str = "redis://localhost:6379/0"
|
||||
# Database (默认使用 SQLite,生产环境可配置为 MySQL)
|
||||
DATABASE_URL: str = "sqlite+aiosqlite:///../weibo_hotsign.db"
|
||||
#mysql+aiomysql://root:password@localhost/weibo_hotsign
|
||||
# Redis (可选,本地开发可以不使用)
|
||||
REDIS_URL: Optional[str] = None
|
||||
USE_REDIS: bool = False # 是否启用 Redis
|
||||
|
||||
# JWT
|
||||
JWT_SECRET_KEY: str = "change-me-in-production"
|
||||
@@ -22,6 +24,9 @@ class SharedSettings(BaseSettings):
|
||||
|
||||
# Cookie encryption
|
||||
COOKIE_ENCRYPTION_KEY: str = "change-me-in-production"
|
||||
|
||||
# Environment
|
||||
ENVIRONMENT: str = "development"
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
162
backend/signin_executor/app/services/antibot.py
Normal file
162
backend/signin_executor/app/services/antibot.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""
|
||||
Anti-bot protection module
|
||||
Implements various techniques to avoid detection by anti-crawling systems
|
||||
"""
|
||||
|
||||
import random
|
||||
import logging
|
||||
from typing import Optional, Dict, Any, List
|
||||
import httpx
|
||||
|
||||
from app.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Predefined User-Agent list for rotation
|
||||
USER_AGENTS = [
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:120.0) Gecko/20100101 Firefox/120.0",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0 Safari/537.36",
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",
|
||||
]
|
||||
|
||||
|
||||
class AntiBotProtection:
|
||||
"""Anti-bot protection service"""
|
||||
|
||||
def __init__(self):
|
||||
self.proxy_pool_url = settings.PROXY_POOL_URL
|
||||
self.random_delay_min = settings.RANDOM_DELAY_MIN
|
||||
self.random_delay_max = settings.RANDOM_DELAY_MAX
|
||||
|
||||
def get_random_delay(self) -> float:
|
||||
"""
|
||||
Generate random delay within configured range.
|
||||
Returns delay in seconds.
|
||||
|
||||
Validates: Requirements 7.1
|
||||
"""
|
||||
delay = random.uniform(self.random_delay_min, self.random_delay_max)
|
||||
logger.debug(f"Generated random delay: {delay:.2f}s")
|
||||
return delay
|
||||
|
||||
def get_random_user_agent(self) -> str:
|
||||
"""
|
||||
Select random User-Agent from predefined list.
|
||||
Returns User-Agent string.
|
||||
|
||||
Validates: Requirements 7.2
|
||||
"""
|
||||
user_agent = random.choice(USER_AGENTS)
|
||||
logger.debug(f"Selected User-Agent: {user_agent[:50]}...")
|
||||
return user_agent
|
||||
|
||||
async def get_proxy(self) -> Optional[Dict[str, str]]:
|
||||
"""
|
||||
Get proxy from proxy pool service.
|
||||
Returns proxy dict or None if unavailable.
|
||||
Falls back to direct connection if proxy pool is unavailable.
|
||||
|
||||
Validates: Requirements 7.3, 7.4
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get(f"{self.proxy_pool_url}/get")
|
||||
|
||||
if response.status_code == 200:
|
||||
proxy_info = response.json()
|
||||
proxy_url = proxy_info.get("proxy")
|
||||
|
||||
if proxy_url:
|
||||
proxy_dict = {
|
||||
"http://": f"http://{proxy_url}",
|
||||
"https://": f"https://{proxy_url}"
|
||||
}
|
||||
logger.info(f"Obtained proxy: {proxy_url}")
|
||||
return proxy_dict
|
||||
else:
|
||||
logger.warning("Proxy pool returned empty proxy")
|
||||
return None
|
||||
else:
|
||||
logger.warning(f"Proxy pool returned status {response.status_code}")
|
||||
return None
|
||||
|
||||
except httpx.RequestError as e:
|
||||
logger.warning(f"Proxy pool service unavailable: {e}, falling back to direct connection")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting proxy: {e}")
|
||||
return None
|
||||
|
||||
def build_headers(self, user_agent: Optional[str] = None) -> Dict[str, str]:
|
||||
"""
|
||||
Build HTTP headers with random User-Agent and common headers.
|
||||
|
||||
Args:
|
||||
user_agent: Optional custom User-Agent, otherwise random one is selected
|
||||
|
||||
Returns:
|
||||
Dict of HTTP headers
|
||||
"""
|
||||
if user_agent is None:
|
||||
user_agent = self.get_random_user_agent()
|
||||
|
||||
headers = {
|
||||
"User-Agent": user_agent,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://weibo.com/",
|
||||
"Sec-Fetch-Dest": "empty",
|
||||
"Sec-Fetch-Mode": "cors",
|
||||
"Sec-Fetch-Site": "same-origin",
|
||||
}
|
||||
|
||||
return headers
|
||||
|
||||
def get_fingerprint_data(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate browser fingerprint data for simulation.
|
||||
|
||||
Returns:
|
||||
Dict containing fingerprint information
|
||||
"""
|
||||
screen_resolutions = [
|
||||
"1920x1080", "1366x768", "1440x900", "1536x864",
|
||||
"1280x720", "2560x1440", "3840x2160"
|
||||
]
|
||||
|
||||
timezones = [
|
||||
"Asia/Shanghai", "Asia/Beijing", "Asia/Hong_Kong",
|
||||
"Asia/Taipei", "Asia/Singapore"
|
||||
]
|
||||
|
||||
languages = [
|
||||
"zh-CN", "zh-CN,zh;q=0.9", "zh-CN,zh;q=0.9,en;q=0.8",
|
||||
"zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||
]
|
||||
|
||||
fingerprint = {
|
||||
"screen_resolution": random.choice(screen_resolutions),
|
||||
"timezone": random.choice(timezones),
|
||||
"language": random.choice(languages),
|
||||
"color_depth": random.choice([24, 32]),
|
||||
"platform": random.choice(["Win32", "MacIntel", "Linux x86_64"]),
|
||||
"hardware_concurrency": random.choice([4, 8, 12, 16]),
|
||||
"device_memory": random.choice([4, 8, 16, 32]),
|
||||
}
|
||||
|
||||
logger.debug(f"Generated fingerprint: {fingerprint}")
|
||||
return fingerprint
|
||||
|
||||
|
||||
# Global instance
|
||||
antibot = AntiBotProtection()
|
||||
@@ -3,6 +3,8 @@ Core sign-in business logic service
|
||||
Handles Weibo super topic sign-in operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import httpx
|
||||
import logging
|
||||
@@ -10,10 +12,21 @@ import random
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, Any, List, Optional
|
||||
from uuid import UUID
|
||||
from sqlalchemy import select, update
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
|
||||
from shared.models.base import AsyncSessionLocal
|
||||
from shared.models.account import Account
|
||||
from shared.models.signin_log import SigninLog
|
||||
from shared.crypto import decrypt_cookie, derive_key
|
||||
from shared.config import shared_settings
|
||||
|
||||
from app.config import settings
|
||||
from app.models.signin_models import SignInRequest, SignInResult, TaskStatus, WeiboAccount, WeiboSuperTopic, AntiBotConfig
|
||||
from app.services.weibo_client import WeiboClient
|
||||
from app.services.antibot import antibot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -72,6 +85,15 @@ class SignInService:
|
||||
|
||||
# Step 2: Setup session with proxy and fingerprint
|
||||
task_status.current_step = "setup_session"
|
||||
|
||||
# Verify cookies before proceeding
|
||||
cookies_valid = await self.weibo_client.verify_cookies(account)
|
||||
if not cookies_valid:
|
||||
logger.error(f"Cookies invalid for account {account_id}")
|
||||
# Update account status to invalid_cookie
|
||||
await self._update_account_status(account_id, "invalid_cookie")
|
||||
raise Exception("Cookie validation failed - cookies are invalid or expired")
|
||||
|
||||
await self._apply_anti_bot_protection()
|
||||
|
||||
task_status.steps_completed.append("setup_session")
|
||||
@@ -156,20 +178,31 @@ class SignInService:
|
||||
self.active_tasks[task_id].updated_at = datetime.now()
|
||||
|
||||
async def _get_account_info(self, account_id: str) -> Optional[WeiboAccount]:
|
||||
"""Get Weibo account information from database"""
|
||||
"""
|
||||
Get Weibo account information from database (replaces mock data).
|
||||
Returns account dict or None if not found.
|
||||
"""
|
||||
try:
|
||||
# Mock implementation - in real system, query database
|
||||
# For demo, return mock account
|
||||
return WeiboAccount(
|
||||
id=UUID(account_id),
|
||||
user_id=UUID("12345678-1234-5678-9012-123456789012"),
|
||||
weibo_user_id="1234567890",
|
||||
remark="Demo Account",
|
||||
encrypted_cookies="mock_encrypted_cookies",
|
||||
iv="mock_iv_16_bytes",
|
||||
status="active",
|
||||
last_checked_at=datetime.now() - timedelta(hours=1)
|
||||
)
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = select(Account).where(Account.id == account_id)
|
||||
result = await session.execute(stmt)
|
||||
account = result.scalar_one_or_none()
|
||||
|
||||
if not account:
|
||||
logger.error(f"Account {account_id} not found in database")
|
||||
return None
|
||||
|
||||
# Convert ORM model to Pydantic model
|
||||
return WeiboAccount(
|
||||
id=UUID(account.id),
|
||||
user_id=UUID(account.user_id),
|
||||
weibo_user_id=account.weibo_user_id,
|
||||
remark=account.remark or "",
|
||||
encrypted_cookies=account.encrypted_cookies,
|
||||
iv=account.iv,
|
||||
status=account.status,
|
||||
last_checked_at=account.last_checked_at or datetime.now()
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching account {account_id}: {e}")
|
||||
return None
|
||||
@@ -177,18 +210,24 @@ class SignInService:
|
||||
async def _apply_anti_bot_protection(self):
|
||||
"""Apply anti-bot protection measures"""
|
||||
# Random delay to mimic human behavior
|
||||
delay = random.uniform(
|
||||
self.antibot_config.random_delay_min,
|
||||
self.antibot_config.random_delay_max
|
||||
)
|
||||
delay = antibot.get_random_delay()
|
||||
logger.debug(f"Applying random delay: {delay:.2f}s")
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
# Additional anti-bot measures would go here:
|
||||
# - User agent rotation
|
||||
# - Proxy selection
|
||||
# - Browser fingerprint simulation
|
||||
# - Request header randomization
|
||||
# Get random User-Agent
|
||||
user_agent = antibot.get_random_user_agent()
|
||||
logger.debug(f"Using User-Agent: {user_agent[:50]}...")
|
||||
|
||||
# Try to get proxy (falls back to direct connection if unavailable)
|
||||
proxy = await antibot.get_proxy()
|
||||
if proxy:
|
||||
logger.info(f"Using proxy for requests")
|
||||
else:
|
||||
logger.info("Using direct connection (no proxy available)")
|
||||
|
||||
# Get browser fingerprint
|
||||
fingerprint = antibot.get_fingerprint_data()
|
||||
logger.debug(f"Browser fingerprint: {fingerprint}")
|
||||
|
||||
async def _get_super_topics_list(self, account: WeiboAccount) -> List[WeiboSuperTopic]:
|
||||
"""Get list of super topics for account"""
|
||||
@@ -244,10 +283,18 @@ class SignInService:
|
||||
|
||||
if topic.is_signed:
|
||||
already_signed.append(topic.title)
|
||||
# Write log for already signed
|
||||
await self._write_signin_log(
|
||||
account_id=str(account.id),
|
||||
topic_title=topic.title,
|
||||
status="failed_already_signed",
|
||||
reward_info=None,
|
||||
error_message="Already signed today"
|
||||
)
|
||||
continue
|
||||
|
||||
# Execute signin for this topic
|
||||
success = await self.weibo_client.sign_super_topic(
|
||||
success, reward_info, error_msg = await self.weibo_client.sign_super_topic(
|
||||
account=account,
|
||||
topic=topic,
|
||||
task_id=task_id
|
||||
@@ -256,16 +303,88 @@ class SignInService:
|
||||
if success:
|
||||
signed.append(topic.title)
|
||||
logger.info(f"✅ Successfully signed topic: {topic.title}")
|
||||
|
||||
# Write success log
|
||||
await self._write_signin_log(
|
||||
account_id=str(account.id),
|
||||
topic_title=topic.title,
|
||||
status="success",
|
||||
reward_info=reward_info,
|
||||
error_message=None
|
||||
)
|
||||
else:
|
||||
errors.append(f"Failed to sign topic: {topic.title}")
|
||||
|
||||
# Write failure log
|
||||
await self._write_signin_log(
|
||||
account_id=str(account.id),
|
||||
topic_title=topic.title,
|
||||
status="failed_network",
|
||||
reward_info=None,
|
||||
error_message=error_msg
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error signing topic {topic.title}: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
errors.append(error_msg)
|
||||
|
||||
# Write error log
|
||||
await self._write_signin_log(
|
||||
account_id=str(account.id),
|
||||
topic_title=topic.title,
|
||||
status="failed_network",
|
||||
reward_info=None,
|
||||
error_message=str(e)
|
||||
)
|
||||
|
||||
return {
|
||||
"signed": signed,
|
||||
"already_signed": already_signed,
|
||||
"errors": errors
|
||||
}
|
||||
|
||||
async def _write_signin_log(
|
||||
self,
|
||||
account_id: str,
|
||||
topic_title: str,
|
||||
status: str,
|
||||
reward_info: Optional[Dict[str, Any]],
|
||||
error_message: Optional[str]
|
||||
):
|
||||
"""
|
||||
Write signin result to signin_logs table.
|
||||
Replaces mock implementation with real database write.
|
||||
"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
log = SigninLog(
|
||||
account_id=account_id,
|
||||
topic_title=topic_title,
|
||||
status=status,
|
||||
reward_info=reward_info,
|
||||
error_message=error_message,
|
||||
)
|
||||
session.add(log)
|
||||
await session.commit()
|
||||
logger.debug(f"Wrote signin log for account {account_id}, topic {topic_title}, status {status}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to write signin log: {e}")
|
||||
|
||||
async def _update_account_status(self, account_id: str, status: str):
|
||||
"""
|
||||
Update account status in database.
|
||||
Used when cookie is invalid or account is banned.
|
||||
"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = (
|
||||
update(Account)
|
||||
.where(Account.id == account_id)
|
||||
.values(status=status, last_checked_at=datetime.now())
|
||||
)
|
||||
await session.execute(stmt)
|
||||
await session.commit()
|
||||
logger.info(f"Updated account {account_id} status to {status}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update account status: {e}")
|
||||
|
||||
@@ -3,14 +3,23 @@ Weibo API Client
|
||||
Handles all interactions with Weibo.com, including login, sign-in, and data fetching
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import httpx
|
||||
import asyncio
|
||||
import logging
|
||||
import random
|
||||
from typing import Dict, Any, Optional, List
|
||||
from typing import Dict, Any, Optional, List, Tuple
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
|
||||
from shared.crypto import decrypt_cookie, derive_key
|
||||
from shared.config import shared_settings
|
||||
|
||||
from app.config import settings
|
||||
from app.models.signin_models import WeiboAccount, WeiboSuperTopic
|
||||
from app.services.antibot import antibot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -18,21 +27,35 @@ class WeiboClient:
|
||||
"""Client for interacting with Weibo API"""
|
||||
|
||||
def __init__(self):
|
||||
self.base_headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Accept-Language": "en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7",
|
||||
"Connection": "keep-alive",
|
||||
"Referer": "https://weibo.com/"
|
||||
}
|
||||
# Use antibot module for dynamic headers
|
||||
self.base_headers = antibot.build_headers()
|
||||
|
||||
async def verify_cookies(self, account: WeiboAccount) -> bool:
|
||||
"""Verify if Weibo cookies are still valid"""
|
||||
try:
|
||||
# Decrypt cookies
|
||||
cookies = self._decrypt_cookies(account.encrypted_cookies, account.iv)
|
||||
# Decrypt cookies using shared crypto module
|
||||
cookies_dict = self._decrypt_cookies(account.encrypted_cookies, account.iv)
|
||||
|
||||
async with httpx.AsyncClient(cookies=cookies, headers=self.base_headers) as client:
|
||||
if not cookies_dict:
|
||||
logger.error(f"Failed to decrypt cookies for account {account.weibo_user_id}")
|
||||
return False
|
||||
|
||||
# Get proxy (with fallback to direct connection)
|
||||
proxy = await antibot.get_proxy()
|
||||
|
||||
# Use dynamic headers with random User-Agent
|
||||
headers = antibot.build_headers()
|
||||
|
||||
# Add random delay before request
|
||||
delay = antibot.get_random_delay()
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
async with httpx.AsyncClient(
|
||||
cookies=cookies_dict,
|
||||
headers=headers,
|
||||
proxies=proxy,
|
||||
timeout=10.0
|
||||
) as client:
|
||||
response = await client.get("https://weibo.com/mygroups", follow_redirects=True)
|
||||
|
||||
if response.status_code == 200 and "我的首页" in response.text:
|
||||
@@ -62,13 +85,34 @@ class WeiboClient:
|
||||
logger.error(f"Error fetching super topics: {e}")
|
||||
return []
|
||||
|
||||
async def sign_super_topic(self, account: WeiboAccount, topic: WeiboSuperTopic, task_id: str) -> bool:
|
||||
async def sign_super_topic(
|
||||
self,
|
||||
account: WeiboAccount,
|
||||
topic: WeiboSuperTopic,
|
||||
task_id: str
|
||||
) -> Tuple[bool, Optional[Dict[str, Any]], Optional[str]]:
|
||||
"""
|
||||
Execute sign-in for a single super topic
|
||||
Returns: (success, reward_info, error_message)
|
||||
"""
|
||||
try:
|
||||
# Decrypt cookies
|
||||
cookies = self._decrypt_cookies(account.encrypted_cookies, account.iv)
|
||||
# Decrypt cookies using shared crypto module
|
||||
cookies_dict = self._decrypt_cookies(account.encrypted_cookies, account.iv)
|
||||
|
||||
if not cookies_dict:
|
||||
error_msg = "Failed to decrypt cookies"
|
||||
logger.error(error_msg)
|
||||
return False, None, error_msg
|
||||
|
||||
# Get proxy (with fallback to direct connection)
|
||||
proxy = await antibot.get_proxy()
|
||||
|
||||
# Use dynamic headers with random User-Agent
|
||||
headers = antibot.build_headers()
|
||||
|
||||
# Add random delay before request (anti-bot protection)
|
||||
delay = antibot.get_random_delay()
|
||||
await asyncio.sleep(delay)
|
||||
|
||||
# Prepare request payload
|
||||
payload = {
|
||||
@@ -78,7 +122,7 @@ class WeiboClient:
|
||||
"location": "page_100808_super_index",
|
||||
"refer_flag": "100808_-_1",
|
||||
"refer_lflag": "100808_-_1",
|
||||
"ua": self.base_headers["User-Agent"],
|
||||
"ua": headers["User-Agent"],
|
||||
"is_new": "1",
|
||||
"is_from_ad": "0",
|
||||
"ext": "mi_898_1_0_0"
|
||||
@@ -104,33 +148,44 @@ class WeiboClient:
|
||||
|
||||
if response_data.get("code") == "100000":
|
||||
logger.info(f"Successfully signed topic: {topic.title}")
|
||||
return True
|
||||
reward_info = response_data.get("data", {}).get("reward", {})
|
||||
return True, reward_info, None
|
||||
elif response_data.get("code") == "382004":
|
||||
logger.info(f"Topic {topic.title} already signed today")
|
||||
return True # Treat as success
|
||||
return True, None, "Already signed"
|
||||
else:
|
||||
logger.error(f"Failed to sign topic {topic.title}: {response_data.get('msg')}")
|
||||
return False
|
||||
error_msg = response_data.get("msg", "Unknown error")
|
||||
logger.error(f"Failed to sign topic {topic.title}: {error_msg}")
|
||||
return False, None, error_msg
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Exception signing topic {topic.title}: {e}")
|
||||
return False
|
||||
error_msg = f"Exception signing topic {topic.title}: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, None, error_msg
|
||||
|
||||
def _decrypt_cookies(self, encrypted_cookies: str, iv: str) -> Dict[str, str]:
|
||||
"""
|
||||
Decrypt cookies using AES-256-GCM
|
||||
In a real system, this would use a proper crypto library
|
||||
Decrypt cookies using AES-256-GCM from shared crypto module.
|
||||
Returns dict of cookie key-value pairs.
|
||||
"""
|
||||
try:
|
||||
# Mock implementation - return dummy cookies
|
||||
return {
|
||||
"SUB": "_2A25z...",
|
||||
"SUBP": "0033Wr...",
|
||||
"ALF": "16...",
|
||||
"SSOLoginState": "16...",
|
||||
"SCF": "...",
|
||||
"UN": "testuser"
|
||||
}
|
||||
# Derive encryption key from shared settings
|
||||
key = derive_key(shared_settings.COOKIE_ENCRYPTION_KEY)
|
||||
|
||||
# Decrypt using shared crypto module
|
||||
plaintext = decrypt_cookie(encrypted_cookies, iv, key)
|
||||
|
||||
# Parse cookie string into dict
|
||||
# Expected format: "key1=value1; key2=value2; ..."
|
||||
cookies_dict = {}
|
||||
for cookie_pair in plaintext.split(";"):
|
||||
cookie_pair = cookie_pair.strip()
|
||||
if "=" in cookie_pair:
|
||||
key, value = cookie_pair.split("=", 1)
|
||||
cookies_dict[key.strip()] = value.strip()
|
||||
|
||||
return cookies_dict
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to decrypt cookies: {e}")
|
||||
return {}
|
||||
|
||||
@@ -4,22 +4,35 @@ Celery Beat configuration for scheduled sign-in tasks
|
||||
"""
|
||||
|
||||
import os
|
||||
from celery import Celery
|
||||
from celery.schedules import crontab
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy import select
|
||||
import sys
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
from datetime import datetime
|
||||
|
||||
from ..config import settings
|
||||
from celery import Celery
|
||||
from celery.schedules import crontab
|
||||
from croniter import croniter
|
||||
from sqlalchemy import select
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../.."))
|
||||
|
||||
from shared.models.base import AsyncSessionLocal
|
||||
from shared.models.task import Task
|
||||
from shared.models.account import Account
|
||||
from shared.config import shared_settings
|
||||
|
||||
from .config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Create Celery app
|
||||
celery_app = Celery(
|
||||
"weibo_hot_sign_scheduler",
|
||||
broker=settings.CELERY_BROKER_URL,
|
||||
backend=settings.CELERY_RESULT_BACKEND,
|
||||
include=["app.tasks.signin_tasks"]
|
||||
include=["task_scheduler.app.tasks.signin_tasks"]
|
||||
)
|
||||
|
||||
# Celery configuration
|
||||
@@ -33,65 +46,168 @@ celery_app.conf.update(
|
||||
beat_max_loop_interval=5,
|
||||
)
|
||||
|
||||
# Database configuration for task scheduler
|
||||
engine = create_async_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
pool_size=10,
|
||||
max_overflow=20
|
||||
)
|
||||
|
||||
AsyncSessionLocal = sessionmaker(
|
||||
engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False
|
||||
)
|
||||
|
||||
async def get_db():
|
||||
"""Get database session for task scheduler"""
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
class TaskSchedulerService:
|
||||
"""Service to manage scheduled tasks from database"""
|
||||
|
||||
def __init__(self):
|
||||
self.engine = engine
|
||||
self.scheduled_tasks: Dict[str, dict] = {}
|
||||
|
||||
async def load_scheduled_tasks(self):
|
||||
"""Load enabled tasks from database and schedule them"""
|
||||
from app.models.task_models import Task
|
||||
|
||||
async def load_scheduled_tasks(self) -> List[Task]:
|
||||
"""
|
||||
Load enabled tasks from database and register them to Celery Beat.
|
||||
Returns list of loaded tasks.
|
||||
"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Query all enabled tasks
|
||||
stmt = select(Task).where(Task.is_enabled == True)
|
||||
# Query all enabled tasks with their accounts
|
||||
stmt = (
|
||||
select(Task, Account)
|
||||
.join(Account, Task.account_id == Account.id)
|
||||
.where(Task.is_enabled == True)
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
tasks = result.scalars().all()
|
||||
task_account_pairs = result.all()
|
||||
|
||||
print(f"📅 Loaded {len(tasks)} enabled tasks from database")
|
||||
logger.info(f"📅 Loaded {len(task_account_pairs)} enabled tasks from database")
|
||||
|
||||
# Here we would dynamically add tasks to Celery Beat
|
||||
# For now, we'll use static configuration in celery_config.py
|
||||
return tasks
|
||||
# Register tasks to Celery Beat dynamically
|
||||
beat_schedule = {}
|
||||
for task, account in task_account_pairs:
|
||||
try:
|
||||
# Validate cron expression
|
||||
if not croniter.is_valid(task.cron_expression):
|
||||
logger.warning(f"Invalid cron expression for task {task.id}: {task.cron_expression}")
|
||||
continue
|
||||
|
||||
# Create schedule entry
|
||||
schedule_name = f"task_{task.id}"
|
||||
beat_schedule[schedule_name] = {
|
||||
"task": "task_scheduler.app.tasks.signin_tasks.execute_signin_task",
|
||||
"schedule": self._parse_cron_to_celery(task.cron_expression),
|
||||
"args": (task.id, task.account_id, task.cron_expression),
|
||||
}
|
||||
|
||||
self.scheduled_tasks[task.id] = {
|
||||
"task_id": task.id,
|
||||
"account_id": task.account_id,
|
||||
"cron_expression": task.cron_expression,
|
||||
"account_status": account.status,
|
||||
}
|
||||
|
||||
logger.info(f"✅ Registered task {task.id} for account {account.weibo_user_id} with cron: {task.cron_expression}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to register task {task.id}: {e}")
|
||||
continue
|
||||
|
||||
# Update Celery Beat schedule
|
||||
celery_app.conf.beat_schedule.update(beat_schedule)
|
||||
|
||||
return [task for task, _ in task_account_pairs]
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error loading tasks from database: {e}")
|
||||
logger.error(f"❌ Error loading tasks from database: {e}")
|
||||
return []
|
||||
|
||||
def _parse_cron_to_celery(self, cron_expression: str) -> crontab:
|
||||
"""
|
||||
Parse cron expression string to Celery crontab schedule.
|
||||
Format: minute hour day month day_of_week
|
||||
"""
|
||||
parts = cron_expression.split()
|
||||
if len(parts) != 5:
|
||||
raise ValueError(f"Invalid cron expression: {cron_expression}")
|
||||
|
||||
return crontab(
|
||||
minute=parts[0],
|
||||
hour=parts[1],
|
||||
day_of_month=parts[2],
|
||||
month_of_year=parts[3],
|
||||
day_of_week=parts[4],
|
||||
)
|
||||
|
||||
async def add_task(self, task_id: str, account_id: str, cron_expression: str):
|
||||
"""Dynamically add a new task to the schedule"""
|
||||
try:
|
||||
if not croniter.is_valid(cron_expression):
|
||||
raise ValueError(f"Invalid cron expression: {cron_expression}")
|
||||
|
||||
schedule_name = f"task_{task_id}"
|
||||
celery_app.conf.beat_schedule[schedule_name] = {
|
||||
"task": "task_scheduler.app.tasks.signin_tasks.execute_signin_task",
|
||||
"schedule": self._parse_cron_to_celery(cron_expression),
|
||||
"args": (task_id, account_id, cron_expression),
|
||||
}
|
||||
|
||||
self.scheduled_tasks[task_id] = {
|
||||
"task_id": task_id,
|
||||
"account_id": account_id,
|
||||
"cron_expression": cron_expression,
|
||||
}
|
||||
|
||||
logger.info(f"✅ Added task {task_id} to schedule")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to add task {task_id}: {e}")
|
||||
raise
|
||||
|
||||
async def remove_task(self, task_id: str):
|
||||
"""Dynamically remove a task from the schedule"""
|
||||
try:
|
||||
schedule_name = f"task_{task_id}"
|
||||
if schedule_name in celery_app.conf.beat_schedule:
|
||||
del celery_app.conf.beat_schedule[schedule_name]
|
||||
logger.info(f"✅ Removed task {task_id} from schedule")
|
||||
|
||||
if task_id in self.scheduled_tasks:
|
||||
del self.scheduled_tasks[task_id]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to remove task {task_id}: {e}")
|
||||
raise
|
||||
|
||||
async def update_task(self, task_id: str, is_enabled: bool, cron_expression: str = None):
|
||||
"""Update an existing task in the schedule"""
|
||||
try:
|
||||
if is_enabled:
|
||||
# Re-add or update the task
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = select(Task).where(Task.id == task_id)
|
||||
result = await session.execute(stmt)
|
||||
task = result.scalar_one_or_none()
|
||||
|
||||
if task:
|
||||
await self.add_task(
|
||||
task_id,
|
||||
task.account_id,
|
||||
cron_expression or task.cron_expression
|
||||
)
|
||||
else:
|
||||
# Remove the task
|
||||
await self.remove_task(task_id)
|
||||
|
||||
logger.info(f"✅ Updated task {task_id}, enabled={is_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update task {task_id}: {e}")
|
||||
raise
|
||||
|
||||
|
||||
# Global scheduler service instance
|
||||
scheduler_service = TaskSchedulerService()
|
||||
|
||||
|
||||
# Synchronous wrapper for async function
|
||||
def sync_load_tasks():
|
||||
"""Synchronous wrapper to load tasks"""
|
||||
service = TaskSchedulerService()
|
||||
"""Synchronous wrapper to load tasks on startup"""
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
return loop.run_until_complete(service.load_scheduled_tasks())
|
||||
return loop.run_until_complete(scheduler_service.load_scheduled_tasks())
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
# Import task modules to register them
|
||||
from app.tasks import signin_tasks
|
||||
from .tasks import signin_tasks
|
||||
|
||||
@@ -9,15 +9,9 @@ from typing import List
|
||||
class Settings(BaseSettings):
|
||||
"""Task Scheduler settings"""
|
||||
|
||||
# Database settings
|
||||
DATABASE_URL: str = os.getenv(
|
||||
"DATABASE_URL",
|
||||
"mysql+aiomysql://weibo:123456789@43.134.68.207/weibo"
|
||||
)
|
||||
|
||||
# Celery settings
|
||||
CELERY_BROKER_URL: str = os.getenv("CELERY_BROKER_URL", "redis://redis:6379/0")
|
||||
CELERY_RESULT_BACKEND: str = os.getenv("CELERY_RESULT_BACKEND", "redis://redis:6379/0")
|
||||
CELERY_BROKER_URL: str = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0")
|
||||
CELERY_RESULT_BACKEND: str = os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/0")
|
||||
|
||||
# Task execution settings
|
||||
MAX_CONCURRENT_TASKS: int = int(os.getenv("MAX_CONCURRENT_TASKS", "10"))
|
||||
@@ -40,6 +34,9 @@ class Settings(BaseSettings):
|
||||
PROXY_POOL_URL: str = os.getenv("PROXY_POOL_URL", "http://proxy-pool:8080")
|
||||
BROWSER_AUTOMATION_URL: str = os.getenv("BROWSER_AUTOMATION_URL", "http://browser-automation:3001")
|
||||
|
||||
# Redis pub/sub settings for task updates
|
||||
REDIS_TASK_CHANNEL: str = os.getenv("REDIS_TASK_CHANNEL", "task_updates")
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
env_file = ".env"
|
||||
|
||||
@@ -3,29 +3,143 @@ Weibo-HotSign Sign-in Task Definitions
|
||||
Celery tasks for scheduled sign-in operations
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import asyncio
|
||||
import httpx
|
||||
import json
|
||||
import logging
|
||||
import redis
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
from celery import current_task
|
||||
from sqlalchemy import select
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
|
||||
from shared.models.base import AsyncSessionLocal
|
||||
from shared.models.task import Task
|
||||
from shared.models.account import Account
|
||||
from shared.config import shared_settings
|
||||
|
||||
from ..celery_app import celery_app
|
||||
from ..config import settings
|
||||
|
||||
# Configure logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Redis client for distributed locks (可选)
|
||||
_redis_client = None
|
||||
|
||||
def get_redis_client():
|
||||
"""获取 Redis 客户端,如果未启用则返回 None"""
|
||||
global _redis_client
|
||||
if not shared_settings.USE_REDIS:
|
||||
return None
|
||||
|
||||
if _redis_client is None:
|
||||
try:
|
||||
_redis_client = redis.from_url(shared_settings.REDIS_URL, decode_responses=True)
|
||||
except Exception as e:
|
||||
logger.warning(f"Redis 连接失败: {e},分布式锁将被禁用")
|
||||
return None
|
||||
return _redis_client
|
||||
|
||||
|
||||
# 内存锁(当 Redis 不可用时)
|
||||
_memory_locks = {}
|
||||
|
||||
|
||||
class DistributedLock:
|
||||
"""分布式锁(支持 Redis 或内存模式)"""
|
||||
|
||||
def __init__(self, lock_key: str, timeout: int = 300):
|
||||
"""
|
||||
Initialize distributed lock
|
||||
|
||||
Args:
|
||||
lock_key: Unique key for the lock
|
||||
timeout: Lock timeout in seconds (default 5 minutes)
|
||||
"""
|
||||
self.lock_key = f"lock:{lock_key}"
|
||||
self.timeout = timeout
|
||||
self.acquired = False
|
||||
self.redis_client = get_redis_client()
|
||||
|
||||
def acquire(self) -> bool:
|
||||
"""
|
||||
Acquire the lock using Redis SETNX or memory dict
|
||||
Returns True if lock acquired, False otherwise
|
||||
"""
|
||||
try:
|
||||
if self.redis_client:
|
||||
# 使用 Redis
|
||||
result = self.redis_client.set(self.lock_key, "1", nx=True, ex=self.timeout)
|
||||
self.acquired = bool(result)
|
||||
else:
|
||||
# 使用内存锁(本地开发)
|
||||
if self.lock_key not in _memory_locks:
|
||||
_memory_locks[self.lock_key] = True
|
||||
self.acquired = True
|
||||
else:
|
||||
self.acquired = False
|
||||
|
||||
return self.acquired
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to acquire lock {self.lock_key}: {e}")
|
||||
return False
|
||||
|
||||
def release(self):
|
||||
"""Release the lock"""
|
||||
if self.acquired:
|
||||
try:
|
||||
if self.redis_client:
|
||||
# 使用 Redis
|
||||
self.redis_client.delete(self.lock_key)
|
||||
else:
|
||||
# 使用内存锁
|
||||
if self.lock_key in _memory_locks:
|
||||
del _memory_locks[self.lock_key]
|
||||
|
||||
self.acquired = False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to release lock {self.lock_key}: {e}")
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry"""
|
||||
if not self.acquire():
|
||||
raise Exception(f"Failed to acquire lock: {self.lock_key}")
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit"""
|
||||
self.release()
|
||||
|
||||
@celery_app.task(bind=True, max_retries=3, default_retry_delay=60)
|
||||
def execute_signin_task(self, task_id: str, account_id: str, cron_expression: str):
|
||||
"""
|
||||
Execute scheduled sign-in task for a specific account
|
||||
This task is triggered by Celery Beat based on cron schedule
|
||||
Uses distributed lock to prevent duplicate execution
|
||||
"""
|
||||
logger.info(f"🎯 Starting sign-in task {task_id} for account {account_id}")
|
||||
lock_key = f"signin_task:{task_id}:{account_id}"
|
||||
lock = DistributedLock(lock_key, timeout=300)
|
||||
|
||||
# Try to acquire lock
|
||||
if not lock.acquire():
|
||||
logger.warning(f"⚠️ Task {task_id} for account {account_id} is already running, skipping")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": "Task already running (distributed lock)",
|
||||
"account_id": account_id,
|
||||
"task_id": task_id
|
||||
}
|
||||
|
||||
try:
|
||||
logger.info(f"🎯 Starting sign-in task {task_id} for account {account_id}")
|
||||
|
||||
# Update task status
|
||||
current_task.update_state(
|
||||
state="PROGRESS",
|
||||
@@ -37,6 +151,20 @@ def execute_signin_task(self, task_id: str, account_id: str, cron_expression: st
|
||||
}
|
||||
)
|
||||
|
||||
# Get account info from database
|
||||
account_info = _get_account_from_db(account_id)
|
||||
if not account_info:
|
||||
raise Exception(f"Account {account_id} not found in database")
|
||||
|
||||
# Check if account is active
|
||||
if account_info["status"] not in ["pending", "active"]:
|
||||
logger.warning(f"Account {account_id} status is {account_info['status']}, skipping sign-in")
|
||||
return {
|
||||
"status": "skipped",
|
||||
"reason": f"Account status is {account_info['status']}",
|
||||
"account_id": account_id
|
||||
}
|
||||
|
||||
# Call signin executor service
|
||||
result = _call_signin_executor(account_id, task_id)
|
||||
|
||||
@@ -58,10 +186,15 @@ def execute_signin_task(self, task_id: str, account_id: str, cron_expression: st
|
||||
except Exception as exc:
|
||||
logger.error(f"❌ Sign-in task {task_id} failed for account {account_id}: {exc}")
|
||||
|
||||
# Retry logic
|
||||
# Retry logic with exponential backoff
|
||||
if self.request.retries < settings.MAX_RETRY_ATTEMPTS:
|
||||
logger.info(f"🔄 Retrying task {task_id} (attempt {self.request.retries + 1})")
|
||||
raise self.retry(exc=exc, countdown=settings.RETRY_DELAY_SECONDS)
|
||||
retry_delay = settings.RETRY_DELAY_SECONDS * (2 ** self.request.retries)
|
||||
logger.info(f"🔄 Retrying task {task_id} (attempt {self.request.retries + 1}) in {retry_delay}s")
|
||||
|
||||
# Release lock before retry
|
||||
lock.release()
|
||||
|
||||
raise self.retry(exc=exc, countdown=retry_delay)
|
||||
|
||||
# Final failure
|
||||
current_task.update_state(
|
||||
@@ -75,74 +208,166 @@ def execute_signin_task(self, task_id: str, account_id: str, cron_expression: st
|
||||
}
|
||||
)
|
||||
raise exc
|
||||
|
||||
finally:
|
||||
# Always release lock
|
||||
lock.release()
|
||||
|
||||
|
||||
def _get_account_from_db(account_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Query account information from database (replaces mock data).
|
||||
Returns account dict or None if not found.
|
||||
"""
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
return loop.run_until_complete(_async_get_account(account_id))
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
async def _async_get_account(account_id: str) -> Optional[Dict[str, Any]]:
|
||||
"""Async helper to query account from database"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
stmt = select(Account).where(Account.id == account_id)
|
||||
result = await session.execute(stmt)
|
||||
account = result.scalar_one_or_none()
|
||||
|
||||
if not account:
|
||||
return None
|
||||
|
||||
return {
|
||||
"id": account.id,
|
||||
"user_id": account.user_id,
|
||||
"weibo_user_id": account.weibo_user_id,
|
||||
"remark": account.remark,
|
||||
"status": account.status,
|
||||
"encrypted_cookies": account.encrypted_cookies,
|
||||
"iv": account.iv,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error querying account {account_id}: {e}")
|
||||
return None
|
||||
|
||||
@celery_app.task
|
||||
def schedule_daily_signin():
|
||||
"""
|
||||
Daily sign-in task - example of scheduled task
|
||||
Can be configured in Celery Beat schedule
|
||||
Daily sign-in task - queries database for enabled tasks
|
||||
"""
|
||||
logger.info("📅 Executing daily sign-in schedule")
|
||||
|
||||
# This would typically query database for accounts that need daily sign-in
|
||||
# For demo purposes, we'll simulate processing multiple accounts
|
||||
|
||||
accounts = ["account_1", "account_2", "account_3"] # Mock account IDs
|
||||
results = []
|
||||
|
||||
for account_id in accounts:
|
||||
try:
|
||||
# Submit individual sign-in task for each account
|
||||
task = execute_signin_task.delay(
|
||||
task_id=f"daily_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||||
account_id=account_id,
|
||||
cron_expression="0 8 * * *" # Daily at 8 AM
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
return loop.run_until_complete(_async_schedule_daily_signin())
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
async def _async_schedule_daily_signin():
|
||||
"""Async helper to query and schedule tasks"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Query all enabled tasks
|
||||
stmt = (
|
||||
select(Task, Account)
|
||||
.join(Account, Task.account_id == Account.id)
|
||||
.where(Task.is_enabled == True)
|
||||
.where(Account.status.in_(["pending", "active"]))
|
||||
)
|
||||
results.append({
|
||||
"account_id": account_id,
|
||||
"task_id": task.id,
|
||||
"status": "submitted"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to submit task for account {account_id}: {e}")
|
||||
results.append({
|
||||
"account_id": account_id,
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
return {
|
||||
"scheduled_date": datetime.now().isoformat(),
|
||||
"accounts_processed": len(accounts),
|
||||
"results": results
|
||||
}
|
||||
result = await session.execute(stmt)
|
||||
task_account_pairs = result.all()
|
||||
|
||||
results = []
|
||||
for task, account in task_account_pairs:
|
||||
try:
|
||||
# Submit individual sign-in task for each account
|
||||
celery_task = execute_signin_task.delay(
|
||||
task_id=task.id,
|
||||
account_id=account.id,
|
||||
cron_expression=task.cron_expression
|
||||
)
|
||||
results.append({
|
||||
"account_id": account.id,
|
||||
"task_id": celery_task.id,
|
||||
"status": "submitted"
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to submit task for account {account.id}: {e}")
|
||||
results.append({
|
||||
"account_id": account.id,
|
||||
"status": "failed",
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
return {
|
||||
"scheduled_date": datetime.now().isoformat(),
|
||||
"accounts_processed": len(task_account_pairs),
|
||||
"results": results
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error in daily signin schedule: {e}")
|
||||
raise
|
||||
|
||||
@celery_app.task
|
||||
def process_pending_tasks():
|
||||
"""
|
||||
Process pending sign-in tasks from database
|
||||
This can be called manually or via external trigger
|
||||
Queries database for enabled tasks and submits them for execution
|
||||
"""
|
||||
logger.info("🔄 Processing pending sign-in tasks from database")
|
||||
|
||||
# In real implementation, this would:
|
||||
# 1. Query database for tasks that need to be executed
|
||||
# 2. Check if they're due based on cron expressions
|
||||
# 3. Submit them to Celery for execution
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
# Mock implementation - query enabled tasks
|
||||
result = {
|
||||
"processed_at": datetime.now().isoformat(),
|
||||
"tasks_found": 5, # Mock number
|
||||
"tasks_submitted": 3,
|
||||
"tasks_skipped": 2,
|
||||
"status": "completed"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Processed pending tasks: {result}")
|
||||
return result
|
||||
|
||||
return loop.run_until_complete(_async_process_pending_tasks())
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
||||
async def _async_process_pending_tasks():
|
||||
"""Async helper to process pending tasks"""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
# Query enabled tasks that are due for execution
|
||||
stmt = (
|
||||
select(Task, Account)
|
||||
.join(Account, Task.account_id == Account.id)
|
||||
.where(Task.is_enabled == True)
|
||||
.where(Account.status.in_(["pending", "active"]))
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
task_account_pairs = result.all()
|
||||
|
||||
tasks_submitted = 0
|
||||
tasks_skipped = 0
|
||||
|
||||
for task, account in task_account_pairs:
|
||||
try:
|
||||
# Submit task for execution
|
||||
execute_signin_task.delay(
|
||||
task_id=task.id,
|
||||
account_id=account.id,
|
||||
cron_expression=task.cron_expression
|
||||
)
|
||||
tasks_submitted += 1
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to submit task {task.id}: {e}")
|
||||
tasks_skipped += 1
|
||||
|
||||
result = {
|
||||
"processed_at": datetime.now().isoformat(),
|
||||
"tasks_found": len(task_account_pairs),
|
||||
"tasks_submitted": tasks_submitted,
|
||||
"tasks_skipped": tasks_skipped,
|
||||
"status": "completed"
|
||||
}
|
||||
|
||||
logger.info(f"✅ Processed pending tasks: {result}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to process pending tasks: {e}")
|
||||
raise
|
||||
|
||||
164
backend/venv/Include/site/python3.11/greenlet/greenlet.h
Normal file
164
backend/venv/Include/site/python3.11/greenlet/greenlet.h
Normal file
@@ -0,0 +1,164 @@
|
||||
/* -*- indent-tabs-mode: nil; tab-width: 4; -*- */
|
||||
|
||||
/* Greenlet object interface */
|
||||
|
||||
#ifndef Py_GREENLETOBJECT_H
|
||||
#define Py_GREENLETOBJECT_H
|
||||
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* This is deprecated and undocumented. It does not change. */
|
||||
#define GREENLET_VERSION "1.0.0"
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
#define implementation_ptr_t void*
|
||||
#endif
|
||||
|
||||
typedef struct _greenlet {
|
||||
PyObject_HEAD
|
||||
PyObject* weakreflist;
|
||||
PyObject* dict;
|
||||
implementation_ptr_t pimpl;
|
||||
} PyGreenlet;
|
||||
|
||||
#define PyGreenlet_Check(op) (op && PyObject_TypeCheck(op, &PyGreenlet_Type))
|
||||
|
||||
|
||||
/* C API functions */
|
||||
|
||||
/* Total number of symbols that are exported */
|
||||
#define PyGreenlet_API_pointers 12
|
||||
|
||||
#define PyGreenlet_Type_NUM 0
|
||||
#define PyExc_GreenletError_NUM 1
|
||||
#define PyExc_GreenletExit_NUM 2
|
||||
|
||||
#define PyGreenlet_New_NUM 3
|
||||
#define PyGreenlet_GetCurrent_NUM 4
|
||||
#define PyGreenlet_Throw_NUM 5
|
||||
#define PyGreenlet_Switch_NUM 6
|
||||
#define PyGreenlet_SetParent_NUM 7
|
||||
|
||||
#define PyGreenlet_MAIN_NUM 8
|
||||
#define PyGreenlet_STARTED_NUM 9
|
||||
#define PyGreenlet_ACTIVE_NUM 10
|
||||
#define PyGreenlet_GET_PARENT_NUM 11
|
||||
|
||||
#ifndef GREENLET_MODULE
|
||||
/* This section is used by modules that uses the greenlet C API */
|
||||
static void** _PyGreenlet_API = NULL;
|
||||
|
||||
# define PyGreenlet_Type \
|
||||
(*(PyTypeObject*)_PyGreenlet_API[PyGreenlet_Type_NUM])
|
||||
|
||||
# define PyExc_GreenletError \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletError_NUM])
|
||||
|
||||
# define PyExc_GreenletExit \
|
||||
((PyObject*)_PyGreenlet_API[PyExc_GreenletExit_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_New(PyObject *args)
|
||||
*
|
||||
* greenlet.greenlet(run, parent=None)
|
||||
*/
|
||||
# define PyGreenlet_New \
|
||||
(*(PyGreenlet * (*)(PyObject * run, PyGreenlet * parent)) \
|
||||
_PyGreenlet_API[PyGreenlet_New_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetCurrent(void)
|
||||
*
|
||||
* greenlet.getcurrent()
|
||||
*/
|
||||
# define PyGreenlet_GetCurrent \
|
||||
(*(PyGreenlet * (*)(void)) _PyGreenlet_API[PyGreenlet_GetCurrent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Throw(
|
||||
* PyGreenlet *greenlet,
|
||||
* PyObject *typ,
|
||||
* PyObject *val,
|
||||
* PyObject *tb)
|
||||
*
|
||||
* g.throw(...)
|
||||
*/
|
||||
# define PyGreenlet_Throw \
|
||||
(*(PyObject * (*)(PyGreenlet * self, \
|
||||
PyObject * typ, \
|
||||
PyObject * val, \
|
||||
PyObject * tb)) \
|
||||
_PyGreenlet_API[PyGreenlet_Throw_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_Switch(PyGreenlet *greenlet, PyObject *args)
|
||||
*
|
||||
* g.switch(*args, **kwargs)
|
||||
*/
|
||||
# define PyGreenlet_Switch \
|
||||
(*(PyObject * \
|
||||
(*)(PyGreenlet * greenlet, PyObject * args, PyObject * kwargs)) \
|
||||
_PyGreenlet_API[PyGreenlet_Switch_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_SetParent(PyObject *greenlet, PyObject *new_parent)
|
||||
*
|
||||
* g.parent = new_parent
|
||||
*/
|
||||
# define PyGreenlet_SetParent \
|
||||
(*(int (*)(PyGreenlet * greenlet, PyGreenlet * nparent)) \
|
||||
_PyGreenlet_API[PyGreenlet_SetParent_NUM])
|
||||
|
||||
/*
|
||||
* PyGreenlet_GetParent(PyObject* greenlet)
|
||||
*
|
||||
* return greenlet.parent;
|
||||
*
|
||||
* This could return NULL even if there is no exception active.
|
||||
* If it does not return NULL, you are responsible for decrementing the
|
||||
* reference count.
|
||||
*/
|
||||
# define PyGreenlet_GetParent \
|
||||
(*(PyGreenlet* (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_GET_PARENT_NUM])
|
||||
|
||||
/*
|
||||
* deprecated, undocumented alias.
|
||||
*/
|
||||
# define PyGreenlet_GET_PARENT PyGreenlet_GetParent
|
||||
|
||||
# define PyGreenlet_MAIN \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_MAIN_NUM])
|
||||
|
||||
# define PyGreenlet_STARTED \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_STARTED_NUM])
|
||||
|
||||
# define PyGreenlet_ACTIVE \
|
||||
(*(int (*)(PyGreenlet*)) \
|
||||
_PyGreenlet_API[PyGreenlet_ACTIVE_NUM])
|
||||
|
||||
|
||||
|
||||
|
||||
/* Macro that imports greenlet and initializes C API */
|
||||
/* NOTE: This has actually moved to ``greenlet._greenlet._C_API``, but we
|
||||
keep the older definition to be sure older code that might have a copy of
|
||||
the header still works. */
|
||||
# define PyGreenlet_Import() \
|
||||
{ \
|
||||
_PyGreenlet_API = (void**)PyCapsule_Import("greenlet._C_API", 0); \
|
||||
}
|
||||
|
||||
#endif /* GREENLET_MODULE */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* !Py_GREENLETOBJECT_H */
|
||||
Binary file not shown.
234
backend/venv/Lib/site-packages/Crypto/Cipher/AES.py
Normal file
234
backend/venv/Lib/site-packages/Crypto/Cipher/AES.py
Normal file
@@ -0,0 +1,234 @@
|
||||
# -*- 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`)
|
||||
|
||||
|
||||
_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)
|
||||
156
backend/venv/Lib/site-packages/Crypto/Cipher/AES.pyi
Normal file
156
backend/venv/Lib/site-packages/Crypto/Cipher/AES.pyi
Normal file
@@ -0,0 +1,156 @@
|
||||
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]
|
||||
175
backend/venv/Lib/site-packages/Crypto/Cipher/ARC2.py
Normal file
175
backend/venv/Lib/site-packages/Crypto/Cipher/ARC2.py
Normal file
@@ -0,0 +1,175 @@
|
||||
# -*- 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)
|
||||
35
backend/venv/Lib/site-packages/Crypto/Cipher/ARC2.pyi
Normal file
35
backend/venv/Lib/site-packages/Crypto/Cipher/ARC2.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
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]
|
||||
136
backend/venv/Lib/site-packages/Crypto/Cipher/ARC4.py
Normal file
136
backend/venv/Lib/site-packages/Crypto/Cipher/ARC4.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# -*- 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)
|
||||
16
backend/venv/Lib/site-packages/Crypto/Cipher/ARC4.pyi
Normal file
16
backend/venv/Lib/site-packages/Crypto/Cipher/ARC4.pyi
Normal file
@@ -0,0 +1,16 @@
|
||||
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]
|
||||
159
backend/venv/Lib/site-packages/Crypto/Cipher/Blowfish.py
Normal file
159
backend/venv/Lib/site-packages/Crypto/Cipher/Blowfish.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- 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)
|
||||
35
backend/venv/Lib/site-packages/Crypto/Cipher/Blowfish.pyi
Normal file
35
backend/venv/Lib/site-packages/Crypto/Cipher/Blowfish.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
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]
|
||||
159
backend/venv/Lib/site-packages/Crypto/Cipher/CAST.py
Normal file
159
backend/venv/Lib/site-packages/Crypto/Cipher/CAST.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# -*- 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)
|
||||
35
backend/venv/Lib/site-packages/Crypto/Cipher/CAST.pyi
Normal file
35
backend/venv/Lib/site-packages/Crypto/Cipher/CAST.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
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]
|
||||
287
backend/venv/Lib/site-packages/Crypto/Cipher/ChaCha20.py
Normal file
287
backend/venv/Lib/site-packages/Crypto/Cipher/ChaCha20.py
Normal file
@@ -0,0 +1,287 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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.
|
||||
|
||||
Args:
|
||||
position (integer):
|
||||
The absolute position within the key stream, in bytes.
|
||||
"""
|
||||
|
||||
position, offset = divmod(position, 64)
|
||||
block_low = position & 0xFFFFFFFF
|
||||
block_high = position >> 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
|
||||
25
backend/venv/Lib/site-packages/Crypto/Cipher/ChaCha20.pyi
Normal file
25
backend/venv/Lib/site-packages/Crypto/Cipher/ChaCha20.pyi
Normal file
@@ -0,0 +1,25 @@
|
||||
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
|
||||
@@ -0,0 +1,336 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
|
||||
self._len_ct += len(plaintext)
|
||||
|
||||
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
|
||||
@@ -0,0 +1,28 @@
|
||||
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
|
||||
158
backend/venv/Lib/site-packages/Crypto/Cipher/DES.py
Normal file
158
backend/venv/Lib/site-packages/Crypto/Cipher/DES.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# -*- 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
|
||||
35
backend/venv/Lib/site-packages/Crypto/Cipher/DES.pyi
Normal file
35
backend/venv/Lib/site-packages/Crypto/Cipher/DES.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
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
|
||||
187
backend/venv/Lib/site-packages/Crypto/Cipher/DES3.py
Normal file
187
backend/venv/Lib/site-packages/Crypto/Cipher/DES3.py
Normal file
@@ -0,0 +1,187 @@
|
||||
# -*- 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)
|
||||
37
backend/venv/Lib/site-packages/Crypto/Cipher/DES3.pyi
Normal file
37
backend/venv/Lib/site-packages/Crypto/Cipher/DES3.pyi
Normal file
@@ -0,0 +1,37 @@
|
||||
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]
|
||||
239
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_OAEP.py
Normal file
239
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_OAEP.py
Normal file
@@ -0,0 +1,239 @@
|
||||
# -*- 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 bord, _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
|
||||
|
||||
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)
|
||||
m_int = self._key._decrypt(ct_int)
|
||||
# Complete step 2c (I2OSP)
|
||||
em = long_to_bytes(m_int, k)
|
||||
# Step 3a
|
||||
lHash = self._hashObj.new(self._label).digest()
|
||||
# Step 3b
|
||||
y = em[0]
|
||||
# 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 3g
|
||||
one_pos = hLen + db[hLen:].find(b'\x01')
|
||||
lHash1 = db[:hLen]
|
||||
invalid = bord(y) | int(one_pos < hLen)
|
||||
hash_compare = strxor(lHash1, lHash)
|
||||
for x in hash_compare:
|
||||
invalid |= bord(x)
|
||||
for x in db[hLen:one_pos]:
|
||||
invalid |= bord(x)
|
||||
if invalid != 0:
|
||||
raise ValueError("Incorrect decryption.")
|
||||
# Step 4
|
||||
return db[one_pos + 1:]
|
||||
|
||||
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)
|
||||
|
||||
35
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_OAEP.pyi
Normal file
35
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_OAEP.pyi
Normal file
@@ -0,0 +1,35 @@
|
||||
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: ...
|
||||
217
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_v1_5.py
Normal file
217
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_v1_5.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# -*- 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 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);
|
||||
""")
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
assert(len(ps) == k - mLen - 3)
|
||||
# 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)
|
||||
m_int = self._key._decrypt(ct_int)
|
||||
|
||||
# Complete step 2c (I2OSP)
|
||||
em = long_to_bytes(m_int, k)
|
||||
|
||||
# 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)
|
||||
20
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_v1_5.pyi
Normal file
20
backend/venv/Lib/site-packages/Crypto/Cipher/PKCS1_v1_5.pyi
Normal file
@@ -0,0 +1,20 @@
|
||||
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: ...
|
||||
167
backend/venv/Lib/site-packages/Crypto/Cipher/Salsa20.py
Normal file
167
backend/venv/Lib/site-packages/Crypto/Cipher/Salsa20.py
Normal file
@@ -0,0 +1,167 @@
|
||||
# -*- 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)
|
||||
|
||||
26
backend/venv/Lib/site-packages/Crypto/Cipher/Salsa20.pyi
Normal file
26
backend/venv/Lib/site-packages/Crypto/Cipher/Salsa20.pyi
Normal file
@@ -0,0 +1,26 @@
|
||||
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]
|
||||
|
||||
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_ARC4.pyd
Normal file
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_ARC4.pyd
Normal file
Binary file not shown.
131
backend/venv/Lib/site-packages/Crypto/Cipher/_EKSBlowfish.py
Normal file
131
backend/venv/Lib/site-packages/Crypto/Cipher/_EKSBlowfish.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
@@ -0,0 +1,15 @@
|
||||
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]
|
||||
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_Salsa20.pyd
Normal file
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_Salsa20.pyd
Normal file
Binary file not shown.
79
backend/venv/Lib/site-packages/Crypto/Cipher/__init__.py
Normal file
79
backend/venv/Lib/site-packages/Crypto/Cipher/__init__.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#
|
||||
# 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.
|
||||
|
||||
import os
|
||||
|
||||
from Crypto.Cipher._mode_ecb import _create_ecb_cipher
|
||||
from Crypto.Cipher._mode_cbc import _create_cbc_cipher
|
||||
from Crypto.Cipher._mode_cfb import _create_cfb_cipher
|
||||
from Crypto.Cipher._mode_ofb import _create_ofb_cipher
|
||||
from Crypto.Cipher._mode_ctr import _create_ctr_cipher
|
||||
from Crypto.Cipher._mode_openpgp import _create_openpgp_cipher
|
||||
from Crypto.Cipher._mode_ccm import _create_ccm_cipher
|
||||
from Crypto.Cipher._mode_eax import _create_eax_cipher
|
||||
from Crypto.Cipher._mode_siv import _create_siv_cipher
|
||||
from Crypto.Cipher._mode_gcm import _create_gcm_cipher
|
||||
from Crypto.Cipher._mode_ocb import _create_ocb_cipher
|
||||
|
||||
_modes = { 1:_create_ecb_cipher,
|
||||
2:_create_cbc_cipher,
|
||||
3:_create_cfb_cipher,
|
||||
5:_create_ofb_cipher,
|
||||
6:_create_ctr_cipher,
|
||||
7:_create_openpgp_cipher,
|
||||
9:_create_eax_cipher
|
||||
}
|
||||
|
||||
_extra_modes = { 8:_create_ccm_cipher,
|
||||
10:_create_siv_cipher,
|
||||
11:_create_gcm_cipher,
|
||||
12:_create_ocb_cipher
|
||||
}
|
||||
|
||||
def _create_cipher(factory, key, mode, *args, **kwargs):
|
||||
|
||||
kwargs["key"] = key
|
||||
|
||||
modes = dict(_modes)
|
||||
if kwargs.pop("add_aes_modes", False):
|
||||
modes.update(_extra_modes)
|
||||
if not mode in modes:
|
||||
raise ValueError("Mode not supported")
|
||||
|
||||
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")
|
||||
|
||||
return modes[mode](factory, **kwargs)
|
||||
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.
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.
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.
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_chacha20.pyd
Normal file
BIN
backend/venv/Lib/site-packages/Crypto/Cipher/_chacha20.pyd
Normal file
Binary file not shown.
293
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cbc.py
Normal file
293
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cbc.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
25
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cbc.pyi
Normal file
25
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cbc.pyi
Normal file
@@ -0,0 +1,25 @@
|
||||
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: ...
|
||||
|
||||
650
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ccm.py
Normal file
650
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ccm.py
Normal file
@@ -0,0 +1,650 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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 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 (nonce and 7 <= len(nonce) <= 13):
|
||||
raise ValueError("Length of parameter 'nonce' must be"
|
||||
" in the range 7..13 bytes")
|
||||
|
||||
# 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)
|
||||
q = 15 - len(nonce) # length of Q, the encoded message length
|
||||
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
|
||||
flags = (64 * (self._assoc_len > 0) + 8 * ((self._mac_len - 2) // 2) +
|
||||
(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:
|
||||
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:
|
||||
raise ValueError("Message is too long")
|
||||
|
||||
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:
|
||||
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:
|
||||
raise ValueError("Message is too long")
|
||||
|
||||
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)
|
||||
47
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ccm.pyi
Normal file
47
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ccm.pyi
Normal file
@@ -0,0 +1,47 @@
|
||||
from types import ModuleType
|
||||
from typing import Union, overload, Dict, Tuple, Optional
|
||||
|
||||
Buffer = Union[bytes, bytearray, memoryview]
|
||||
|
||||
__all__ = ['CcmMode']
|
||||
|
||||
class CcmMode(object):
|
||||
block_size: int
|
||||
nonce: bytes
|
||||
|
||||
def __init__(self,
|
||||
factory: ModuleType,
|
||||
key: Buffer,
|
||||
nonce: Buffer,
|
||||
mac_len: int,
|
||||
msg_len: int,
|
||||
assoc_len: 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: ...
|
||||
293
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cfb.py
Normal file
293
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cfb.py
Normal file
@@ -0,0 +1,293 @@
|
||||
# -*- 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)
|
||||
26
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cfb.pyi
Normal file
26
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_cfb.pyi
Normal file
@@ -0,0 +1,26 @@
|
||||
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: ...
|
||||
393
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ctr.py
Normal file
393
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ctr.py
Normal file
@@ -0,0 +1,393 @@
|
||||
# -*- 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)
|
||||
27
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ctr.pyi
Normal file
27
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ctr.pyi
Normal file
@@ -0,0 +1,27 @@
|
||||
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: ...
|
||||
|
||||
408
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_eax.py
Normal file
408
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_eax.py
Normal file
@@ -0,0 +1,408 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
45
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_eax.pyi
Normal file
45
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_eax.pyi
Normal file
@@ -0,0 +1,45 @@
|
||||
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: ...
|
||||
220
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ecb.py
Normal file
220
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ecb.py
Normal file
@@ -0,0 +1,220 @@
|
||||
# -*- 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)
|
||||
19
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ecb.pyi
Normal file
19
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ecb.pyi
Normal file
@@ -0,0 +1,19 @@
|
||||
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: ...
|
||||
|
||||
620
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_gcm.py
Normal file
620
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_gcm.py
Normal file
@@ -0,0 +1,620 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
45
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_gcm.pyi
Normal file
45
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_gcm.pyi
Normal file
@@ -0,0 +1,45 @@
|
||||
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: ...
|
||||
532
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ocb.py
Normal file
532
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ocb.py
Normal file
@@ -0,0 +1,532 @@
|
||||
# ===================================================================
|
||||
#
|
||||
# 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)
|
||||
36
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ocb.pyi
Normal file
36
backend/venv/Lib/site-packages/Crypto/Cipher/_mode_ocb.pyi
Normal file
@@ -0,0 +1,36 @@
|
||||
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: ...
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user