ver 0.0.1

This commit is contained in:
2026-08-09 23:47:39 +03:00
parent 411b00c3ee
commit 8c431c1742
6 changed files with 506 additions and 3 deletions

View File

@@ -0,0 +1,58 @@
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';
/// Сервис асимметричной криптографии Ed25519
class CryptoService {
static final Ed25519 _algorithm = Ed25519();
/// Генерация новой пары ключей
static Future<SimpleKeyPair> generateKeyPair() async {
return await _algorithm.newKeyPair();
}
/// Получение сырых 32 байт публичного ключа из объекта SimpleKeyPair
static Future<Uint8List> getPublicKeyBytes(SimpleKeyPair keyPair) async {
final SimplePublicKey publicKey = await keyPair.extractPublicKey();
return Uint8List.fromList(publicKey.bytes);
}
/// Создание цифровой подписи (64 байта)
static Future<Uint8List> sign({
required List<int> message,
required SimpleKeyPair keyPair,
}) async {
final Signature signature = await _algorithm.sign(
message,
keyPair: keyPair,
);
return Uint8List.fromList(signature.bytes);
}
/// Проверка цифровой подписи [signatureBytes] (64 байта)
/// для сообщения [message] с помощью публичного ключа [publicKeyBytes] (32 байта)
static Future<bool> verify({
required List<int> message,
required List<int> signatureBytes,
required List<int> publicKeyBytes,
}) async {
if (signatureBytes.length != 64 || publicKeyBytes.length != 32) {
return false;
}
final SimplePublicKey publicKey = SimplePublicKey(
publicKeyBytes,
type: KeyPairType.ed25519,
);
final Signature signature = Signature(
signatureBytes,
publicKey: publicKey,
);
return await _algorithm.verify(
message,
signature: signature,
);
}
}

View File

@@ -0,0 +1,20 @@
import 'dart:math';
import 'dart:typed_data';
abstract class SaltGenerator {
// Стандартный размер соли: 16 байт (128 бит)
static const int defaultSize = 16;
// Генерирует случайный массив байт указанной длины
static Uint8List generate([int length = defaultSize]) {
final Random secureRandom = Random.secure();
final Uint8List salt = Uint8List(length);
for (int i = 0; i < length; i++) {
salt[i] = secureRandom.nextInt(256);
}
return salt;
}
}

View File

@@ -0,0 +1,79 @@
import 'dart:typed_data';
import 'package:potato_pass_core/src/encoding/compact_text_encoder.dart';
/// Модель данных пропуска PotatoPass
class PassPayload {
final String lastName;
final String firstName;
final String middleName;
final String position;
final String department;
/// Битовая маска зон доступа
final Uint8List zoneMask;
/// 32 байта публичного ключа клиента (он же уникальный ID пропуска)
final Uint8List clientPublicKey;
/// 64 байта подписи Службы Безопасности (Security Department Signature)
final Uint8List securityDepartmentSignature;
/// 64 байта подписи Соли
final Uint8List challengeSignature;
PassPayload({
required this.lastName,
required this.firstName,
required this.middleName,
required this.position,
required this.department,
required this.zoneMask,
required this.clientPublicKey,
required this.securityDepartmentSignature,
required this.challengeSignature,
});
/// Проверяет, разрешен ли проход в конкретную зону по её индексу
bool hasZoneAccess(int zoneIndex) {
final int byteIndex = zoneIndex ~/ 8;
final int bitIndex = zoneIndex % 8;
if (byteIndex >= zoneMask.length) return false;
return (zoneMask[byteIndex] & (1 << bitIndex)) != 0;
}
/// Возвращает байты тела пропуска (Текст + Зоны + PubKey)
Uint8List getSignedBodyBytes() {
final List<int> bytes = [];
void writeField(String text) {
final Uint8List encoded = CompactTextEncoder.encode(text);
bytes.add(encoded.length);
bytes.addAll(encoded);
}
writeField(lastName);
writeField(firstName);
writeField(middleName);
writeField(position);
writeField(department);
bytes.add(zoneMask.length);
bytes.addAll(zoneMask);
bytes.addAll(clientPublicKey);
return Uint8List.fromList(bytes);
}
/// Вспомогательный статический метод для генерации битовой маски зон
static Uint8List createZoneMask(List<int> activeZoneIndices, {int byteCount = 16}) {
final mask = Uint8List(byteCount);
for (final zone in activeZoneIndices) {
final int byteIndex = zone ~/ 8;
final int bitIndex = zone % 8;
if (byteIndex < byteCount) {
mask[byteIndex] |= (1 << bitIndex);
}
}
return mask;
}
}

View File

@@ -0,0 +1,65 @@
import 'dart:typed_data';
import 'package:potato_pass_core/src/encoding/compact_text_encoder.dart';
import 'package:potato_pass_core/src/models/pass_payload.dart';
/// Утилита для упаковки PassPayload в бинарный массив и обратно
abstract class BinarySerializer {
/// Упаковывает модель PassPayload в бинарный массив Uint8List
static Uint8List serialize(PassPayload pass) {
final List<int> bytes = [];
//Добавляем тело (Текст + Зоны + PubKey)
bytes.addAll(pass.getSignedBodyBytes());
//Добавляем 64 байта подписи Службы Безопасности
bytes.addAll(pass.securityDepartmentSignature);
//Добавляем 64 байта подписи Соли
bytes.addAll(pass.challengeSignature);
return Uint8List.fromList(bytes);
}
/// Распаковывает байтовый массив обратно в объект PassPayload
static PassPayload deserialize(Uint8List bytes) {
int offset = 0;
String readField() {
final int length = bytes[offset++];
final Uint8List fieldBytes = Uint8List.sublistView(bytes, offset, offset + length);
offset += length;
return CompactTextEncoder.decode(fieldBytes);
}
final String lastName = readField();
final String firstName = readField();
final String middleName = readField();
final String position = readField();
final String department = readField();
final int zoneMaskLength = bytes[offset++];
final Uint8List zoneMask = Uint8List.sublistView(bytes, offset, offset + zoneMaskLength);
offset += zoneMaskLength;
final Uint8List clientPublicKey = Uint8List.sublistView(bytes, offset, offset + 32);
offset += 32;
final Uint8List securityDepartmentSignature = Uint8List.sublistView(bytes, offset, offset + 64);
offset += 64;
final Uint8List challengeSignature = Uint8List.sublistView(bytes, offset, offset + 64);
offset += 64;
return PassPayload(
lastName: lastName,
firstName: firstName,
middleName: middleName,
position: position,
department: department,
zoneMask: zoneMask,
clientPublicKey: clientPublicKey,
securityDepartmentSignature: securityDepartmentSignature,
challengeSignature: challengeSignature,
);
}
}