Skip to content

Commit

Permalink
Enable digest generator with stream for ChaCha20 and Salsa20
Browse files Browse the repository at this point in the history
  • Loading branch information
dipu-bd committed Apr 18, 2024
1 parent 101118e commit d770cf3
Show file tree
Hide file tree
Showing 25 changed files with 716 additions and 212 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ Implementations of cryptographic algorithms for encryption and decryption in Dar

## Features

| Ciphers | Public class and methods | Source |
| ----------------- | ---------------------------------------------------------------- | :----------: |
| XOR | `XOR`, `xor`, `xorPipe` | Wikipedia |
| ChaCha20 | `ChaCha20`, `chacha20`, `chacha20Pipe` | RFC-8439 |
| ChaCha20/Poly1305 | `ChaCha20Poly1305`, `chacha20poly1305`, `chacha20poly1305digest` | RFC-8439 |
| Salsa20 | `Salsa20`, `salsa20`, `salsa20Pipe` | Snuffle 2005 |
| Salsa20/Poly1305 | `Salsa20Poly1305`, `salsa20poly1305`, `salsa20poly1305digest` | Snuffle 2005 |
| Ciphers | Public class and methods | Source |
| ----------------- | -------------------------------------------------------------------------------------------------------------------- | :----------: |
| XOR | `XOR`, `xor`, `xorStream` | Wikipedia |
| ChaCha20 | `ChaCha20`, `chacha20`, `chacha20Stream` | RFC-8439 |
| ChaCha20/Poly1305 | `ChaCha20Poly1305`, `chacha20poly1305digest`, `chacha20poly1305verify`, `chacha20poly1305`, `chacha20poly1305Stream` | RFC-8439 |
| Salsa20 | `Salsa20`, `salsa20`, `salsa20Stream` | Snuffle 2005 |
| Salsa20/Poly1305 | `Salsa20Poly1305`, `salsa20poly1305digest`, `salsa20poly1305verify`, `salsa20poly1305`, `salsa20poly1305Stream` | Snuffle 2005 |

## Getting started

Expand Down
2 changes: 1 addition & 1 deletion benchmark/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CipherlibStreamBenchmark extends AsyncBenchmark {

@override
Future<void> run() async {
await cipher.chacha20Pipe(inputStream, key).drain();
await cipher.chacha20Stream(inputStream, key).drain();
}
}

Expand Down
2 changes: 1 addition & 1 deletion benchmark/salsa20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class CipherlibStreamBenchmark extends AsyncBenchmark {

@override
Future<void> run() async {
await cipher.salsa20Pipe(inputStream, key).drain();
await cipher.salsa20Stream(inputStream, key).drain();
}
}

Expand Down
2 changes: 1 addition & 1 deletion benchmark/xor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CipherlibStreamBenchmark extends AsyncBenchmark {

@override
Future<void> run() async {
await cipher.xorPipe(inputStream, key).drain();
await cipher.xorStream(inputStream, key).drain();
}
}

Expand Down
4 changes: 2 additions & 2 deletions example/cipherlib_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ void main() {
result.cipher,
key,
nonce: nonce,
tag: result.tag.bytes,
mac: result.mac.bytes,
);
print(' Text: $text');
print(' Key: ${toHex(key)}');
print(' Nonce: ${toHex(nonce)}');
print('Cipher: ${toHex(result.cipher)}');
print(' Tag: ${result.tag.hex()}');
print(' Tag: ${result.mac.hex()}');
print(' Plain: ${utf8.decode(plain.cipher)}');
}
}
6 changes: 3 additions & 3 deletions lib/src/algorithms/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ChaCha20 extends SymmetricCipher {
Uint8List convert(
List<int> message, {
List<int>? nonce,
int blockCount = 1,
int blockId = 1,
}) {
if (message.isEmpty) {
return Uint8List(0);
Expand All @@ -42,7 +42,7 @@ class ChaCha20 extends SymmetricCipher {
var result = Uint8List.fromList(message);
for (int i = 0; i < message.length; ++i) {
if (pos == 0 || pos == 64) {
_block(state, key32, nonce32, blockCount++);
_block(state, key32, nonce32, blockId++);
pos = 0;
}
result[i] ^= state8[pos++];
Expand All @@ -51,7 +51,7 @@ class ChaCha20 extends SymmetricCipher {
}

@override
Stream<int> pipe(
Stream<int> bind(
Stream<int> stream, {
List<int>? nonce,
int blockId = 1,
Expand Down
125 changes: 118 additions & 7 deletions lib/src/algorithms/chacha20_poly1305.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,140 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:async';
import 'dart:typed_data';

import 'package:cipherlib/src/core/authenticator.dart';
import 'package:cipherlib/src/core/chunk_stream.dart';
import 'package:hashlib/hashlib.dart' show HashDigest;

import 'chacha20.dart';
import 'poly1305.dart';

/// ChaCha20-Poly1305 is a cryptographic algorithm combining the [ChaCha20]
/// stream cipher for encryption and the [Poly1305Authenticator] for message
/// authentication. It provides both confidentiality and integrity protection,
/// making it a popular choice for secure communication protocols like TLS.
/// stream cipher for encryption andthe [Poly1305Mac] for generating message
/// authentication code.
/// It provides both confidentiality and integrity protection, making it a
/// popular choice for secure communication protocols like TLS.
///
/// This implementation is based on the [RFC-8439][rfc]
///
/// [rfc]: https://www.rfc-editor.org/rfc/rfc8439.html
class ChaCha20Poly1305 extends ChaCha20 with Poly1305Authenticator {
class ChaCha20Poly1305 extends ChaCha20 with Authenticator {
@override
String get name => "${super.name}/Poly1305";

const ChaCha20Poly1305(List<int> key) : super(key);

@override
Uint8List generateOTK([List<int>? nonce]) => convert(
/// Generate One-Time-Key for Poly1305
@pragma('vm:prefer-inline')
Uint8List _generateOTK([List<int>? nonce]) => convert(
Uint8List(32),
nonce: nonce,
blockCount: 0,
blockId: 0,
);

@override
HashDigest digest(
List<int> message, {
List<int>? nonce,
List<int>? aad,
}) =>
Poly1305Mac(
_generateOTK(nonce),
aad: aad,
).convert(message);

@override
bool verify(
List<int> message,
List<int> mac, {
List<int>? nonce,
List<int>? aad,
}) =>
digest(
message,
nonce: nonce,
aad: aad,
).isEqual(mac);

@override
CipherMAC convertWithDigest(
List<int> message, {
List<int>? mac,
List<int>? nonce,
List<int>? aad,
int blockId = 1,
}) {
var otk = _generateOTK(nonce);
if (mac != null) {
var digest = Poly1305Mac(otk, aad: aad).convert(message);
if (!digest.isEqual(mac)) {
throw StateError('Invalid MAC');
}
}
var cipher = convert(
message,
nonce: nonce,
blockId: blockId,
);
var digest = Poly1305Mac(otk, aad: aad).convert(cipher);
return CipherMAC(cipher, digest);
}

@override
AsyncCipherMAC streamWithDigest(
Stream<int> stream, {
Future<HashDigest>? mac,
List<int>? nonce,
List<int>? aad,
int blockId = 1,
}) {
var controller = StreamController<int>(sync: true);
return AsyncCipherMAC(
controller.stream,
$buildDigest(
controller,
stream,
mac: mac,
nonce: nonce,
aad: aad,
blockId: blockId,
),
);
}

Future<HashDigest> $buildDigest(
StreamController<int> controller,
Stream<int> stream, {
Future<HashDigest>? mac,
List<int>? nonce,
List<int>? aad,
int blockId = 1,
}) async {
var otk = _generateOTK(nonce);
var sink = mac != null ? Poly1305Mac(otk, aad: aad).createSink() : null;
// create digest sink for cipher
var cipherSink = Poly1305Mac(otk, aad: aad).createSink();
// cipher stream
var it = generate(nonce, blockId).iterator;
await for (var buffer in asChunkedStream(4096, stream)) {
sink?.add(buffer);
for (int p = 0; p < buffer.length; ++p) {
it.moveNext();
buffer[p] ^= it.current;
controller.add(buffer[p]);
}
cipherSink.add(buffer);
}
controller.close();
// message digest
if (sink != null && mac != null) {
if (!sink.digest().isEqual(await mac)) {
throw StateError('Invalid MAC');
}
}
// cipher digest
return cipherSink.digest();
}
}
97 changes: 47 additions & 50 deletions lib/src/algorithms/poly1305.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,66 @@

import 'dart:typed_data';

import 'package:cipherlib/src/core/auth_cipher.dart';
import 'package:hashlib/hashlib.dart' show HashDigest, Poly1305;
import 'package:hashlib/hashlib.dart' show HashDigest, Poly1305, Poly1305Sink;

/// [Poly1305] is an authentication algorithm used for verifying the integrity
/// of messages. It generates a short, fixed-length tag based on a secret key
/// and the message, providing assurance that the message has not been
/// tampered with.
///
/// This is intended to be used as a mixin with the original ChaCha20 or Salsa20
/// algorithms to generate message digests.
abstract class Poly1305Authenticator implements Authenticator {
// Generate a 32-bytes long One-Time-Key for Poly1305 digest
Uint8List generateOTK([List<int>? nonce]);
class Poly1305Mac extends Poly1305 {
final List<int>? aad;

@override
HashDigest digest(
List<int> message, {
List<int>? nonce,
List<int>? aad,
}) {
// create key
var otk = generateOTK(nonce);
/// Creates a new instance
///
/// Parameters:
/// - [keypair] : A 32-bytes long key.
/// - [aad] : Additional authenticated data.
const Poly1305Mac(
List<int> keypair, {
this.aad,
}) : super(keypair);

// create sink
var sink = Poly1305(otk).createSink();
@override
Poly1305AuthenticatorSink createSink() =>
Poly1305AuthenticatorSink()..init(key, aad);
}

// add AAD
int aadLength = aad?.length ?? 0;
if (aad != null && aadLength > 0) {
sink.add(aad);
sink.add(Uint8List(16 - (aadLength & 15)));
}
/// Extends the base [Poly1305Sink] to generate message digest for cipher
/// algorithms.
class Poly1305AuthenticatorSink extends Poly1305Sink {
int _aadLength = 0;
int _messageLength = 0;

// add cipher text
int messageLength = message.length;
if (messageLength > 0) {
sink.add(message);
sink.add(Uint8List(16 - (messageLength & 15)));
@override
void init(List<int> keypair, [List<int>? aad]) {
super.init(keypair);
_aadLength = aad?.length ?? 0;
if (aad != null && _aadLength > 0) {
super.add(aad);
super.add(Uint8List(16 - (_aadLength & 15)));
}
_messageLength = 0;
}

// add lengths
sink.add(Uint32List.fromList([
aadLength,
aadLength >>> 32,
messageLength,
messageLength >>> 32,
]).buffer.asUint8List());

return sink.digest();
@override
void add(List<int> data, [int start = 0, int? end]) {
end ??= data.length;
_messageLength += end - start;
super.add(data, start, end);
}

@override
bool verify(
List<int> message,
List<int> tag, {
List<int>? nonce,
List<int>? aad,
}) {
var current = digest(
message,
nonce: nonce,
aad: aad,
);
return current.isEqual(tag);
HashDigest digest() {
if (_messageLength > 0) {
super.add(Uint8List(16 - (_messageLength & 15)));
}

super.add(Uint32List.fromList([
_aadLength,
_aadLength >>> 32,
_messageLength,
_messageLength >>> 32,
]).buffer.asUint8List());
return super.digest();
}
}
2 changes: 1 addition & 1 deletion lib/src/algorithms/salsa20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class Salsa20 extends SymmetricCipher {
}

@override
Stream<int> pipe(
Stream<int> bind(
Stream<int> stream, {
List<int>? nonce,
}) async* {
Expand Down
Loading

0 comments on commit d770cf3

Please sign in to comment.