- Flutter app (Android/iOS): home, explore, albums, search, player with equalizer, mini player, sign up/sign in with email codes, profile, encrypted offline downloads, About page - server/: one-command deploy of self-hosted Supabase behind HestiaCP/nginx (or Caddy), migrations, backups, email templates - supabase/migrations: schema, row-level security, profiles - tools/: song scanner and importer Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
54 lines
1.6 KiB
Dart
54 lines
1.6 KiB
Dart
import 'dart:math';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:pointycastle/export.dart';
|
|
|
|
/// AES-256 in counter mode, so any byte range of a song can be decrypted on
|
|
/// its own (seeking without reading the whole file). Encrypting and decrypting
|
|
/// are the same operation.
|
|
///
|
|
/// File layout: 8-byte random nonce, then the encrypted song.
|
|
class SongCipher {
|
|
SongCipher(Uint8List key) : _aes = AESEngine()..init(true, KeyParameter(key));
|
|
|
|
static const headerLength = 8;
|
|
static const _block = 16;
|
|
|
|
final AESEngine _aes;
|
|
|
|
static Uint8List newNonce() {
|
|
final r = Random.secure();
|
|
return Uint8List.fromList(List.generate(headerLength, (_) => r.nextInt(256)));
|
|
}
|
|
|
|
static Uint8List newKey() {
|
|
final r = Random.secure();
|
|
return Uint8List.fromList(List.generate(32, (_) => r.nextInt(256)));
|
|
}
|
|
|
|
/// XORs [data] with the key stream for song position [offset] (bytes after the header), in place.
|
|
void apply(Uint8List data, int offset, Uint8List nonce) {
|
|
final counter = Uint8List(_block)..setRange(0, headerLength, nonce);
|
|
final stream = Uint8List(_block);
|
|
var blockIndex = -1;
|
|
for (var i = 0; i < data.length; i++) {
|
|
final pos = offset + i;
|
|
final b = pos ~/ _block;
|
|
if (b != blockIndex) {
|
|
blockIndex = b;
|
|
_writeCounter(counter, b);
|
|
_aes.processBlock(counter, 0, stream, 0);
|
|
}
|
|
data[i] ^= stream[pos % _block];
|
|
}
|
|
}
|
|
|
|
static void _writeCounter(Uint8List counter, int blockIndex) {
|
|
var v = blockIndex;
|
|
for (var i = _block - 1; i >= headerLength; i--) {
|
|
counter[i] = v & 0xff;
|
|
v >>= 8;
|
|
}
|
|
}
|
|
}
|