ver 0.0.1
This commit is contained in:
58
lib/src/crypto/crypto_service.dart
Normal file
58
lib/src/crypto/crypto_service.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
20
lib/src/crypto/salt_generator.dart
Normal file
20
lib/src/crypto/salt_generator.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
79
lib/src/models/pass_payload.dart
Normal file
79
lib/src/models/pass_payload.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
65
lib/src/serializer/binary_serializer.dart
Normal file
65
lib/src/serializer/binary_serializer.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user