Skip to content

Commit

Permalink
Refactor Nonce to be little-endian by default.
Browse files Browse the repository at this point in the history
- ChaCha20, and Salsa20 will accept Nonce now for counter.
- Test coverage reporting
  • Loading branch information
dipu-bd committed Aug 17, 2024
1 parent 278e541 commit 4fd3d19
Show file tree
Hide file tree
Showing 21 changed files with 790 additions and 191 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ on:
pull_request:
branches: [master]
paths: ['**.dart', '**.yaml']
schedule:
- cron: '0 0 * * 5' # m h d M w

jobs:
build:
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,10 @@ jobs:
- name: Install dependencies
run: dart pub get

- name: Run tests
run: dart test
- name: Run tests with coverage
run: bash ./scripts/coverage.sh

- name: Upload results to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
.dart_tool/
.packages

# Conventional directory for build outputs.
# Conventional directory for project outputs.
build/
doc/
coverage/
benchmark/**/*.exe
test/**/*.exe

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# cipherlib

[![test](https://github.com/bitanon/cipherlib/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/bitanon/cipherlib/actions/workflows/test.yml)
[![codecov](https://codecov.io/gh/bitanon/cipherlib/graph/badge.svg?token=ISIYJ8MNI0)](https://codecov.io/gh/bitanon/cipherlib)
[![plugin version](https://img.shields.io/pub/v/cipherlib?label=pub)](https://pub.dev/packages/cipherlib)
[![dart support](https://img.shields.io/badge/dart-%3e%3d%202.14.0-39f?logo=dart)](https://dart.dev/guides/whats-new#september-8-2021-214-release)
[![likes](https://img.shields.io/pub/likes/cipherlib?logo=dart)](https://pub.dev/packages/cipherlib/score)
Expand Down
4 changes: 2 additions & 2 deletions benchmark/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CipherlibBenchmark extends Benchmark {

@override
void run() {
cipher.ChaCha20(key, nonce).convert(input);
cipher.chacha20(input, key, nonce: nonce);
}
}

Expand Down Expand Up @@ -60,7 +60,7 @@ class CipherlibStreamBenchmark extends AsyncBenchmark {

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

Expand Down
7 changes: 4 additions & 3 deletions dart_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ concurrency: 8
platforms: [vm, node]

tags:
skip-js:
vm-only:
skip: true
on_platform:
node:
skip: true
vm:
skip: false
22 changes: 15 additions & 7 deletions lib/src/algorithms/aead_cipher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@ import 'package:hashlib/hashlib.dart' show HashDigest, MACSinkBase, MACHashBase;

/// The result fromo AEAD ciphers
class AEADResult {
/// The IV, available if and only if cipher does supports it.
final Uint8List? iv;

/// The output message
final Uint8List data;

/// The message authentication code
final HashDigest tag;

const AEADResult({
this.iv,
const AEADResult._({
required this.tag,
required this.data,
});
Expand All @@ -30,7 +26,19 @@ class AEADResult {
bool verify(List<int>? digest) => tag.isEqual(digest);

/// Creates a new instance of AEADResult with IV parameter
AEADResult withIV(Uint8List iv) => AEADResult(tag: tag, data: data, iv: iv);
AEADResultWithIV withIV(Uint8List iv) =>
AEADResultWithIV._(tag: tag, data: data, iv: iv);
}

class AEADResultWithIV extends AEADResult {
/// The IV, available if and only if cipher does supports it.
final Uint8List iv;

const AEADResultWithIV._({
required this.iv,
required HashDigest tag,
required Uint8List data,
}) : super._(tag: tag, data: data);
}

/// Extends the base [AEADCipherSink] to generate message digest for cipher
Expand Down Expand Up @@ -178,7 +186,7 @@ abstract class AEADCipher<C extends Cipher, M extends MACHashBase>
var sink = createSink();
var cipher = sink.add(message, 0, null, true);
var digest = sink.digest();
return AEADResult(
return AEADResult._(
tag: digest,
data: cipher,
);
Expand Down
4 changes: 2 additions & 2 deletions lib/src/algorithms/aes/ctr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ class AESInCTRMode extends SaltedCollateCipher {
Nonce64? nonce,
Nonce64? counter,
}) {
var nonce8 = (nonce ?? Nonce64.random()).bytes;
var counter8 = (counter ?? Nonce64.random()).bytes;
var nonce8 = (nonce?.reverse() ?? Nonce64.random()).bytes;
var counter8 = (counter?.reverse() ?? Nonce64.random()).bytes;
var iv = Uint8List.fromList([...nonce8, ...counter8]);
return AESInCTRMode(key, iv);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/src/algorithms/aes/xts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -420,9 +420,10 @@ class AESInXTSMode extends SaltedCollateCipher {
/// packet number or frame number. The initial tweak value is calculated
/// this value.
factory AESInXTSMode.fromSector(List<int> key, Nonce64 sector) {
var sector8 = sector.bytes;
var tweak = Uint8List(16);
for (int i = 0; i < 8; ++i) {
tweak[i] = sector.bytes[7 - i];
tweak[i] = sector8[i];
}
return AESInXTSMode(key, tweak);
}
Expand Down
87 changes: 51 additions & 36 deletions lib/src/algorithms/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,34 @@ import 'dart:typed_data';

import 'package:cipherlib/src/core/cipher_sink.dart';
import 'package:cipherlib/src/core/salted_cipher.dart';
import 'package:cipherlib/src/utils/nonce.dart';
import 'package:hashlib/hashlib.dart';

const int _mask32 = 0xFFFFFFFF;

/// This sink is used by the [ChaCha20] algorithm.
class ChaCha20Sink extends CipherSink {
ChaCha20Sink(this._key, this._iv, this._counterStart) {
ChaCha20Sink(this._key, this._nonce, this._counter) {
if (_key.length != 16 && _key.length != 32) {
throw ArgumentError('The key should be either 16 or 32 bytes');
}
if (_iv.length != 8 && _iv.length != 12) {
if (_nonce.length != 8 && _nonce.length != 12) {
throw ArgumentError('The nonce should be either 8 or 12 bytes');
}
if (_counter.length != 4 && _counter.length != 8) {
throw ArgumentError('The counter should be either 4 or 8 bytes');
}
_counterSize = _nonce.length == 8 ? 8 : 4;
reset();
}

int _pos = 0;
int _counter = 0;
bool _closed = false;
final Uint8List _key;
final Uint8List _iv;
final int _counterStart;
final Uint8List _nonce;
final Uint8List _counter;
late final int _counterSize;
final _iv = Uint8List(16);
final _state = Uint32List(16);
late final _state8 = _state.buffer.asUint8List();
late final _key32 = _key.buffer.asUint32List();
Expand All @@ -38,8 +45,14 @@ class ChaCha20Sink extends CipherSink {
void reset() {
_pos = 0;
_closed = false;
_counter = _counterStart;
_block(_state, _key32, _iv32, _counter++);
for (int i = 0; i < _counterSize; ++i) {
_iv[i] = _counter[i];
}
for (int i = _counterSize; i < 16; ++i) {
_iv[i] = _nonce[i - _counterSize];
}
_block(_state, _key32, _iv32);
_increment();
}

@override
Expand All @@ -58,19 +71,26 @@ class ChaCha20Sink extends CipherSink {
var result = Uint8List(end - start);
for (int i = start; i < end; i++) {
if (_pos == 64) {
_block(_state, _key32, _iv32, _counter++);
_block(_state, _key32, _iv32);
_increment();
_pos = 0;
}
result[i] = data[i] ^ _state8[_pos++];
}
return result;
}

void _increment() {
for (int i = 0; i < _counterSize; ++i) {
if ((++_iv[i]) != 0) return;
}
}

@pragma('vm:prefer-inline')
static int _rotl32(int x, int n) =>
(((x << n) & _mask32) ^ ((x & _mask32) >>> (32 - n)));

static void _block(Uint32List B, Uint32List K, Uint32List N, int counter) {
static void _block(Uint32List B, Uint32List K, Uint32List N) {
int i, s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15;

// init state
Expand All @@ -83,10 +103,10 @@ class ChaCha20Sink extends CipherSink {
s5 = B[5] = K[1];
s6 = B[6] = K[2];
s7 = B[7] = K[3];
s8 = B[8] = K[4];
s9 = B[9] = K[5];
s10 = B[10] = K[6];
s11 = B[11] = K[7];
s8 = B[8] = K[0];
s9 = B[9] = K[1];
s10 = B[10] = K[2];
s11 = B[11] = K[3];
} else {
s0 = B[0] = 0x61707865; // 'expa'
s1 = B[1] = 0x3320646e; // 'nd 3'
Expand All @@ -101,16 +121,10 @@ class ChaCha20Sink extends CipherSink {
s10 = B[10] = K[6];
s11 = B[11] = K[7];
}
s12 = B[12] = counter;
if (N.lengthInBytes == 8) {
s13 = B[13] = counter >>> 32;
s14 = B[14] = N[0];
s15 = B[15] = N[1];
} else {
s13 = B[13] = N[0];
s14 = B[14] = N[1];
s15 = B[15] = N[2];
}
s12 = B[12] = N[0];
s13 = B[13] = N[1];
s14 = B[14] = N[2];
s15 = B[15] = N[3];

// 10 diagonal(column) rounds
for (i = 0; i < 10; ++i) {
Expand Down Expand Up @@ -225,27 +239,28 @@ class ChaCha20 extends SaltedCipher {
final Uint8List key;

/// The initial block id
final int counter;
final Uint8List counter;

const ChaCha20(
this.key,
Uint8List nonce, [
this.counter = 1,
]) : super(nonce);
Uint8List nonce,
this.counter,
) : super(nonce);

/// Creates a [ChaCha20] with List<int> [key], and [nonce].
///
/// Every elements of the both list is transformed to unsigned 8-bit numbers.
factory ChaCha20.fromList(
List<int> key,
List<int> nonce, [
int counter = 1,
]) =>
ChaCha20(
key is Uint8List ? key : Uint8List.fromList(key),
nonce is Uint8List ? nonce : Uint8List.fromList(nonce),
counter,
);
List<int> key, {
List<int>? nonce,
Nonce64? counter,
}) {
nonce ??= randomBytes(8);
counter ??= Nonce64.int64(1);
var key8 = key is Uint8List ? key : Uint8List.fromList(key);
var nonce8 = nonce is Uint8List ? nonce : Uint8List.fromList(nonce);
return ChaCha20(key8, nonce8, counter.bytes);
}

@override
@pragma('vm:prefer-inline')
Expand Down
23 changes: 13 additions & 10 deletions lib/src/chacha20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,50 @@
import 'dart:typed_data';

import 'package:cipherlib/src/algorithms/chacha20.dart';
import 'package:cipherlib/src/utils/nonce.dart';

export 'algorithms/chacha20.dart' show ChaCha20, ChaCha20Sink;

/// Apply [ChaCha20] cipher with the follwing parameters:
///
/// Parameters:
/// - [message] : arbitrary length plain-text.
/// - [key] : Either 16 or 32 bytes key.
/// - [nonce] : Either 8 or 12 bytes nonce.
/// - [key] : 16 or 32 bytes key.
/// - [nonce] : 8 or 12 bytes nonce.
/// - [counter] : 64-bit counter. (Default: 1)
///
/// Both the encryption and decryption can be done using this same method.
@pragma('vm:prefer-inline')
Uint8List chacha20(
List<int> message,
List<int> key, {
List<int>? nonce,
int counter = 1,
Nonce64? counter,
}) =>
ChaCha20.fromList(
key,
nonce ?? Uint8List(12),
counter,
nonce: nonce,
counter: counter,
).convert(message);

/// Apply [ChaCha20] cipher with the follwing parameters:
///
/// Parameters:
/// - [stream] : arbitrary length plain-text.
/// - [key] : Either 16 or 32 bytes key.
/// - [nonce] : Either 8 or 12 bytes nonce.
/// - [key] : 16 or 32 bytes key.
/// - [nonce] : 8 or 12 bytes nonce.
/// - [counter] : 64-bit counter. (Default: 1)
///
/// Both the encryption and decryption can be done using this same method.
@pragma('vm:prefer-inline')
Stream<int> chacha20Stream(
Stream<int> stream,
List<int> key, {
List<int>? nonce,
int counter = 1,
Nonce64? counter,
}) =>
ChaCha20.fromList(
key,
nonce ?? Uint8List(12),
counter,
nonce: nonce,
counter: counter,
).stream(stream);
Loading

0 comments on commit 4fd3d19

Please sign in to comment.