Pointy Castle

repository·master·Indexed 42 days ago

https://github.com/bcgit/pc-dart

A Dart library for encryption and decryption, serving as a direct port of the Bouncy Castle Java library. It provides a wide array of cryptographic algorithms including block ciphers, digests, MACs, and asymmetric primitives, along with comprehensive ASN.1 support for encoding, decoding, and handling BER tags and PKCS#12 SafeBags.

Tokens
20.5K
Snippets
53
Records
72
Agent score
53%

What's inside Pointy Castle

  1. How to instantiate algorithms in Pointy Castle

    master

    Pointy Castle algorithms must be instantiated and then initialized with their specific parameters. There are two primary ways to create algorithm objects:

    Use high-level class factories (like Digest, Mac, KeyDerivator, etc.) and provide an algorithm name string. This is convenient because the registry automatically handles complex algorithms that require multiple underlying implementation classes (e.g., "SHA-1/HMAC/PBKDF2").

    Instantiate each implementation class directly using its constructor. For complex algorithms, you must manually instantiate and combine the individual components (e.g., passing a SHA256Digest into an HMac constructor).

    Note on binary size: Using the registry imports all algorithms by default, which increases the compiled size of your program. To minimize size, instantiate classes directly via constructors.

    // Using the Registry
    final sha256 = Digest("SHA-256");
    final hmacSha256 = Mac("SHA-256/HMAC");
    final derivator = KeyDerivator("SHA-1/HMAC/PBKDF2");
    
    // Without the Registry
    final sha256 = SHA256Digest();
    final hmacSha256 = HMac(SHA256Digest(), 64);
    final derivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64));
  2. Instantiate digest algorithms

    master

    You can instantiate digest algorithms in two ways:

    Using the Registry

    Invoke the Digest factory with the name of the algorithm as a string. This is useful for dynamic algorithm selection.

    final d = Digest("SHA-256");

    Supported names:

    • MD2, MD4, MD5
    • RIPEMD-128, RIPEMD-160, RIPEMD-256, RIPEMD-320
    • SHA-1, SHA-224, SHA-256, SHA-384, SHA-512
    • Tiger, Whirlpool, SM3

    Warning: Do not use the registry for Blake2bDigest, SHA3Digest, or SHA512tDigest, as these require additional parameters in their constructors.

    Without the Registry

    Directly invoke the specific implementation's constructor. This is the preferred method when the algorithm is known at compile time.

    final d = SHA256Digest(); // SHA-256
    // Using the registry
    final d = Digest("SHA-256");
    
    // Without the registry
    final d = SHA256Digest();
  3. Identify algorithm names using the algorithmName property

    master
    Every implementation class in Pointy Castle has an algorithmName property. If you have an instantiated object and need to know the exact string identifier required by the registry to recreate or use that algorithm, print the algorithmName property of the object.
  4. Supported RSA Encryption Standards

    master

    Pointy Castle implements PKCS #1 version 2.0 encryption and decryption schemes as defined in RFC 2437.

    • RSAES-OAEP (Optimal Asymmetric Encryption Padding): Implemented by the OAEPEncoding class. This is recommended for new applications.
    • RSAES-PKCS1-v1_5: Implemented by the PKCS1Encoding class. This is included for compatibility with existing applications and is not recommended for new ones.

    Compatibility Warning: Pointy Castle's OAEPEncoding implements PKCS #1 version 2.0. It is not compatible with programs expecting OAEP from PKCS #1 version 2.1 or later.

  5. Parse ASN1 data with ASN1Parser

    master

    To parse ASN1 data, pass the raw bytes to an ASN1Parser. You can then traverse the structure by calling nextObject() and casting the result to the expected ASN1 type (e.g., ASN1Sequence, ASN1Set). Once you have a container object like ASN1Sequence, you can iterate through its elements to access nested objects.

    // 1. Prepare the bytes (e.g., from Base64)
    var bytes = base64.decode(base64String);
    
    // 2. Initialize the parser
    var parser = ASN1Parser(bytes);
    
    // 3. Retrieve and traverse objects
    var sequence = parser.nextObject() as ASN1Sequence;
    sequence.elements.forEach((element) {
        var asn1Set = element as ASN1Set;
        var seq = asn1Set.elements.elementAt(0) as ASN1Sequence;
        var objectIdentifier = seq.elements.elementAt(0) as ASN1ObjectIdentifier;
        var printableString = seq.elements.elementAt(1) as ASN1PrintableString;
        print('${objectIdentifier.objectIdentifierAsString} = ${printableString.stringValue}');
    });
  6. Choose an import strategy for Pointy Castle

    master

    Depending on your needs for convenience versus compiled binary size, you can import the library in three ways:

    1. import "package:pointycastle/pointycastle.dart";

      • Exports the high-level API and interfaces.
      • Limitation: You cannot instantiate implementation classes directly (e.g., MD5Digest() will not work). You must use the Registry.
    2. import "package:pointycastle/export.dart";

      • Exports everything: high-level API, interfaces, and all algorithm implementation classes.
      • Usage: Allows both Registry usage and direct constructor instantiation. This is the easiest but results in the largest binary size.
    3. import "package:pointycastle/api.dart"; + individual imports

      • Exports only the high-level API.
      • Usage: You must manually import the specific implementation files you need (e.g., import "package:pointycastle/digests/sha256.dart";) to use direct constructors. This is the best approach for minimizing binary size.
  7. Encode ASN1 objects to bytes

    master

    To encode ASN1 structures, build a hierarchy of ASN1 objects (like ASN1Sequence or ASN1BitString) and use the .encodedBytes property of the top-level object to retrieve the DER/BER encoded byte array. This byte array can then be converted to Base64 for use in formats like PEM.

    // Create top level container
    var topLevelSeq = ASN1Sequence();
    
    // Create and add child sequences
    var algorithmSeq = ASN1Sequence();
    algorithmSeq.add(ASN1ObjectIdentifier.fromComponentString('1.2.840.113549.1.1.1'));
    algorithmSeq.add(ASN1Object.fromBytes(Uint8List.fromList([0x5, 0x0])));
    
    // Create constructed bit string with child elements
    var modulus = ASN1Integer(publicKey.modulus);
    var exponent = ASN1Integer(publicKey.exponent);
    var publicKeySeqBitString = ASN1BitString(
      elements: [modulus, exponent], 
      tag: ASN1Tags.BIT_STRING_CONSTRUCTED
    );
    
    // Assemble and encode
    topLevelSeq.add(algorithmSeq);
    topLevelSeq.add(publicKeySeqBitString);
    
    var encodedBytes = topLevelSeq.encodedBytes;
    var dataBase64 = base64.encode(encodedBytes);
  8. Compare Pointy Castle import behaviors

    master

    The import-demo examples demonstrate how different import styles in Dart affect the availability of constructors for Pointy Castle classes. Some import methods may restrict access to certain constructors. To compare the differences between these import strategies, run the provided shell script.

    ./differences-show.sh
  9. Generate RSA key pairs

    master

    To generate an RSA key pair in Pointy Castle, you must use an RSAKeyGenerator initialized with RSAKeyGeneratorParameters and a SecureRandom instance.

    Steps:

    1. Obtain a SecureRandom instance (e.g., using the 'Fortuna' algorithm).
    2. Instantiate RSAKeyGenerator (either via the KeyGenerator registry with the name "RSA" or directly via the constructor).
    3. Initialize the generator using init() with a ParametersWithRandom object containing your parameters and the random source.
    4. Call generateKeyPair().

    Note: The returned AsymmetricKeyPair contains abstract PublicKey and PrivateKey objects. You must cast them to RSAPublicKey and RSAPrivateKey to use them for RSA-specific operations.

    import "package:pointycastle/export.dart";
    
    // 1. Setup parameters
    final rsaParams = RSAKeyGeneratorParameters(BigInt.parse('65537'), 2048, 64);
    
    // 2. Initialize generator
    final keyGen = RSAKeyGenerator();
    keyGen.init(ParametersWithRandom(rsaParams, mySecureRandom));
    
    // 3. Generate and cast
    final pair = keyGen.generateKeyPair();
    final myPublic = pair.publicKey as RSAPublicKey;
    final myPrivate = pair.privateKey as RSAPrivateKey;
  10. Encrypt and decrypt using RSA

    master

    To perform RSA encryption and decryption in Pointy Castle, you must use an AsymmetricBlockCipher (such as OAEPEncoding or PKCS1Encoding) wrapping an RSAEngine.

    Encryption Workflow

    1. Instantiate an AsymmetricBlockCipher with an RSAEngine.
    2. Initialize the cipher for encryption (true) using a PublicKeyParameter<RSAPublicKey>.
    3. Process the plaintext in blocks using processBlock.

    Decryption Workflow

    1. Instantiate an AsymmetricBlockCipher with an RSAEngine.
    2. Initialize the cipher for decryption (false) using a PrivateKeyParameter<RSAPrivateKey>.
    3. Process the ciphertext in blocks using processBlock.

    Note: RSA provides confidentiality but not integrity. For data integrity, use digests, HMACs, or signatures alongside encryption.

    import "package:pointycastle/export.dart";
    
    Uint8List rsaEncrypt(RSAPublicKey myPublic, Uint8List dataToEncrypt) {
      final encryptor = OAEPEncoding(RSAEngine())
        ..init(true, PublicKeyParameter<RSAPublicKey>(myPublic)); // true=encrypt
    
      return _processInBlocks(encryptor, dataToEncrypt);
    }
    
    Uint8List rsaDecrypt(RSAPrivateKey myPrivate, Uint8List cipherText) {
      final decryptor = OAEPEncoding(RSAEngine())
        ..init(false, PrivateKeyParameter<RSAPrivateKey>(myPrivate)); // false=decrypt
    
      return _processInBlocks(decryptor, cipherText);
    }
    
    Uint8List _processInBlocks(AsymmetricBlockCipher engine, Uint8List input) {
      final numBlocks = input.length ~/ engine.inputBlockSize +
          ((input.length % engine.inputBlockSize != 0) ? 1 : 0);
    
      final output = Uint8List(numBlocks * engine.outputBlockSize);
    
      var inputOffset = 0;
      var outputOffset = 0;
      while (inputOffset < input.length) {
        final chunkSize = (inputOffset + engine.inputBlockSize <= input.length)
            ? engine.inputBlockSize
            : input.length - inputOffset;
    
        outputOffset += engine.processBlock(
            input, inputOffset, chunkSize, output, outputOffset);
    
        inputOffset += chunkSize;
      }
    
      return (output.length == outputOffset)
          ? output
          : output.sublist(0, outputOffset);
    }
  11. Instantiate ASN1 objects

    master

    ASN1 objects can be created using two primary methods:

    1. fromBytes named constructor: Automatically detects the encoding rule used within the provided bytes to create the instance.
    2. Default constructor: Takes specific arguments based on the object type (e.g., utf8StringValue). The constructor automatically sets the correct ASN1 tag.

    Constructed Objects: If an object supports constructed encoding/decoding, you can pass a list of child elements via the elements parameter. When using elements, you must manually set the tag to the appropriate constructed tag (e.g., ASN1Tags.UTF8_STRING_CONSTRUCTED).

    // Using default constructor for a simple string
    var asn1Object = ASN1UTF8String(utf8StringValue: 'Hello World');
    
    // Using default constructor for a constructed object with child elements
    var e1 = ASN1UTF8String(utf8StringValue: 'Hello');
    var e2 = ASN1UTF8String(utf8StringValue: ' World');
    var asn1Object = ASN1UTF8String(elements: [e1, e2], tag: ASN1Tags.UTF8_STRING_CONSTRUCTED);
    
    // Using fromBytes to automatically detect encoding
    var autoDetected = ASN1Object.fromBytes(someBytes);
  12. Calculate a digest using Pointy Castle

    master

    A digest (or hash) is a fixed-size value derived from a message using a cryptographic algorithm. In Pointy Castle, the Digest class represents the algorithm itself.

    To calculate a digest, you must:

    1. Instantiate a class that implements the Digest abstract class.
    2. Provide the bytes to the instance using either the process method (for complete data) or a combination of update, updateByte, and doFinal (for progressive data).

    Example of calculating a SHA-256 digest from a single Uint8List:

    import 'dart:convert';
    import 'dart:typed_data';
    import "package:pointycastle/export.dart";
    
    Uint8List sha256Digest(Uint8List dataToDigest) {
      final d = SHA256Digest();
    
      return d.process(dataToDigest);
    }
    
    void main(List<String> args) {
      final valuesToDigest = (args.isNotEmpty) ? args : ['Hello world!'];
    
      for (final data in valuesToDigest) {
        print('Data: "$data"');
        final hash = sha256Digest(utf8.encode(data) as Uint8List);
        print('SHA-256: $hash');
        print('SHA-256: ${bin2hex(hash)}'); // output in hexadecimal
      }
    }