166 lines
4.6 KiB
Dart
166 lines
4.6 KiB
Dart
import 'dart:convert';
|
|
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:bcrypt/bcrypt.dart';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:crypton/crypton.dart' as crypton;
|
|
import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart' as jwt;
|
|
|
|
import '../../config/config.dart';
|
|
|
|
class KeyPair {
|
|
final String publicKey;
|
|
final String privateKey;
|
|
|
|
KeyPair({required this.publicKey, required this.privateKey});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'publicKey': publicKey,
|
|
'privateKey': privateKey,
|
|
};
|
|
|
|
factory KeyPair.fromJson(Map<String, dynamic> json) {
|
|
return KeyPair(
|
|
publicKey: json['publicKey'] as String,
|
|
privateKey: json['privateKey'] as String,
|
|
);
|
|
}
|
|
}
|
|
|
|
class CryptoService {
|
|
/// Generate a new Ed25519 key pair
|
|
static KeyPair generateKeyPair() {
|
|
final keyPair = crypton.ECKeypair.fromRandom();
|
|
return KeyPair(
|
|
publicKey: keyPair.publicKey.toString(),
|
|
privateKey: keyPair.privateKey.toString(),
|
|
);
|
|
}
|
|
|
|
/// Sign data with private key
|
|
static String sign(String data, String privateKey) {
|
|
final privKey = crypton.ECPrivateKey.fromString(privateKey);
|
|
final message = Uint8List.fromList(utf8.encode(data));
|
|
final signature = privKey.createSHA256Signature(message);
|
|
return base64Encode(signature);
|
|
}
|
|
|
|
/// Verify signature with public key
|
|
static bool verify(String data, String signature, String publicKey) {
|
|
try {
|
|
final pubKey = crypton.ECPublicKey.fromString(publicKey);
|
|
final message = Uint8List.fromList(utf8.encode(data));
|
|
final signatureBytes = Uint8List.fromList(base64Decode(signature));
|
|
return pubKey.verifySHA256Signature(message, signatureBytes);
|
|
} catch (_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Encrypt private key with password (for storage/backup)
|
|
static EncryptedKey encryptPrivateKey(String privateKey, String password) {
|
|
final salt = BCrypt.gensalt();
|
|
final key = _deriveKey(password, salt);
|
|
final encrypted = _xorEncrypt(privateKey, key);
|
|
return EncryptedKey(
|
|
encryptedData: encrypted,
|
|
salt: salt,
|
|
);
|
|
}
|
|
|
|
/// Decrypt private key with password
|
|
static String? decryptPrivateKey(
|
|
String encryptedData, String salt, String password) {
|
|
try {
|
|
final key = _deriveKey(password, salt);
|
|
return _xorDecrypt(encryptedData, key);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
static String _deriveKey(String password, String salt) {
|
|
final bytes = utf8.encode(password + salt);
|
|
final hash = sha256.convert(bytes);
|
|
return hash.toString();
|
|
}
|
|
|
|
static String _xorEncrypt(String data, String key) {
|
|
final dataBytes = utf8.encode(data);
|
|
final keyBytes = utf8.encode(key);
|
|
final encrypted = <int>[];
|
|
for (var i = 0; i < dataBytes.length; i++) {
|
|
encrypted.add(dataBytes[i] ^ keyBytes[i % keyBytes.length]);
|
|
}
|
|
return base64Encode(encrypted);
|
|
}
|
|
|
|
static String _xorDecrypt(String encrypted, String key) {
|
|
final encryptedBytes = base64Decode(encrypted);
|
|
final keyBytes = utf8.encode(key);
|
|
final decrypted = <int>[];
|
|
for (var i = 0; i < encryptedBytes.length; i++) {
|
|
decrypted.add(encryptedBytes[i] ^ keyBytes[i % keyBytes.length]);
|
|
}
|
|
return utf8.decode(decrypted);
|
|
}
|
|
}
|
|
|
|
class EncryptedKey {
|
|
final String encryptedData;
|
|
final String salt;
|
|
|
|
EncryptedKey({required this.encryptedData, required this.salt});
|
|
|
|
Map<String, dynamic> toJson() => {
|
|
'encryptedData': encryptedData,
|
|
'salt': salt,
|
|
};
|
|
}
|
|
|
|
class AuthService {
|
|
static final _nonceRandom = Random.secure();
|
|
|
|
/// Generate JWT token for user
|
|
static String generateToken(String userId, String username) {
|
|
final payload = {
|
|
'sub': userId,
|
|
'username': username,
|
|
};
|
|
|
|
final token = jwt.JWT(payload);
|
|
return token.sign(
|
|
jwt.SecretKey(Config.jwtSecret),
|
|
expiresIn: Config.tokenExpiry,
|
|
);
|
|
}
|
|
|
|
/// Verify JWT token and return payload
|
|
static Map<String, dynamic>? verifyToken(String token) {
|
|
try {
|
|
final verified = jwt.JWT.verify(token, jwt.SecretKey(Config.jwtSecret));
|
|
return Map<String, dynamic>.from(verified.payload as Map);
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Hash password with bcrypt
|
|
static String hashPassword(String password) {
|
|
return BCrypt.hashpw(password, BCrypt.gensalt());
|
|
}
|
|
|
|
/// Verify password against hash
|
|
static bool verifyPassword(String password, String hash) {
|
|
return BCrypt.checkpw(password, hash);
|
|
}
|
|
|
|
/// Generate a nonce for signature verification
|
|
static String generateNonce() {
|
|
final bytes = List<int>.generate(32, (_) => _nonceRandom.nextInt(256));
|
|
final hash = sha256.convert(bytes);
|
|
return hash.toString().substring(0, 32);
|
|
}
|
|
}
|