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)
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.
-
JDK 8 or higher
For a more detailed explanation, please refer to RbNaCl’s documentation
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 buildlibbridge
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.
-
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.
-
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.
-
Elliptic-curve signatures.
-
Engineered at several levels of design and implementation to achieve very high speeds without compromising security.
-
Random bytes are used to form a private key as it ensures they key is difficult to guess, therefore more secure.
Bytes<?> rand = Ed25519.generateRandomBytes(32);
-
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.
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. |
System.out.println(privateKey.toHexString());
System.out.println(publicKey.toHexString());
System.out.println(secretKey.toHexString());
Prints something like
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
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.
|
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
|
Bytes<?> signatureAndMsg = Bytes.allocateElasticDirect();
Ed25519.sign(signatureAndMsg, message, secretKey);
Ed25519.sign(signatureAndMsg, message2, secretKey); // (1)
-
signatureAndMsg now contains two messages
Bytes<?> signatureAndMsg = Bytes.allocateElasticDirect();
Ed25519.sign(signatureAndMsg, message, secretKey); // (1)
client.write(signatureAndMsg);
signatureAndMsg.clear()
Ed25519.sign(signatureAndMsg, message2, secretKey); // (2)
client.write(signatureAndMsg);
-
first message signed
-
signatureAndMsg contains one message
Once a message has been signed, you can verify it using the public key alone.
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 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
).
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.
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);
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();
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();
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);
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();
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);
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);
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 );
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
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.
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);
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);
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
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.
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);
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);
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
The library can be run in parallel to improve throughput
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 |
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 |
- 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.