Skip to content

Commit

Permalink
showed how to encode msgs as Uint8List and how to use Hash-and-Sign-P…
Browse files Browse the repository at this point in the history
…aradigm
  • Loading branch information
JannesNebendahl committed Aug 28, 2024
1 parent 72fc048 commit f33ac6b
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 7 deletions.
78 changes: 73 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Dart implementation of the [Dilithium](https://www.pq-crystals.org/dilithium/) s

## Usage

### Key Pair generation:
### Key Pair Generation:
```dart
Uint8List randomSeed = Uint8List(Dilithium.SEEDBYTES);
Expand All @@ -15,10 +15,10 @@ DilithiumKeyPair keyPair = Dilithium.generateKeyPair(DilithiumParameterSpec.LEVE
DilithiumPublicKey sk = keyPair.publicKey;
DilithiumPrivateKey pk = keyPair.privateKey;
```
Note that you must provide an algorithm parameter spec representing the desired security level - the above example uses level 3, but you can select 2 and 5 as well. The three parameter spec objects are declared as static fields on the DilithiumParameterSpec class. Alternatively, a static method, getSpecForSecurityLevel(), is provided on DilithiumParameterSpec, allowing you to easily retrieve the spec for a given level at runtime.
Note that you must provide an algorithm's parameter spec representing the desired security level - the above example uses level 3, but you can select 2 and 5 as well. The three parameter spec objects are declared as static fields on the DilithiumParameterSpec class. Alternatively, a static method, getSpecForSecurityLevel(), is provided on DilithiumParameterSpec, allowing you to easily retrieve the spec for a given level at runtime.

### Signing:
Having generated a key pair, use the private key of it to sign a message, which has to be encoded as an Uint8List.
Having generated a key pair, use the private key of it to sign a message, which has to be encoded as an Uint8List. (More to encoding messages as Uint8Lists under )
```dart
Uint8List message = utf8.encode("Valid Message");
Expand All @@ -29,7 +29,7 @@ Uint8List signature = Dilithium.sign(keyPair.privateKey, message);
```dart
bool isValid = Dilithium.verify(keyPair.publicKey, signature, message);
```
The isValid variable shows now if the message and signature can be verified and aren't modified. Note that a `InvalidSignature` exception can be thrown in case of malformed signatures.
The isValid variable indicates whether the message and signature can be verified and have not been modified. Note that a `InvalidSignature` exception can be thrown in case of malformed signatures.

### Keys serialization/deserialization:
You can use the `.serialize()` method of the public and private key to obtain a byte representation of the key (in case of dart this refers to a Uint8List). The formats are compatible with the [c reference implementation](https://github.com/pq-crystals/dilithium) and [java implementation](https://github.com/mthiim/dilithium-java).
Expand All @@ -42,6 +42,74 @@ final reinstantiatedPk = DilithiumPublicKey.deserialize(DilithiumParameterSpec.L
final reinstantiatedSk = DilithiumPrivateKey.deserialize(DilithiumParameterSpec.LEVEL3, encodedPrivateKey);
```

The usage of the package can be further explored in [this](./integration_test/package_usage_test.dart) test.

### Encoding messages as Uint8List
This package is set up to sign and verify messages which are encoded as Uint8List (means practically Byte Streams). Dart provides a variety of options to convert different types to a Uint8List.
The following code snippets show you conversions of the most typical message types:

#### String Messages
```Dart
String stringMsg = 'Hello World!';
Uint8List byteMsg = utf8.encode(stringMsg); // [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33]
```

#### Integer Messages
```Dart
int intMsg = 1234567890;
ByteData byteData = ByteData(8); // 8 bytes for int (int64)
byteData.setInt64(0, intMsg);
Uint8List byteMsg = byteData.buffer.asUint8List(); // [0, 0, 0, 0, 73, 150, 2, 210]
```

#### Floating Point Messages
```Dart
double doubleMsg = 3.141592653589793;
ByteData byteData = ByteData(8); // 8 bytes for double
byteData.setFloat64(0, doubleMsg);
Uint8List byteMsg = byteData.buffer.asUint8List(); // [64, 9, 33, 251, 84, 68, 45, 24]
```
You can further investigate the message encoding in [this](./integration_test/msg_setup_test.dart) test.

### Sign-and-Hash-Paradigm
To make the signing and verifying of messages of arbitrary length more efficient, you can use the `Hash-and-Sign-Paradigm`. This approach involves converting the message to a hash and then signing and verifying this hash rather than the entire message. Here's how it works:

Sender:
1. **Hashing the Message**: First, the message or file that needs to be signed is processed through a cryptographic hash function. This function creates a unique, fixed-length hash value (or digital fingerprint) from the original message. Regardless of the size of the original message, the hash value always has the same length, making it efficient to handle.

2. **Signing the Hash**: The generated hash value is then used in the signing process. Using Dilithium, you sign the hash value instead of the original message: `Dilithium.sign(privateKey, hash_value)`. The resulting digital signature, along with the original message, is then sent to the recipient.

Receiver:
1. **Hashing the Received Message**: Upon receiving the message, the recipient applies the same cryptographic hash function to the received message. This step produces a hash value of the received message. If the communication has not been tampered, this hash value will match the hash value that was signed by the sender.

2. **Verifying the Hash**: To verify the message, the recipient uses Dilithium to check the validity of the signature: `Dilithium.verify(publicKey, hash_value)`. The sender's public key and the newly computed hash value are used in this verification process. If the signature is valid for the hash value, it confirms that the message has not been altered. Any modification in the message would result in a different hash value, which would not match the original signature, thereby indicating tampering.

This method ensures both the integrity and authenticity of the message with arbitrary length while optimizing the efficiency of the signing and verification process.

#### Example: Sign-and-Hash with SHA-256
```Dart
String msg = "This is a long test message, longer than 32 bytes.";
// Sender:
Uint8List originalMsg = utf8.encode(msg);
Uint8List hashValue = Uint8List.fromList(sha256.convert(originalMsg).bytes);
final signature = Dilithium.sign(keyPair.privateKey, hashValue);
// Receiver
String receivedMsg = msg;
Uint8List receivedMsgBytes = utf8.encode(receivedMsg);
Uint8List generatedHashValue = Uint8List.fromList(sha256.convert(receivedMsgBytes).bytes);
bool isValid = Dilithium.verify(keyPair.publicKey, signature, generatedHashValue);
expect(isValid, isTrue); // --> the received message is unmodified
```
This example requires the [crypto](https://pub.dev/packages/crypto) package for SHA-256 hashing. You can further investigate this process in [this](./integration_test/hash_and_sign_test.dart) test.

## What is Dilithium
Dilithium is a post-quantum secure digital signature algorithm designed to provide robust security against potential future quantum computer attacks. It is part of the [CRYSTALS](https://pq-crystals.org/) (Cryptographic Suite for Algebraic Lattices) suite, which is a collection of cryptographic primitives intended to resist quantum computing threats.

Expand All @@ -52,7 +120,7 @@ Dilithium's key features include:

For more detailed information, you can visit the official [Dilithium](https://www.pq-crystals.org/dilithium/) website.

## Advantages of Dart implementation
## Advantages of the Dart Implementation

Implementing the Dilithium signature scheme directly in Dart offers several advantages over using the [c reference implementation](https://github.com/pq-crystals/dilithium) via [`dart:ffi`](https://pub.dev/packages/ffi):

Expand Down
38 changes: 38 additions & 0 deletions integration_test/hash_and_sign_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@

import 'dart:convert';
import 'dart:typed_data';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:dilithium/dilithium.dart';
import 'package:test/test.dart';

main(){
late DilithiumKeyPair keyPair;

setUpAll((){
final random = Random.secure();
final seed = Uint8List.fromList(List<int>.generate(Dilithium.SEEDBYTES, (_) => random.nextInt(256)));

keyPair = Dilithium.generateKeyPair(DilithiumParameterSpec.LEVEL3, seed);
});

test('Hash and Sign Paradigma', (){
String msg = "This is a long test message, longer than 32 bytes.";

// Sender:
Uint8List originalMsg = utf8.encode(msg);
Uint8List hashValue = Uint8List.fromList(sha256.convert(originalMsg).bytes);

final signature = Dilithium.sign(keyPair.privateKey, hashValue);

// Receiver:
String receivedMsg = msg;
Uint8List receivedMsgBytes = utf8.encode(receivedMsg);
Uint8List generatedHashValue = Uint8List.fromList(sha256.convert(receivedMsgBytes).bytes);

bool isValid = Dilithium.verify(keyPair.publicKey, signature, generatedHashValue);
expect(isValid, isTrue); // --> the received message is unmodified
});


}
56 changes: 56 additions & 0 deletions integration_test/msg_setup_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:dilithium/dilithium.dart';
import 'package:test/test.dart';

void main(){
late DilithiumKeyPair keyPair;

setUpAll((){
final random = Random.secure();
final seed = Uint8List.fromList(List<int>.generate(Dilithium.SEEDBYTES, (_) => random.nextInt(256)));

keyPair = Dilithium.generateKeyPair(DilithiumParameterSpec.LEVEL3, seed);
});

group('encode message as Uint8List', (){
test('string msg', () {
String stringMsg = 'Hello World!';
Uint8List byteMsg = utf8.encode(stringMsg);

final signature = Dilithium.sign(keyPair.privateKey, byteMsg);
final isValid = Dilithium.verify(keyPair.publicKey, signature, byteMsg);

expect(isValid, isTrue);
});

test('int msg', (){
ByteData byteData = ByteData(8); // 8 bytes for int (int64)

int intMsg = 1234567890;
byteData.setInt64(0, intMsg);
Uint8List byteMsg = byteData.buffer.asUint8List();

final signature = Dilithium.sign(keyPair.privateKey, byteMsg);
final isValid = Dilithium.verify(keyPair.publicKey, signature, byteMsg);

expect(isValid, isTrue);
});

test('double msg', (){
ByteData byteData = ByteData(8); // 8 bytes for double

double doubleMsg = 3.141592653589793;
byteData.setFloat64(0, doubleMsg);
Uint8List byteMsg = byteData.buffer.asUint8List();

final signature = Dilithium.sign(keyPair.privateKey, byteMsg);
final isValid = Dilithium.verify(keyPair.publicKey, signature, byteMsg);

expect(isValid, isTrue);
});
});

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'dart:math';
import 'dart:typed_data';

import 'package:dilithium/dilithium.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -69,7 +68,7 @@ void main() {
expect(Dilithium.verify(keyPair.publicKey, newSignature, newMessage), isTrue);
}

group('Usage', (){
group('Algorithm (Dilithium) usage', (){
group('Dilithium 2', (){
final spec = DilithiumParameterSpec.LEVEL2;

Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ dependencies:
dev_dependencies:
lints: ^3.0.0
test: ^1.24.0
crypto: ^3.0.0

0 comments on commit f33ac6b

Please sign in to comment.