/// Demonstrates different approaches to importing Pointy Castle libraries.
///
/// - import-demo-1.dart - import 'package:pointycastle/pointycastle.dart';
///                        can only used registry
/// - import-demo-2.dart - import 'package:pointycastle/export.dart';
///                        can use registry and all constructors
/// - import-demo-3.dart - import 'package:pointycastle/api.dart' plus
///                        individual libraries; can use registry and
///                        constructors from individually imported libraries
/// - import-demo-4.dart - import 'package:pointycastle/api.dart' plus
///                        individual libraries; same as 3, but tries
///                        to use the registry for classes that have NOT
///                        been individually imported. This should not
///                        work, but strangely does.
///
/// The useRegistry and explicit functions are the same in all examples,
/// but they can or cannot be used depending on what imports were used.
///
/// To see the differences between the examples, run 'diff' on the files.
library;

import 'dart:convert';
import 'dart:typed_data';

import 'package:pointycastle/export.dart';
import 'package:pointycastle/src/platform_check/platform_check.dart';

void main() {
  useRegistry();
  useConstructors();
}

void useRegistry() {
  final sha256 = Digest('SHA-256');
  final sha1 = Digest('SHA-1');
  final md5 = Digest('MD5');

  final digest = sha256.process(Uint8List.fromList(_data));

  final hmacSha256 = Mac('SHA-256/HMAC');
  final hmacSha512 = Mac('SHA-512/HMAC');
  final hmacMd5 = Mac('MD5/HMAC');

  final hmacValue = hmacSha256.process(Uint8List.fromList(_data));

  //final kd = KeyDerivator('SHA-256/HMAC/PBKDF2');

  final seed = Platform.instance.platformEntropySource().getBytes(32);
  final secRnd = SecureRandom('Fortuna')..seed(KeyParameter(seed));

  // AES-CBC encryption

  final salt = secRnd.nextBytes(32);

  final keyDerivator256 = KeyDerivator('SHA-256/HMAC/PBKDF2')
    ..init(Pbkdf2Parameters(salt, 10000, 256 ~/ 8));

  final aes256key = keyDerivator256.process(Uint8List.fromList(_secret));

  final iv = secRnd.nextBytes(128 ~/ 8);
  final aesCbc = BlockCipher('AES/CBC')
    ..init(true, ParametersWithIV(KeyParameter(aes256key), iv));

  final paddedData = Uint8List(
      _data.length + (aesCbc.blockSize - (_data.length % aesCbc.blockSize)))
    ..setAll(0, _data);
  Padding('PKCS7').addPadding(paddedData, _data.length);

  final ciphertext = aesCbc.process(paddedData);

  // RSA key generation and signing

  final keyGen = KeyGenerator('RSA');
  keyGen.init(ParametersWithRandom(
      RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 64), secRnd));
  final pair = keyGen.generateKeyPair();

  final signer = Signer('SHA-256/RSA')
    ..init(true, PrivateKeyParameter<RSAPrivateKey>(pair.privateKey));

  final signature =
      signer.generateSignature(Uint8List.fromList(_data)) as RSASignature;

  final verifier = Signer('SHA-256/RSA')
    ..init(false, PublicKeyParameter<RSAPublicKey>(pair.publicKey));
  final sigOk = verifier.verifySignature(Uint8List.fromList(_data), signature);

  print('''
Data: '${utf8.decode(Uint8List.fromList(_data))}'

SHA-256: ${bin2hex(digest)}
SHA-1:   ${bin2hex(sha1.process(Uint8List.fromList(_data)))}
MD5:     ${bin2hex(md5.process(Uint8List.fromList(_data)), separator: ':')}

HMAC-SHA256: ${bin2hex(hmacValue)}
HMAC-512:    ${bin2hex(hmacSha512.process(Uint8List.fromList(_data)))}
HMAC-MD5:    ${bin2hex(hmacMd5.process(Uint8List.fromList(_data)))}

AES-CBC ciphertext:
${bin2hex(ciphertext, wrap: 64)}

Signature:
${bin2hex(signature.bytes, wrap: 64)}
Verifies: $sigOk
''');
}

void useConstructors() {
  // Digest

  final sha256 = SHA256Digest();
  final sha1 = SHA1Digest();
  final md5 = MD5Digest();

  final digest = sha256.process(Uint8List.fromList(_data));

  // HMAC

  final hmacSha256 = HMac(SHA256Digest(), 64)
    ..init(KeyParameter(Uint8List.fromList(_secret)));
  final hmacSha512 = HMac(SHA512Digest(), 128)
    ..init(KeyParameter(Uint8List.fromList(_secret)));
  final hmacMd5 = HMac(MD5Digest(), 64)
    ..init(KeyParameter(Uint8List.fromList(_secret)));

  final hmacValue = hmacSha256.process(Uint8List.fromList(_data));

  // Secure random number generator

  final seed = Platform.instance.platformEntropySource().getBytes(32);
  final secRnd = FortunaRandom()..seed(KeyParameter(seed));

  // AES-CBC encryption

  final salt = secRnd.nextBytes(32);

  final keyDerivator256 = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64))
    ..init(Pbkdf2Parameters(salt, 10000, 256 ~/ 8));

  final aes256key = keyDerivator256.process(Uint8List.fromList(_secret));

  final iv = secRnd.nextBytes(128 ~/ 8);
  final aesCbc = CBCBlockCipher(AESEngine())
    ..init(true, ParametersWithIV(KeyParameter(aes256key), iv));

  final paddedData = Uint8List(
      _data.length + (aesCbc.blockSize - (_data.length % aesCbc.blockSize)))
    ..setAll(0, _data);
  PKCS7Padding().addPadding(paddedData, _data.length);

  final ciphertext = aesCbc.process(paddedData);

  // RSA key generation and signing

  final keyGen = RSAKeyGenerator();
  keyGen.init(ParametersWithRandom(
      RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 64), secRnd));
  final pair = keyGen.generateKeyPair();

  final signer = RSASigner(SHA256Digest(), '0609608648016503040201')
    ..init(true, PrivateKeyParameter<RSAPrivateKey>(pair.privateKey));

  final signature = signer.generateSignature(Uint8List.fromList(_data));

  final verifier = RSASigner(SHA256Digest(), '0609608648016503040201')
    ..init(false, PublicKeyParameter<RSAPublicKey>(pair.publicKey));
  final sigOk = verifier.verifySignature(Uint8List.fromList(_data), signature);

  print('''
Data: '${utf8.decode(Uint8List.fromList(_data))}'

SHA-256: ${bin2hex(digest)}
SHA-1:   ${bin2hex(sha1.process(Uint8List.fromList(_data)))}
MD5:     ${bin2hex(md5.process(Uint8List.fromList(_data)), separator: ':')}

HMAC-SHA256: ${bin2hex(hmacValue)}
HMAC-512:    ${bin2hex(hmacSha512.process(Uint8List.fromList(_data)))}
HMAC-MD5:    ${bin2hex(hmacMd5.process(Uint8List.fromList(_data)))}

AES-CBC ciphertext:
${bin2hex(ciphertext, wrap: 64)}

Signature:
${bin2hex(signature.bytes, wrap: 64)}
Verifies: $sigOk
''');
}

//----------------------------------------------------------------

final _data = utf8.encode('Hello world!');

final _secret = utf8.encode('p@ssw0rd');

//----------------------------------------------------------------
/// Represent bytes in hexadecimal
///
/// If a [separator] is provided, it is placed the hexadecimal characters
/// representing each byte. Otherwise, all the hexadecimal characters are
/// simply concatenated together.

String bin2hex(Uint8List bytes, {String? separator, int? wrap}) {
  var len = 0;
  final buf = StringBuffer();
  for (final b in bytes) {
    final s = b.toRadixString(16);
    if (buf.isNotEmpty && separator != null) {
      buf.write(separator);
      len += separator.length;
    }

    if (wrap != null && wrap < len + 2) {
      buf.write('\n');
      len = 0;
    }

    buf.write('${(s.length == 1) ? '0' : ''}$s');
    len += 2;
  }
  return buf.toString();
}
