Skip to content

Commit

Permalink
feat: null safety (#195)
Browse files Browse the repository at this point in the history
  • Loading branch information
are committed Feb 25, 2021
1 parent bf5794c commit 1c9aa8f
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 105 deletions.
2 changes: 2 additions & 0 deletions lib/encrypt.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart' hide Digest;
import 'package:pointycastle/export.dart' hide Signer hide RSASigner;

part 'src/utils.dart';

part 'src/algorithm.dart';

part 'src/algorithms/aes.dart';
Expand Down
4 changes: 2 additions & 2 deletions lib/src/algorithm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ part of encrypt;
/// Interface for the Algorithms.
abstract class Algorithm {
/// Encrypt [bytes].
Encrypted encrypt(Uint8List bytes, {IV iv});
Encrypted encrypt(Uint8List bytes, {IV? iv});

/// Decrypt [encrypted] value.
Uint8List decrypt(Encrypted encrypted, {IV iv});
Uint8List decrypt(Encrypted encrypted, {IV? iv});
}

/// Interface for the signing algorithms
Expand Down
24 changes: 16 additions & 8 deletions lib/src/algorithms/aes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ part of encrypt;
class AES implements Algorithm {
final Key key;
final AESMode mode;
final String padding;
final String? padding;
final BlockCipher _cipher;
final StreamCipher _streamCipher;
final StreamCipher? _streamCipher;

AES(this.key, {this.mode = AESMode.sic, this.padding = 'PKCS7'})
: _cipher = padding != null
Expand All @@ -17,13 +17,17 @@ class AES implements Algorithm {
: null;

@override
Encrypted encrypt(Uint8List bytes, {IV iv}) {
Encrypted encrypt(Uint8List bytes, {IV? iv}) {
if (iv == null) {
throw StateError('IV is required.');
}

if (_streamCipher != null) {
_streamCipher
_streamCipher!
..reset()
..init(true, _buildParams(iv));

return Encrypted(_streamCipher.process(bytes));
return Encrypted(_streamCipher!.process(bytes));
}

_cipher
Expand All @@ -38,13 +42,17 @@ class AES implements Algorithm {
}

@override
Uint8List decrypt(Encrypted encrypted, {IV iv}) {
Uint8List decrypt(Encrypted encrypted, {IV? iv}) {
if (iv == null) {
throw StateError('IV is required.');
}

if (_streamCipher != null) {
_streamCipher
_streamCipher!
..reset()
..init(false, _buildParams(iv));

return _streamCipher.process(encrypted.bytes);
return _streamCipher!.process(encrypted.bytes);
}

_cipher
Expand Down
12 changes: 6 additions & 6 deletions lib/src/algorithms/fernet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ part of encrypt;
class Fernet implements Algorithm {
final _maxClockSkew = 60;

Key _signKey;
Key _encryptionKey;
Clock _clock;
late final Key _signKey;
late final Key _encryptionKey;
late final Clock _clock;

Fernet(Key key, {Clock clock}) {
Fernet(Key key, {Clock? clock}) {
if (key.length != 32) {
throw StateError('Fernet key must be 32 url-safe base64-encoded bytes.');
}
Expand All @@ -22,7 +22,7 @@ class Fernet implements Algorithm {
}

@override
Encrypted encrypt(Uint8List bytes, {IV iv}) {
Encrypted encrypt(Uint8List bytes, {IV? iv}) {
if (iv == null) {
iv = IV.fromSecureRandom(16);
}
Expand All @@ -32,7 +32,7 @@ class Fernet implements Algorithm {
}

@override
Uint8List decrypt(Encrypted encrypted, {IV iv, int ttl}) {
Uint8List decrypt(Encrypted encrypted, {IV? iv, int? ttl}) {
final data = encrypted.bytes;
if (data.first != 0x80) {
throw StateError('Invalid token');
Expand Down
56 changes: 29 additions & 27 deletions lib/src/algorithms/rsa.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,53 @@ part of encrypt;

// Abstract class for encryption and signing.
abstract class AbstractRSA {
final RSAPublicKey publicKey;
final RSAPrivateKey privateKey;
final PublicKeyParameter<RSAPublicKey> _publicKeyParams;
final PrivateKeyParameter<RSAPrivateKey> _privateKeyParams;
final RSAPublicKey? publicKey;
final RSAPrivateKey? privateKey;
PublicKeyParameter<RSAPublicKey>? get _publicKeyParams =>
publicKey != null ? PublicKeyParameter(publicKey!) : null;
PrivateKeyParameter<RSAPrivateKey>? get _privateKeyParams =>
privateKey != null ? PrivateKeyParameter(privateKey!) : null;
final AsymmetricBlockCipher _cipher;

AbstractRSA({
this.publicKey,
this.privateKey,
RSAEncoding encoding = RSAEncoding.PKCS1,
}) : this._publicKeyParams = PublicKeyParameter(publicKey),
this._privateKeyParams = PrivateKeyParameter(privateKey),
this._cipher = encoding == RSAEncoding.OAEP
}) : this._cipher = encoding == RSAEncoding.OAEP
? OAEPEncoding(RSAEngine())
: PKCS1Encoding(RSAEngine());
}

/// Wraps the RSA Engine Algorithm.
class RSA extends AbstractRSA implements Algorithm {
RSA(
{RSAPublicKey publicKey,
RSAPrivateKey privateKey,
{RSAPublicKey? publicKey,
RSAPrivateKey? privateKey,
RSAEncoding encoding = RSAEncoding.PKCS1})
: super(publicKey: publicKey, privateKey: privateKey, encoding: encoding);

@override
Encrypted encrypt(Uint8List bytes, {IV iv}) {
Encrypted encrypt(Uint8List bytes, {IV? iv}) {
if (publicKey == null) {
throw StateError('Can\'t encrypt without a public key, null given.');
}

_cipher
..reset()
..init(true, _publicKeyParams);
..init(true, _publicKeyParams!);

return Encrypted(_cipher.process(bytes));
}

@override
Uint8List decrypt(Encrypted encrypted, {IV iv}) {
Uint8List decrypt(Encrypted encrypted, {IV? iv}) {
if (privateKey == null) {
throw StateError('Can\'t decrypt without a private key, null given.');
}

_cipher
..reset()
..init(false, _privateKeyParams);
..init(false, _privateKeyParams!);

return _cipher.process(encrypted.bytes);
}
Expand All @@ -59,13 +59,17 @@ class RSASigner extends AbstractRSA implements SignerAlgorithm {
final Uint8List _digestId;
final Digest _digestCipher;

RSASigner(this.digest, {RSAPublicKey publicKey, RSAPrivateKey privateKey})
: _digestId = _digestIdFactoryMap[digest].id,
_digestCipher = _digestIdFactoryMap[digest].factory(),
RSASigner(this.digest, {RSAPublicKey? publicKey, RSAPrivateKey? privateKey})
: _digestId = _digestIdFactoryMap[digest]!.id,
_digestCipher = _digestIdFactoryMap[digest]!.factory(),
super(publicKey: publicKey, privateKey: privateKey);

@override
Encrypted sign(Uint8List bytes) {
if (privateKey == null) {
throw StateError('Can\'t sign without a private key, null given.');
}

final hash = Uint8List(_digestCipher.digestSize);

_digestCipher
Expand All @@ -75,13 +79,17 @@ class RSASigner extends AbstractRSA implements SignerAlgorithm {

_cipher
..reset()
..init(true, _privateKeyParams);
..init(true, _privateKeyParams!);

return Encrypted(_cipher.process(_encode(hash)));
}

@override
bool verify(Uint8List bytes, Encrypted signature) {
if (publicKey == null) {
throw StateError('Can\'t verify without a public key, null given.');
}

final hash = Uint8List(_digestCipher.digestSize);

_digestCipher
Expand All @@ -91,7 +99,7 @@ class RSASigner extends AbstractRSA implements SignerAlgorithm {

_cipher
..reset()
..init(false, _publicKeyParams);
..init(false, _publicKeyParams!);

var _signature = Uint8List(_cipher.outputBlockSize);

Expand Down Expand Up @@ -171,7 +179,7 @@ enum RSASignDigest {

final _digestIdFactoryMap = <RSASignDigest, _DigestIdFactory>{
RSASignDigest.SHA256: _DigestIdFactory(
_hexToBytes('0609608648016503040201'), () => SHA256Digest())
decodeHexString('0609608648016503040201'), () => SHA256Digest())
};

class _DigestIdFactory {
Expand Down Expand Up @@ -211,7 +219,7 @@ class RSAKeyParser {
final modulus = (sequence.elements[0] as ASN1Integer).valueAsBigInteger;
final exponent = (sequence.elements[1] as ASN1Integer).valueAsBigInteger;

return RSAPublicKey(modulus, exponent);
return RSAPublicKey(modulus!, exponent!);
}

RSAAsymmetricKey _parsePrivate(ASN1Sequence sequence) {
Expand All @@ -220,7 +228,7 @@ class RSAKeyParser {
final p = (sequence.elements[4] as ASN1Integer).valueAsBigInteger;
final q = (sequence.elements[5] as ASN1Integer).valueAsBigInteger;

return RSAPrivateKey(modulus, exponent, p, q);
return RSAPrivateKey(modulus!, exponent!, p, q);
}

ASN1Sequence _parseSequence(List<String> rows) {
Expand Down Expand Up @@ -252,9 +260,3 @@ class RSAKeyParser {
return parser.nextObject() as ASN1Sequence;
}
}

Uint8List _hexToBytes(String encoded) => (Uint8List.fromList(List.generate(
encoded.length, (i) => i % 2 == 0 ? encoded.substring(i, i + 2) : null)
.where((b) => b != null)
.map((b) => int.parse(b, radix: 16))
.toList()));
12 changes: 10 additions & 2 deletions lib/src/algorithms/salsa20.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ class Salsa20 implements Algorithm {
Salsa20(this.key);

@override
Encrypted encrypt(Uint8List bytes, {IV iv}) {
Encrypted encrypt(Uint8List bytes, {IV? iv}) {
if (iv == null) {
throw StateError('IV is required.');
}

_cipher
..reset()
..init(true, _buildParams(iv));
Expand All @@ -18,7 +22,11 @@ class Salsa20 implements Algorithm {
}

@override
Uint8List decrypt(Encrypted encrypted, {IV iv}) {
Uint8List decrypt(Encrypted encrypted, {IV? iv}) {
if (iv == null) {
throw StateError('IV is required.');
}

_cipher
..reset()
..init(false, _buildParams(iv));
Expand Down
11 changes: 2 additions & 9 deletions lib/src/encrypted.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,7 @@ class Encrypted {
final Uint8List _bytes;

/// Creates an Encrypted object from a hexdecimal string.
Encrypted.fromBase16(String encoded)
: _bytes = Uint8List.fromList(
List.generate(encoded.length,
(i) => i % 2 == 0 ? encoded.substring(i, i + 2) : null)
.where((b) => b != null)
.map((b) => int.parse(b, radix: 16))
.toList(),
);
Encrypted.fromBase16(String encoded) : _bytes = decodeHexString(encoded);

/// Creates an Encrypted object from a Base64 string.
Encrypted.fromBase64(String encoded)
Expand Down Expand Up @@ -70,7 +63,7 @@ class Key extends Encrypted {
Key.fromSecureRandom(int length) : super(SecureRandom(length).bytes);

Key stretch(int desiredKeyLength,
{int iterationCount = 100, Uint8List salt}) {
{int iterationCount = 100, Uint8List? salt}) {
if (salt == null) {
salt = SecureRandom(desiredKeyLength).bytes;
}
Expand Down
12 changes: 6 additions & 6 deletions lib/src/encrypter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Encrypter {
Encrypter(this.algo);

/// Calls [encrypt] on the wrapped Algorithm using a raw binary.
Encrypted encryptBytes(List<int> input, {IV iv}) {
Encrypted encryptBytes(List<int> input, {IV? iv}) {
if (input is Uint8List) {
return algo.encrypt(input, iv: iv);
}
Expand All @@ -16,28 +16,28 @@ class Encrypter {
}

/// Calls [encrypt] on the wrapped Algorithm.
Encrypted encrypt(String input, {IV iv}) {
Encrypted encrypt(String input, {IV? iv}) {
return encryptBytes(convert.utf8.encode(input), iv: iv);
}

/// Calls [decrypt] on the wrapped Algorith without UTF-8 decoding.
List<int> decryptBytes(Encrypted encrypted, {IV iv}) {
List<int> decryptBytes(Encrypted encrypted, {IV? iv}) {
return algo.decrypt(encrypted, iv: iv).toList();
}

/// Calls [decrypt] on the wrapped Algorithm.
String decrypt(Encrypted encrypted, {IV iv}) {
String decrypt(Encrypted encrypted, {IV? iv}) {
return convert.utf8
.decode(decryptBytes(encrypted, iv: iv), allowMalformed: true);
}

/// Sugar for `decrypt(Encrypted.fromBase16(encoded))`.
String decrypt16(String encoded, {IV iv}) {
String decrypt16(String encoded, {IV? iv}) {
return decrypt(Encrypted.fromBase16(encoded), iv: iv);
}

/// Sugar for `decrypt(Encrypted.fromBase64(encoded))`.
String decrypt64(String encoded, {IV iv}) {
String decrypt64(String encoded, {IV? iv}) {
return decrypt(Encrypted.fromBase64(encoded), iv: iv);
}
}
12 changes: 12 additions & 0 deletions lib/src/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
part of encrypt;

Uint8List decodeHexString(String input) {
assert(input.length % 2 == 0, 'Input needs to be an even length.');

return Uint8List.fromList(
List.generate(
input.length ~/ 2,
(i) => int.parse(input.substring(i * 2, (i * 2) + 2), radix: 16),
).toList(),
);
}
Loading

0 comments on commit 1c9aa8f

Please sign in to comment.