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 047bd36
Show file tree
Hide file tree
Showing 20 changed files with 821 additions and 231 deletions.
40 changes: 0 additions & 40 deletions .github/workflows/build.yml

This file was deleted.

40 changes: 36 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,58 @@ on:
push:
branches: [master]
paths: ['**.dart', '**.yaml', '**.yml']
pull_request:
branches: [master]
paths: ['**.dart', '**.yaml']

jobs:
test:
if: github.repository == 'bitanon/cipherlib'
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
sdk: ['2.14.0', 'stable', 'dev']
sdk: ['2.14', 'stable', 'beta']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

# You can specify other versions if desired, see documentation here:
# https://github.com/dart-lang/setup-dart/blob/main/README.md
- uses: dart-lang/[email protected]
- uses: dart-lang/setup-dart@v1
with:
sdk: ${{ matrix.sdk }}

- name: Install dependencies
run: dart pub get

- name: Verify formatting
run: dart format --output=none --set-exit-if-changed .

- name: Analyze project source
run: dart analyze --fatal-infos

- name: Generate documentation
run: dart doc

- name: Run tests
run: dart test

coverage:
if: github.repository == 'bitanon/cipherlib'
strategy:
matrix:
os: [ubuntu-latest]
sdk: ['stable']
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

- uses: dart-lang/setup-dart@v1
with:
sdk: ${{ matrix.sdk }}

- 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
Loading

0 comments on commit 047bd36

Please sign in to comment.