Skip to content

Commit 4fd3d19

Browse files
committed
Refactor Nonce to be little-endian by default.
- ChaCha20, and Salsa20 will accept Nonce now for counter. - Test coverage reporting
1 parent 278e541 commit 4fd3d19

21 files changed

+790
-191
lines changed

.github/workflows/build.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ on:
88
pull_request:
99
branches: [master]
1010
paths: ['**.dart', '**.yaml']
11-
schedule:
12-
- cron: '0 0 * * 5' # m h d M w
1311

1412
jobs:
1513
build:

.github/workflows/test.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,10 @@ jobs:
2626
- name: Install dependencies
2727
run: dart pub get
2828

29-
- name: Run tests
30-
run: dart test
29+
- name: Run tests with coverage
30+
run: bash ./scripts/coverage.sh
31+
32+
- name: Upload results to Codecov
33+
uses: codecov/codecov-action@v4
34+
with:
35+
token: ${{ secrets.CODECOV_TOKEN }}

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
.dart_tool/
44
.packages
55

6-
# Conventional directory for build outputs.
6+
# Conventional directory for project outputs.
77
build/
88
doc/
9+
coverage/
910
benchmark/**/*.exe
1011
test/**/*.exe
1112

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# cipherlib
22

3+
[![test](https://github.com/bitanon/cipherlib/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/bitanon/cipherlib/actions/workflows/test.yml)
4+
[![codecov](https://codecov.io/gh/bitanon/cipherlib/graph/badge.svg?token=ISIYJ8MNI0)](https://codecov.io/gh/bitanon/cipherlib)
35
[![plugin version](https://img.shields.io/pub/v/cipherlib?label=pub)](https://pub.dev/packages/cipherlib)
46
[![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)
57
[![likes](https://img.shields.io/pub/likes/cipherlib?logo=dart)](https://pub.dev/packages/cipherlib/score)

benchmark/chacha20.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class CipherlibBenchmark extends Benchmark {
2323

2424
@override
2525
void run() {
26-
cipher.ChaCha20(key, nonce).convert(input);
26+
cipher.chacha20(input, key, nonce: nonce);
2727
}
2828
}
2929

@@ -60,7 +60,7 @@ class CipherlibStreamBenchmark extends AsyncBenchmark {
6060

6161
@override
6262
Future<void> run() async {
63-
await cipher.ChaCha20(key, nonce).stream(inputStream).drain();
63+
await cipher.chacha20Stream(inputStream, key, nonce: nonce).drain();
6464
}
6565
}
6666

dart_test.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ concurrency: 8
66
platforms: [vm, node]
77

88
tags:
9-
skip-js:
9+
vm-only:
10+
skip: true
1011
on_platform:
11-
node:
12-
skip: true
12+
vm:
13+
skip: false

lib/src/algorithms/aead_cipher.dart

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,13 @@ import 'package:hashlib/hashlib.dart' show HashDigest, MACSinkBase, MACHashBase;
1010

1111
/// The result fromo AEAD ciphers
1212
class AEADResult {
13-
/// The IV, available if and only if cipher does supports it.
14-
final Uint8List? iv;
15-
1613
/// The output message
1714
final Uint8List data;
1815

1916
/// The message authentication code
2017
final HashDigest tag;
2118

22-
const AEADResult({
23-
this.iv,
19+
const AEADResult._({
2420
required this.tag,
2521
required this.data,
2622
});
@@ -30,7 +26,19 @@ class AEADResult {
3026
bool verify(List<int>? digest) => tag.isEqual(digest);
3127

3228
/// Creates a new instance of AEADResult with IV parameter
33-
AEADResult withIV(Uint8List iv) => AEADResult(tag: tag, data: data, iv: iv);
29+
AEADResultWithIV withIV(Uint8List iv) =>
30+
AEADResultWithIV._(tag: tag, data: data, iv: iv);
31+
}
32+
33+
class AEADResultWithIV extends AEADResult {
34+
/// The IV, available if and only if cipher does supports it.
35+
final Uint8List iv;
36+
37+
const AEADResultWithIV._({
38+
required this.iv,
39+
required HashDigest tag,
40+
required Uint8List data,
41+
}) : super._(tag: tag, data: data);
3442
}
3543

3644
/// Extends the base [AEADCipherSink] to generate message digest for cipher
@@ -178,7 +186,7 @@ abstract class AEADCipher<C extends Cipher, M extends MACHashBase>
178186
var sink = createSink();
179187
var cipher = sink.add(message, 0, null, true);
180188
var digest = sink.digest();
181-
return AEADResult(
189+
return AEADResult._(
182190
tag: digest,
183191
data: cipher,
184192
);

lib/src/algorithms/aes/ctr.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ class AESInCTRMode extends SaltedCollateCipher {
157157
Nonce64? nonce,
158158
Nonce64? counter,
159159
}) {
160-
var nonce8 = (nonce ?? Nonce64.random()).bytes;
161-
var counter8 = (counter ?? Nonce64.random()).bytes;
160+
var nonce8 = (nonce?.reverse() ?? Nonce64.random()).bytes;
161+
var counter8 = (counter?.reverse() ?? Nonce64.random()).bytes;
162162
var iv = Uint8List.fromList([...nonce8, ...counter8]);
163163
return AESInCTRMode(key, iv);
164164
}

lib/src/algorithms/aes/xts.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,9 +420,10 @@ class AESInXTSMode extends SaltedCollateCipher {
420420
/// packet number or frame number. The initial tweak value is calculated
421421
/// this value.
422422
factory AESInXTSMode.fromSector(List<int> key, Nonce64 sector) {
423+
var sector8 = sector.bytes;
423424
var tweak = Uint8List(16);
424425
for (int i = 0; i < 8; ++i) {
425-
tweak[i] = sector.bytes[7 - i];
426+
tweak[i] = sector8[i];
426427
}
427428
return AESInXTSMode(key, tweak);
428429
}

lib/src/algorithms/chacha20.dart

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,34 @@ import 'dart:typed_data';
55

66
import 'package:cipherlib/src/core/cipher_sink.dart';
77
import 'package:cipherlib/src/core/salted_cipher.dart';
8+
import 'package:cipherlib/src/utils/nonce.dart';
9+
import 'package:hashlib/hashlib.dart';
810

911
const int _mask32 = 0xFFFFFFFF;
1012

1113
/// This sink is used by the [ChaCha20] algorithm.
1214
class ChaCha20Sink extends CipherSink {
13-
ChaCha20Sink(this._key, this._iv, this._counterStart) {
15+
ChaCha20Sink(this._key, this._nonce, this._counter) {
1416
if (_key.length != 16 && _key.length != 32) {
1517
throw ArgumentError('The key should be either 16 or 32 bytes');
1618
}
17-
if (_iv.length != 8 && _iv.length != 12) {
19+
if (_nonce.length != 8 && _nonce.length != 12) {
1820
throw ArgumentError('The nonce should be either 8 or 12 bytes');
1921
}
22+
if (_counter.length != 4 && _counter.length != 8) {
23+
throw ArgumentError('The counter should be either 4 or 8 bytes');
24+
}
25+
_counterSize = _nonce.length == 8 ? 8 : 4;
2026
reset();
2127
}
2228

2329
int _pos = 0;
24-
int _counter = 0;
2530
bool _closed = false;
2631
final Uint8List _key;
27-
final Uint8List _iv;
28-
final int _counterStart;
32+
final Uint8List _nonce;
33+
final Uint8List _counter;
34+
late final int _counterSize;
35+
final _iv = Uint8List(16);
2936
final _state = Uint32List(16);
3037
late final _state8 = _state.buffer.asUint8List();
3138
late final _key32 = _key.buffer.asUint32List();
@@ -38,8 +45,14 @@ class ChaCha20Sink extends CipherSink {
3845
void reset() {
3946
_pos = 0;
4047
_closed = false;
41-
_counter = _counterStart;
42-
_block(_state, _key32, _iv32, _counter++);
48+
for (int i = 0; i < _counterSize; ++i) {
49+
_iv[i] = _counter[i];
50+
}
51+
for (int i = _counterSize; i < 16; ++i) {
52+
_iv[i] = _nonce[i - _counterSize];
53+
}
54+
_block(_state, _key32, _iv32);
55+
_increment();
4356
}
4457

4558
@override
@@ -58,19 +71,26 @@ class ChaCha20Sink extends CipherSink {
5871
var result = Uint8List(end - start);
5972
for (int i = start; i < end; i++) {
6073
if (_pos == 64) {
61-
_block(_state, _key32, _iv32, _counter++);
74+
_block(_state, _key32, _iv32);
75+
_increment();
6276
_pos = 0;
6377
}
6478
result[i] = data[i] ^ _state8[_pos++];
6579
}
6680
return result;
6781
}
6882

83+
void _increment() {
84+
for (int i = 0; i < _counterSize; ++i) {
85+
if ((++_iv[i]) != 0) return;
86+
}
87+
}
88+
6989
@pragma('vm:prefer-inline')
7090
static int _rotl32(int x, int n) =>
7191
(((x << n) & _mask32) ^ ((x & _mask32) >>> (32 - n)));
7292

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

7696
// init state
@@ -83,10 +103,10 @@ class ChaCha20Sink extends CipherSink {
83103
s5 = B[5] = K[1];
84104
s6 = B[6] = K[2];
85105
s7 = B[7] = K[3];
86-
s8 = B[8] = K[4];
87-
s9 = B[9] = K[5];
88-
s10 = B[10] = K[6];
89-
s11 = B[11] = K[7];
106+
s8 = B[8] = K[0];
107+
s9 = B[9] = K[1];
108+
s10 = B[10] = K[2];
109+
s11 = B[11] = K[3];
90110
} else {
91111
s0 = B[0] = 0x61707865; // 'expa'
92112
s1 = B[1] = 0x3320646e; // 'nd 3'
@@ -101,16 +121,10 @@ class ChaCha20Sink extends CipherSink {
101121
s10 = B[10] = K[6];
102122
s11 = B[11] = K[7];
103123
}
104-
s12 = B[12] = counter;
105-
if (N.lengthInBytes == 8) {
106-
s13 = B[13] = counter >>> 32;
107-
s14 = B[14] = N[0];
108-
s15 = B[15] = N[1];
109-
} else {
110-
s13 = B[13] = N[0];
111-
s14 = B[14] = N[1];
112-
s15 = B[15] = N[2];
113-
}
124+
s12 = B[12] = N[0];
125+
s13 = B[13] = N[1];
126+
s14 = B[14] = N[2];
127+
s15 = B[15] = N[3];
114128

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

227241
/// The initial block id
228-
final int counter;
242+
final Uint8List counter;
229243

230244
const ChaCha20(
231245
this.key,
232-
Uint8List nonce, [
233-
this.counter = 1,
234-
]) : super(nonce);
246+
Uint8List nonce,
247+
this.counter,
248+
) : super(nonce);
235249

236250
/// Creates a [ChaCha20] with List<int> [key], and [nonce].
237251
///
238252
/// Every elements of the both list is transformed to unsigned 8-bit numbers.
239253
factory ChaCha20.fromList(
240-
List<int> key,
241-
List<int> nonce, [
242-
int counter = 1,
243-
]) =>
244-
ChaCha20(
245-
key is Uint8List ? key : Uint8List.fromList(key),
246-
nonce is Uint8List ? nonce : Uint8List.fromList(nonce),
247-
counter,
248-
);
254+
List<int> key, {
255+
List<int>? nonce,
256+
Nonce64? counter,
257+
}) {
258+
nonce ??= randomBytes(8);
259+
counter ??= Nonce64.int64(1);
260+
var key8 = key is Uint8List ? key : Uint8List.fromList(key);
261+
var nonce8 = nonce is Uint8List ? nonce : Uint8List.fromList(nonce);
262+
return ChaCha20(key8, nonce8, counter.bytes);
263+
}
249264

250265
@override
251266
@pragma('vm:prefer-inline')

0 commit comments

Comments
 (0)