Skip to content

OpenHFT/Chronicle-Salt

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Chronicle Salt - Java binding to wrap libsodium which implements the NaCl crypto library

badge

javadoc

This library natively supports Chronicle Bytes and can sign and verify data entirely off heap. This saves copying data to/from byte[] (not creating them)

Based on

This library is a port of the Abstractj Kalium library to use Chronicle Bytes off heap instead of byte[] on heap.

Using off heap data directly improves performance and scalability.

Requirements

Installation

libsodium

Chronicle Salt uses libsodium wrapped via jnr-ffi

For a more detailed explanation, please refer to RbNaCl’s documentation

Linux

Linux users can download the source tar for Linux

  • Download libsodium from https://download.libsodium.org/libsodium/releases/

  • Choose the version of libsodium you wish to use

  • The archives follow the following pattern: libsodium-{version}.tar.gz

  • tar xzvf libsodium-{version}.tar.gz

  • cd libsodium-{version}

  • ./configure

  • make

  • sudo make install

  • After this has released do a mvn install from the command line to build libbridge

OSX

OS X users can get libsodium via homebrew with:

brew install libsodium

Windows

Windows users will need to provide the pre-build binaries from libsodium.

  • Download libsodium from https://download.libsodium.org/libsodium/releases/

  • Choose the version of libsodium you wish to use

  • The archives follow the following pattern: libsodium-{version}-msvc.zip

  • From the archive find the artifacts compiled for your architecture and then the MSVC tool set of your choice

  • For example: v141 // these were compiled against the MSVC v141 (i.e. Visual Studio 2017)

  • Extract from the archive the dll library files into one of the following locations:

  • into the lib at the root of the working directory directory of your project.

  • into a location that is included in your PATH environment variable.

For example, on my Windows 10 machine with a x64 architecture:

{archive root}
└───x64
    ...
    └───Release
        ...
        └───v141
            ...
            └───dynamic <- copy the library files from this locaiton.

Private (Secret) and Public Keys and Ed25519 Signatures

Private (Secret) Keys

  • One of two keys used in public key cryptography.

  • It is used to sign digital signatures and to decrypt data that was encoded using the recipient’s public key.

  • The private key is not visible to the public; it is only visible to the owner.

  • Detailed definition

Public Keys

  • One of two keys used in public key cryptography.

  • The public key is used to encrypt the message receieved, and is assumed visibe to everyone.

  • Detailed definition

Ed25519 Signatures

  • Elliptic-curve signatures.

  • Engineered at several levels of design and implementation to achieve very high speeds without compromising security.

  • Detailed definition

Using Chronicle Salt

Generating Random Bytes

  • Random bytes are used to form a private key as it ensures they key is difficult to guess, therefore more secure.

Generating random bytes which could be used for a private key
    Bytes<?> rand = Ed25519.generateRandomBytes(32);

Generating a public and secret key from a seed

  • A public and secret key need to be generated as they are used to sign a message, making it secure and allowing the receiver authenticate the sender/message.

Generating private first and then a public and secret key
    Bytes<?> privateKey = Ed25519.generatePrivateKey();

    Bytes<?> publicKey = Bytes.allocateElasticDirect();
    Bytes<?> secretKey = Bytes.allocateElasticDirect();

    Ed25519.privateToPublicAndSecret(publicKey, secretKey, privateKey);
Note
The secret key holds the private AND public key and is needed for some operations.

Viewing keys as a hexadecimal dump

Viewing all three keys
    System.out.println(privateKey.toHexString());
    System.out.println(publicKey.toHexString());
    System.out.println(secretKey.toHexString());

Prints something like

private, public and secret keys
00000000 54 c8 b8 05 5a df 56 9f  8a ae b4 72 2c 69 26 42 T···Z·V· ···r,i&B
00000010 99 c6 d4 36 13 4c cc 2b  83 04 da c5 71 75 b0 1a ···6·L·+ ····qu··

00000000 95 65 db 8d 48 06 12 ae  c4 fe 44 c1 d9 07 5f 19 ·e··H··· ··D···_·
00000010 19 de 6b 13 cc 24 67 27  3a bf 9b ce 25 c8 a1 33 ··k··$g' :···%··3

00000000 54 c8 b8 05 5a df 56 9f  8a ae b4 72 2c 69 26 42 T···Z·V· ···r,i&B
00000010 99 c6 d4 36 13 4c cc 2b  83 04 da c5 71 75 b0 1a ···6·L·+ ····qu··
00000020 95 65 db 8d 48 06 12 ae  c4 fe 44 c1 d9 07 5f 19 ·e··H··· ··D···_·
00000030 19 de 6b 13 cc 24 67 27  3a bf 9b ce 25 c8 a1 33 ··k··$g' :···%··3

Signing a message

After creating a message, it can be signed.

Note
The signatureAndMsg includes the signature and the messages as this is the way the underlying library is written.
Signing a message
    Bytes<?> signatureAndMsg = Bytes.allocateElasticDirect();
    // OR
    Bytes<?> signatureAndMsg = Bytes.allocateDirect(Ed25519.SIGNATURE_LENGTH + message.readRemaining());
    Ed25519.sign(signatureAndMsg, message, secretKey);
Note
The sign method appends, rather than overwrites the signatureAndMsg. If you want to overwrite, you need to call clear() first
Signing two messages
    Bytes<?> signatureAndMsg = Bytes.allocateElasticDirect();
    Ed25519.sign(signatureAndMsg, message, secretKey);
    Ed25519.sign(signatureAndMsg, message2, secretKey); // (1)
  1. signatureAndMsg now contains two messages

Signing two messages with overwriting
    Bytes<?> signatureAndMsg = Bytes.allocateElasticDirect();
    Ed25519.sign(signatureAndMsg, message, secretKey); // (1)
    client.write(signatureAndMsg);

    signatureAndMsg.clear()
    Ed25519.sign(signatureAndMsg, message2, secretKey); // (2)
    client.write(signatureAndMsg);
  1. first message signed

  2. signatureAndMsg contains one message

Verifying a message

Once a message has been signed, you can verify it using the public key alone.

Verifying a message
    boolean verified = Ed25519.verify(signatureAndMsg, publicKey);
  • Verifying a message is a means of authenticating that a message is received from a certain sender.

  • The digital signature, put simply, is a hash of the data (message, file, etc.).

  • To validate a message, the receipient calculates the hash of the same data and will use the senders public key to decrypt the digital signature.

  • The two hash values are compared - if they match, the signature is considered valid. If they don’t match, it can mean that another signature was used to sign it, or the data was (intentionally or unintentionally) altered.

  • If the hash values do not match, the message will not be verified.

  • Using the public key to verify a message ensures you are receiving a genuine message from the sender, and that it hasn’t been altered in any way.

Public-Key Cryptography

Public-key cryptography requires two different keys: a public key which can be shared and is used to encrypt or authenticate a message, and a complementary private key which must be kept secret and is used to decrypt or sign a message. Chronicle-Salt wraps public-key cryptography in the EasyBox class (reflecting the underlying Sodium crypto_box_easy interface).

Authenticated encryption

A sender (Bob) can encrypt a confidential message for a specific receiver (Alice) using Alice’s public key. Using either Alice’s public key and Bob’s private key, or Bob’s public key and Alice’s private key, the (same) shared secret key can be computed. This shared secret is used to verify an encrypted message has not been tampered with.

Each message exchanged between two users should also have an associated nonce. This is some arbitrary additional data which is folded into the encryption, and is used to ensure that old communications cannot be simply reused as part of a replay attack. Crucially, for this to be effective, a nonce should never be re-used when encrypting messages between a given sender/receiver. In some applications, the nonce can be used as a form of message sequencer in which case a simple incrementing counter between messages is acceptable. Otherwise, the nonce would normally be refreshed/stirred between messages. A nonce does not need to be confidential.

Key Pair Generation

A public/private key pair can be generated as follows:

EasyBox.KeyPair keys = EasyBox.KeyPair.generate();

The above will generate a random key pair on each call. In some cases (such as testing) it is useful to have a deterministic key pair. Chronicle-Salt provides two options for this. The first is a simplistic but convenient call taking a long seed value, providing 64 seed bits:

EasyBox.KeyPair keys = EasyBox.KeyPair.deterministic(123);

Alternatively, a 32-byte BytesStore can be used, providing control over the full 256 seed bits, eg:

BytesStore seed = NativeBytesStore.from("01234567890123456789012345678901");
EasyBox.KeyPair keys = EasyBox.KeyPair.deterministic(seed);

Securely Wiping Keys

Sensitive data in general, and secret components of key pairs in particular, should be overwritten when no longer required. Chronicle-Salt provides convenient calls wrapping sodium_memzero() which attempts to securely zero a range of memory vs memset and similar which may be silently stripped by some optimisations.

Once a key pair is no longer needed, the following should be called to securely clear the data:

void KeyPair.wipe();

Nonce Generation

Nonces are arbitrary 32-byte sequences and can be generated in much the same way as key pairs:

// generate a random nonce
EasyBox.Nonce nonce = EasyBox.Nonce.generate();

// deterministic option 1: simplistic long/64-bit seed
EasyBox.Nonce nonce = EasyBox.Nonce.deterministic(123);

// deterministic option 2: 32-byte/256-bit seed
BytesStore seed = NativeBytesStore.from("01234567890123456789012345678901");
EasyBox.Nonce nonce = EasyBox.Nonce.deterministic(seed);

As described above, a given nonce value should never be re-used across messages between the same two parties. Given a nonce, a new value can be obtained in one of two ways depending on the use case:

// standard randomising call
nonce.stir();

// increment by 1, eg useful as a form of message sequencer
nonce.next();

Encryption/Decryption

Given two key pairs and a fresh nonce, a message can be sent between two parties using the recipient’s public key and the sender’s private key eg:

BytesStore message = NativeBytesStore.from("test message");

// Generate the key pairs and nonce
EasyBox.KeyPair alice = EasyBox.KeyPair.generate();
EasyBox.KeyPair bob = EasyBox.KeyPair.generate();
EasyBox.Nonce nonce = EasyBox.Nonce.generate();

// Alice sends to Bob
BytesStore cipherText = EasyBox.encrypt(message, nonce, bob.publicKey, alice.secretKey);

// Bob decrypts the message
BytesStore clearText = EasyBox.decrypt(cipherText, nonce, alice.publicKey, bob.secretKey);

// clear sensitive data when done
alice.wipe();
bob.wipe();

The decrypt call will throw an IllegalStateException if the decryption step fails for any reason.

The above creates the cipherText and clearText BytesStores as needed. Optionally an existing BytesStore can be provided, although the user needs to ensure sufficient size:

// ... as above

// Alice sends to Bob
EasyBox.encrypt(cipherText, message, nonce, bob.publicKey, alice.secretKey);

// Bob decrypts the message
EasyBox.decrypt(clearText, cipherText, nonce, alice.publicKey, bob.secretKey);

The above interfaces are strongly-typed on nonce, public key, and private key which helps to avoid mistakes from accidentally transposing arguments. This is the recommended approach, however a lower level interface taking explicit BytesStores is available and may be preferrable in some situations:

EasyBox.KeyPair alice = EasyBox.KeyPair.generate();
EasyBox.KeyPair bob = EasyBox.KeyPair.generate();

BytesStore alicePublicKey = alice.publicKey.store; // or some other manually managed area
BytesStore aliceSecretKey = alice.secretKey.store; // or some other manually managed area
BytesStore bobPublicKey = bob.publicKey.store;     // or some other manually managed area
BytesStore bobSecretKey = bob.secretKey.store;     // or some other manually managed area

BytesStore nonce = ...;

// Alice sends to Bob
EasyBox.encrypt(cipherText, message, nonce, bobPublicKey, aliceSecretKey);

// Bob decrypts the message
EasyBox.decrypt(clearText, cipherText, nonce, alicePublicKey, bobSecretKey);

Precalculation/Multiple messages

The standard encryption/decryption interface described above internally calculates a shared secret key (from the public and private keys passed in the encrypt/decrypt calls respectively). Where it is known that a number of messages will be sent between the same two parties, this shared secret key can be calculated once and reused on each operation, resulting in much improved performance.

As with standard key pairs, a SharedKey should be wiped when no longer required.

BytesStore message = NativeBytesStore.from("test message");

EasyBox.KeyPair alice = EasyBox.KeyPair.generate();
EasyBox.KeyPair bob = EasyBox.KeyPair.generate();
EasyBox.Nonce nonce = EasyBox.Nonce.generate();

// precalculate the shared secret key
EasyBox.SharedKey shared = EasyBox.SharedKey.precalc( alice, bob );

for (int i=0; i<1000; ++i)
{
    BytesStore cipherText = EasyBox.encryptShared(message, nonce, shared);
    BytesStore clearText  = EasyBox.decryptShared(ciphertext, nonce, shared);

    // increment the nonce, or alternatively use nonce.stir()
    nonce.next();
}

// clear sensitive data when done
alice.wipe();
bob.wipe();
shared.wipe();

Anonymous Sender/Sealed Boxes

A reduced form of public-key cryptography can be used to anonymously send a message to a recipient given the recipient’s public key. Chronicle-Salt wraps anonymous sender public-key cryptography in the SealedBox class (reflecting the underlying Sodium crypto_box_seal interface). A recipient can decypt a SealedBox message using their private key, but it is not possible to verify the identity of the sender. The integrity of the message itself can however be verified.

Internally, an ephemeral key pair is used on the sender’s side when encrypting a SealedBox message. This ephemeral key is not exposed by the underlying Sodium library, and cannot be controlled. For this reason there are no "deterministic" calls in the SealedBox interface, as while one public/private key pair could be deterministic the ephemeral key pair could not, meaning the ciphertext would vary from run to run.

The form of the SealedBox calls closely follows EasyBox (minus the nonce and second key pair), for example to encrypt/decrypt:

BytesStore message = NativeBytesStore.from("test message");

SealedBox.KeyPair keys = SealedBox.KeyPair.generate();

// Alice (anonymously) encrypts a message using Bob's public key
BytesStore ciphertext = SealedBox.encrypt(message, keys.publicKey);

// Bob decrypts the message using his own public and private keys
BytesStore cleartext = SealedBox.decrypt(ciphertext, keys.publicKey, keys.secretKey);

// clear sensitive data when done
keys.wipe();

The decrypt call will throw an IllegalStateException if the decryption step fails for any reason.

As for the EasyBox interface, an existing BytesStore can optionally be provided for the encrypt/decrypt call if preferred:

// ... as above

// Alice (anonymously) encrypts a message using Bob's public key
SealedBox.encrypt(ciphertext, message, keys.publicKey);

// Bob decrypts the message using his own public and private keys
SealedBox.decrypt(cleartext, ciphertext, keys.publicKey, keys.secretKey);

The above interfaces are strongly-typed on public/private key which helps to avoid mistakes from accidentally transposing arguments. This is the recommended approach, however a lower level interface taking explicit BytesStores is available and may be preferrable in some situations:

SealedBox.KeyPair keys = SealedBox.KeyPair.generate();

BytesStore publicKey = keys.publicKey.store; // or some other manually managed area
BytesStore secretKey = keys.secretKey.store; // or some other manually managed area

// Alice sends to Bob
SealedBox.encrypt(cipherText, message, publicKey);

// Bob decrypts the message
SealedBox.decrypt(clearText, cipherText, publicKey, secretKey);

Public-Key Signatures

Given a trusted public key from a particular sender, recipients can verify messages signed using the sender’s private key originated from the sender and have not subsequently been tampered with.

Note, this mechanism is used only to verify the source and integrity of a message. The message content itself is not changed in any way so this is not suitable for protecting sensitive data. For that use case, see the encryption/decryption support above.

Chronicle-Salt wraps public-key signatures in the Signature class, which in turn is built on the underlying Sodium crypto_sign interface. The form of the Signature calls closely follows EasyBox, but with just one key pair, and sign/verify instead of encrypt/decrypt.

The sender’s key pair can be generated randomly, or deterministically using a seed for repeatable behaviour:

// generate a random key pair
Signature.KeyPair keys = Signature.KeyPair.generate();

// deterministic option 1: simplistic long/64-bit seed
Signature.KeyPair keys = Signature.KeyPair.deterministic(123);

// deterministic option 2: 32-byte/256-bit seed
BytesStore seed = NativeBytesStore.from("01234567890123456789012345678901");
Signature.KeyPair keys = Signature.KeyPair.deterministic(seed);

A message can then be signed and subsequently verified as follows:

BytesStore message = NativeBytesStore.from( "test message" );

Signature.KeyPair keys = Signature.KeyPair.generate();

// Sender signs the message using their secret key
BytesStore signed = Signature.sign( message, keys.secretKey );

// Recipient verifies the message using the sender's public key
BytesStore unsigned = Signature.verify( signed, keys.publicKey);

// clear sensitive data when done
keys.wipe();

The verify call will throw an IllegalStateException if the verification step fails for any reason.

As for the EasyBox interface, an existing BytesStore can optionally be provided for the sign/verify call if preferred:

// ... as above

// Sender signs the message using their secret key
Signature.sign(signed, message, keys.secretKey);

// Recipient verifies the message using the sender's public key
Signature.verify(unsigned, signed, keys.publicKey);

The above interfaces are strongly-typed on public/private key which helps to avoid mistakes from accidentally using the wrong part. This is the recommended approach, however a lower level interface taking explicit BytesStores is available and may be preferrable in some situations:

Signature.KeyPair keys = Signature.KeyPair.generate();

BytesStore publicKey = keys.publicKey.store; // or some other manually managed area
BytesStore secretKey = keys.secretKey.store; // or some other manually managed area

// Sender signs the message using their secret key
Signature.sign(signed, message, secretKey);

// Recipient verifies the message using the sender's public key
Signature.verify(unsigned, signed, publicKey);

Signatures for Multi-Part Messages

In addition to single-message signing as described above, it is also possible to generate a single secure signature for a collection of several arbitrarily-sized message parts. Where possible, the single-message interface described above should be preferred, however where multi-part messages are required Chronicle-Salt provides the Signature.MultiPart wrapper class.

Once a MultiPart message is initialised, individual message parts can be added using:

void Signtaure.MultiPart.add( BytesStore message );

The signature for the collection of messages is then obtained using the signer’s secret key:

// option 1 (preferred): pass strongly-typed secret key
BytesStore Signature.MultiPart.sign( SecretKey sk );

// option 2: pass explicit BytesStore representing secret key
BytesStore Signature.MultiPart.sign( BytesStore secretkey );

Once sign has been called the MultiPart object should not be used further without first being reset:

void Signature.MultiPart.reset();

The recipient/verifier builds a multi-part wrapper in a similar fashion, then verifies the collection using the signer’s public key by calling:

// option 1 (preferred): pass strongly-typed public key
void Signature.MultiPart.verify( BytesStore signature, PublicKey pk );

// option 2: pass explicit BytesStore representing public key
void Signature.MultiPart.verify( BytesStore signatire, BytesStore publickey );

Verify will throw an IllegalStateException if the call fails for any reason. Once verify has been called the MultiPart object should not be used further without first being reset.

The following is a complete example illustrating signing and subsequently verifying a collection of messages:

BytesStore message1 = NativeBytesStore.from( "Message part1");
BytesStore message2 = NativeBytesStore.from( "Message part2");
BytesStore message3 = NativeBytesStore.from( "Message part3");

// Generate the signer's key pair
Signature.KeyPair keys = Signature.KeyPair.generate();

// Initialise a MultiPart wrapper, and add multiple messages
Signature.MultiPart multi = new Signature.MultiPart();
multi.add( message1 );
multi.add( message2 );
multi.add( message3 );

// Generate the signature for the collection of messages using the signer's secret key
BytesStore signature = multi.sign( keys.secretKey );

// Initialise the recipient's MultiPart wrapper, and add the received multiple message parts
Signature.MultiPart recv = new Signature.MultiPart();
recv.add( message1 );
recv.add( message2 );
recv.add( message3 );

// Verify the signature using the signer's public key
recv.verify( signature, keys.publicKey );

Extracting Seed and Public Key from Signature Secret Key

The secret key used for public-key message signing includes within it the public key and seed (either random or deterministic as relevant). Given a signer’s secret key, these seed can be extracted as follows:

BytesStore extractSeed();                 // extract seed; creates and returns a suitable BytesStore
BytesStore extractSeed( BytesStore seed); // extract seed to provided BytesStore (which is returned)

The public key can be extracted similarly:

BytesStore extractPublicKey();                // extract public key; creates and returns suitable BytesStore
BytesStore extractPublicKey( BytesStore pk ); // extract public key to provided BytesStore

For example:

BytesStore seed = NativeBytesStore.from( "01234567890123456789012345678901" );
Signature.KeyPair keys = Signature.KeyPair.deterministic(seed);

BytesStore seed2 = keys.secretKey.extractSeed();
System.out.println(DatatypeConverter.printHexBinary(seed2.toByteArray()) );

BytesStore pk = keys.secretKey.extractPublicKey();
System.out.println(DatatypeConverter.printHexBinary(pk.toByteArray()) );

prints

3031323334353637383930313233343536373839303132333435363738393031
7BC3079518ED11DA0336085BF6962920FF87FB3C4D630A9B58CB6153674F5DD6

SHA-2 Hashing

A given message or data of arbitrary size can be deterministically hashed to a 32-byte or 64-byte value via standard SHA-256 or SHA-512 respectively. Chronicle-Salt supports various options for invoking the SHA-2 hash functions, as well as a multi-part API to support generating a hash for a sequence of messages/data.

SHA-256 Hash

The SHA-256 hash of a message can be obtained using one of the following:

BytesStore SHA2.sha256( BytesStore message );                    // creates a BytesStore to hold the hash
BytesStore SHA2.sha256( BytesStore result, BytesStore message ); // place hash in provided BytesStore

Alternatively, a SHA-256 hash can be appended to a given Bytes handle:

void SHA2.appendSha256( Bytes<?> output, BytesStore message );

For example:

    BytesStore message = "example message";

    // Option 1: Create and return the BytesStore
    BytesStore hash = SHA2.sha256( message );

    // Option 2: Use an existing BytesStore to hold the result
    BytesStore hash = ...;
    SHA2.sha256( hash, message );

    // Option 3: append the hash to a given Bytes handle
    Bytes<?> hash256 = Bytes.allocateDirect(SHA2.HASH_SHA256_BYTES));
    SHA2.appendSha256(hash256, message);

SHA-512 Hash

The SHA-512 hash of a message can be obtained using one of the following:

BytesStore SHA2.sha512( BytesStore message );                    // creates a BytesStore to hold the hash
BytesStore SHA2.sha512( BytesStore result, BytesStore message ); // place hash in provided BytesStore

Alternatively, a SHA-512 hash can be appended to a given Bytes handle:

void SHA2.appendSha512( Bytes<?> output, BytesStore message );

For example:

    BytesStore message = "example message";

    // Option 1: Create and return the BytesStore
    BytesStore hash = SHA2.sha512( message );

    // Option 2: Use an existing BytesStore to hold the result
    BytesStore hash = ...;
    SHA2.sha512( hash, message );

    // Option 3: append the hash to a given Bytes handle
    Bytes<?> hash512 = Bytes.allocateDirect(SHA2.HASH_SHA512_BYTES));
    SHA2.appendSha512(hash512, message);

Multi-Part SHA-256 and SHA-512 Hashing

In addition to single-message hashing as described above, it is also possible to generate a single hash for a collection of several arbitrarily-sized message parts. Multi-part hashing is provided by the SHA2.MultiPartSHA256 and SHA2.MultiPartSHA512 wrapper classes.

Once a MultiPartSHA256 or 512 message is initialised, individual message parts can be added using:

void SHA2.MultiPartSHA256.add( BytesStore message );
void SHA2.MultiPartSHA512.add( BytesStore message );

The hash for the collection of messages is then obtained as follows:

BytesStore SHA2.MultiPartSHA256.hash();                   // create a BytesStore to hold the hash
BytesStore SHA2.MultiPartSHA256.hash( BytesStore result); // place hash in provided BytesStore

BytesStore SHA2.MultiPartSHA512.hash();                   // create a BytesStore to hold the hash
BytesStore SHA2.MultiPartSHA512.hash( BytesStore result); // place hash in provided BytesStore

Once hash has been called the MultiPartSHA256 or 512 object should not be used further without first being reset:

void SHA2.MultiPartSHA256.reset();
void SHA2.MultiPartSHA512.reset();

The following is a complete example generating the SHA-256 and SHA-512 hash of a collection of messages:

BytesStore message1 = NativeBytesStore.from( "abcdefgh");
BytesStore message2 = NativeBytesStore.from( "ijklmnop");
BytesStore message3 = NativeBytesStore.from( "qrstuvwxyz");

// Initialise a MultiPartSHA256 wrapper
SHA2.MultiPartSHA256 multi256 = new SHA2.MultiPartSHA256();
multi256.add( message1 );
multi256.add( message2 );
multi256.add( message3 );

// Generate the single SHA-256 hash of the set of messages
BytesStore hash256 = multi256.hash();

// Initialise a MultiPartSHA512 wrapper
SHA2.MultiPartSHA512 multi512 = new SHA2.MultiPartSHA512();
multi512.add( message1 );
multi512.add( message2 );
multi512.add( message3 );

// Generate the single SHA-512 hash of the set of messages
BytesStore hash512 = multi512.hash();

System.out.println("SHA256: " + DatatypeConverter.printHexBinary(hash256.toByteArray()));
System.out.println("SHA512: " + DatatypeConverter.printHexBinary(hash512.toByteArray()));

The above prints the following, matching the hashes of the full message abcdefghijklmnopqrstuvwxyz:

SHA256: 71C480DF93D6AE2F1EFAD1447C66C9525E316218CF51FC8D9ED832F2DAF18B73

SHA512: 4DBFF86CC2CA1BAE1E16468A05CB9881C97F1753BCE3619034898FAA1AABE429
        955A1BF8EC483D7421FE3C1646613A59ED5441FB0F321389F77F48A879C7B1F1

Blake2b Hashing

A given message or data of arbitrary size can be deterministically hashed to a 32-byte or 64-byte value via standard Blake2b and varying the output size length accordingly. Chronicle-Salt supports various options for invoking the Blake2b hash functions (or generic hash functions as they are called in the Sodium API), as well as a multi-part API to support generating a hash for a sequence of messages/data.

Blake2b-256 Hash

The Blake2b-256 hash of a message can be obtained using one of the following:

BytesStore Blake2b.hash256( BytesStore message );                    // creates a BytesStore to hold the hash
BytesStore Blake2b.hash256( BytesStore result, BytesStore message ); // place hash in provided BytesStore

Alternatively, a Blake2b-256 hash can be appended to a given Bytes handle:

void Blake2b.append256( Bytes<?> output, BytesStore message );

For example:

    BytesStore message = "example message";

    // Option 1: Create and return the BytesStore
    BytesStore hash = Blake2b.hash256( message );

    // Option 2: Use an existing BytesStore to hold the result
    BytesStore hash = ...;
    Blake2b.hash256( hash, message );

    // Option 3: append the hash to a given Bytes handle
    Bytes<?> hash256 = Bytes.allocateDirect(Blake2b.HASH_BLAKE2B_256_BYTES));
    Blake2b.append256(hash256, message);

Blake2b-512 Hash

The Blake2b-512 hash of a message can be obtained using one of the following:

BytesStore Blake2b.hash512( BytesStore message );                    // creates a BytesStore to hold the hash
BytesStore Blake2b.hash512( BytesStore result, BytesStore message ); // place hash in provided BytesStore

Alternatively, a Blake2b-512 hash can be appended to a given Bytes handle:

void Blake2b.append512( Bytes<?> output, BytesStore message );

For example:

    BytesStore message = "example message";

    // Option 1: Create and return the BytesStore
    BytesStore hash = Blake2b.hash512( message );

    // Option 2: Use an existing BytesStore to hold the result
    BytesStore hash = ...;
    Blake2b.hash512( hash, message );

    // Option 3: append the hash to a given Bytes handle
    Bytes<?> hash512 = Bytes.allocateDirect(Blake2b.HASH_BLAKE2B_512_BYTES));
    Blake2b.append512(hash512, message);

Multi-Part Blake2b-256 and Blake2b-512 Hashing

In addition to single-message hashing as described above, it is also possible to generate a single hash for a collection of several arbitrarily-sized message parts. Multi-part hashing is provided by the Blake2b.MultiPart256 and Blake2b.MultiPart512 wrapper classes.

Once a MultiPart256 or MultiPart512 message is initialised, individual message parts can be added using:

void Blake2b.MultiPart256.add( BytesStore message );
void Blake2b.MultiPart512.add( BytesStore message );

The hash for the collection of messages is then obtained as follows:

BytesStore Blake2b.MultiPart256.hash();                   // create a BytesStore to hold the hash
BytesStore Blake2b.MultiPart256.hash( BytesStore result); // place hash in provided BytesStore

BytesStore Blake2b.MultiPart512.hash();                   // create a BytesStore to hold the hash
BytesStore Blake2b.MultiPart512.hash( BytesStore result); // place hash in provided BytesStore

Once hash has been called the MultiPart256 or MultiPart512 object should not be used further without first being reset:

void Blake2b.MultiPart256.reset();
void Blake2b.MultiPart512.reset();

The following is a complete example generating the Blake2b-256 and Blake2b-512 hash of a collection of messages:

BytesStore message1 = NativeBytesStore.from( "abcdefgh");
BytesStore message2 = NativeBytesStore.from( "ijklmnop");
BytesStore message3 = NativeBytesStore.from( "qrstuvwxyz");

// Initialise a MultiPart256 wrapper
Blake2b.MultiPart256 multi256 = new Blake2b.MultiPart256();
multi256.add( message1 );
multi256.add( message2 );
multi256.add( message3 );

// Generate the single Blake2b-256 hash of the set of messages
BytesStore hash256 = multi256.hash();

// Initialise a MultiPart512 wrapper
Blake2b.MultiPart512 multi512 = new Blake2b.MultiPart512();
multi512.add( message1 );
multi512.add( message2 );
multi512.add( message3 );

// Generate the single Blake2b-512 hash of the set of messages
BytesStore hash512 = multi512.hash();

System.out.println("Blake2b 256: " + DatatypeConverter.printHexBinary(hash256.toByteArray()));
System.out.println("Blake2b 512: " + DatatypeConverter.printHexBinary(hash512.toByteArray()));

The above prints the following, matching the hashes of the full message abcdefghijklmnopqrstuvwxyz:

Blake2b-256: 117AD6B940F5E8292C007D9C7E7350CD33CF85B5887E8DA71C7957830F536E7C

Blake2b-512: C68EDE143E416EB7B4AAAE0D8E48E55DD529EAFED10B1DF1A61416953A2B0A56
             66C761E7D412E6709E31FFE221B7A7A73908CB95A4D120B8B090A87D1FBEDB4C

Benchmark

The library can be run in parallel to improve throughput

Table 1. Ed25519 performance

system

sign

verify

i7-7700HQ 4 core

64K/s

26K/s

i7-7820X 8 core

206K/s

87K/s

E5-2650 v4 24 core

306K/s

154K/s

E5-2650 v4 24 core, batch

506K/s

202K/s

Table 2. SHA-2 performance

system

sha256 of 55 bytes

sha512 of 110 bytes

i7-7820X 8 core

21 M/s

17 M/s

E5-2650 v4 24 core

39 M/s

31 M/s

Error message

java.lang.UnsatisfiedLinkError: net.openhft.chronicle.salt.Bridge.crypto_box_easy(JJJJJJ)I You need to run mvn install to build libbridge first.

Key Terms

Chronicle Bytes

A similar purpose to Java NIO’s ByteBuffer, but with added extenstions. View Chronicle-Bytes here

Cryptography

The practice of hiding information using a mix of mathematics, computer science and electrical engineering.

Decrypt

Decoding a message using a public key.

Digital Signature

A digital code attached to an electronically transmitted document to verify its contents and the senders identity.

Ed25519 Signatures

A public key signature system

Hash

A mathematical algorithm that maps data of arbitrary size, to a bit string of a fixed size (a hash). It is designed to be a one way function i.e. a function which is infeasible to revert.

Hexadecimal Dump - To be updated.

Libsodium

A modern, easy-to-use software library for encryption, decryption, signatures, password hashing and more.

Private Key

A variable used within an algorithm to encrypt and decrypt code. Mathematically linked to a public key.

Public Key

A large numerical value used to encrypt data.

Scalability

The capability of a system, network or process to handle large amounts of work, or its potential to be enlarged to accommodate growth.

Seed

A number or other value that has been generated by software using one or more values.

Throughput

The amount of data successfully moved from one place to another in a given timeframe.