Java Wrapper
Requirements
The Keychain Core Java wrapper requirements are:
-
Java 8 or newer
-
A version of Keychain Core compatible with the 2.4.10 wrapper
Installation
The Java wrapper package has many folders, but we only care about 2 files inside - the keychain.jar
file and libkeychain-jni.so
(or DLL) file.
These 2 files may be found in separate packages within the keychain-core-release-generic/keychain-java subtree, one for "java" and the other for "jni".
|
These 2 files must be downloaded and placed in a folder that Java and the shared library loader are aware of. We recommend putting the JAR with the other project-specific JARs (e.g. in a local lib/
folder) and the shared object next to the Keychain Core library, wherever that has been installed.
For example, suppose you installed Keychain Core into ~/.keychain
. Then you should drop the libkeychain-jni.so
file into ~/.keychain/lib/
:
~/.keychain/
bin/
config/
data/
include/
lib/
libkeychain-jni.so <--- put it here so it will be found with libkeychain.so
libkeychain.so
scripts/
Then when you make your project, drop keychain.jar
into the lib/
folder within it.
~/projects/KeychainProject
lib/
keychain.jar <--- goes here with your other cool JARs
src/
com/
your/
company/
Main.java
Classpath
Remember to set the Java Classpath so it finds keychain.jar
. If you have placed it alongside your other JARs then generally (unless you explicitly add named JARs) it will be found, otherwise you’ll need to add it.
Shared Libraries
If you have placed the libkeychain-jni.so
file alongside libkeychain.so
, then the steps in getting started will automatically take care of the JNI shared library being discovered too. If you opt to place it in another location, make sure you tell ldconfig
where to find it, or else prefix your run command with LD_LIBRARY_PATH=<path_to_libkeychain-jni.so>
.
Reference
Full reference docs for the Java wrapper are available in the Java reference section of this site. Go here to get more details about specific classes and methods.
Usage
In Java, we have to explicitly load the shared libraries before we can use them. This is best done in a static
block in the class that will first use a Keychain library, as the static
block will run before any constructor or methods get a chance to.
public class KeychainTest {
static {
System.loadLibrary("keychain");
System.loadLibrary("keychain-jni");
}
}
Of course you can opt to do this even earlier to shift the load time to a less-critical section where it’s ok to take a few milliseconds.
After this, a Gateway
is required. It is created in 3 steps.
-
Validate the Keychain license with the Keychain server (requires internet access)
-
Load the database and create the
Gateway
-
Seed the
Gateway
so it generates appropriate pseudo-random keys
These 3 steps are summarized below:
import io.keychain.core.Gateway;
import io.keychain.core.Context;
...
Context keychainContext = Gateway.initializeDb(cfgFile, dbFile, dropSqlFile, createSqlFile);
Gateway gateway = new Gateway(keychainContext, new Refreshable() {
public void onRefresh() {
// logic to be executed on new blocks
}
});
gateway.seed()
And of course, you will want to start the monitor thread within Gateway
to actually receive the blockchain updates the Refreshable
is created to respond to:
gateway.onStart(); // creates the thread
gateway.onResume(); // starts the thread
// When the UI or Activity goes out of scope momentarily, for example:
gateway.onPause();
// When you're done with the Gateway:
gateway.onStop();
You are all set to start using Keychain!
Examples and Patterns
Here are some quick patterns that you will find yourself using quite frequently.
Managing Personas
The Gateway
is most useful with a persona - in fact, without a persona created and confirmed most features are not available. Therefore we want to create a persona immediately if one does not already exist.
If there is at least 1 mature persona in the |
Creating a persona is a very quick operation, but in order to use it it must be confirmed, which means the underlying blockchain upon which the persona’s DID is stored has had enough transaction confirmations to trust that the data is impervious to rollback. Therefore our pattern is to check for an active persona, then if none found we create and wait for confirmation.
// building on the previous example with 'gateway' as the Gateway object
if (!gateway.maturePersonaExists()) {
Persona persona = gateway.createPersona("Momo", "Taro", SecurityLevel.MEDIUM);
}
while (!gateway.maturePersonaExists()) {
try {
LOGGER.info("Sleeping until mature persona exists");
Thread.sleep(11000); // random time
} catch (Exception e) {
LOGGER.severe("Exception waiting for mature persona: " + e.getMessage());
}
}
// Now we have an active persona!
Persona activePersona = gateway.getActivePersona();
After creating a persona, you will be able to access its Uri
to share with others, letting them add you as a Contact
.
Managing Contacts
With an active persona, you will often find yourself wanting to modify the contacts for it - add, remove, and get all are common operations.
Interestingly, Contact
does not have a public constructor for developers. So to add a contact, you do it by Uri
, which can be created by a string representation. We use this in the example below.
The string below is a non-functional example and will fail if you use it. Always only use strings from |
// Create 2 new contacts from a Uri string
Contact alice = gateway.addContact("Alice", "crypto", new Uri("1234:1;5678:1"));
Contact bob = gateway.addContact("Bob", "hacker", new Uri("4545:1;7878:1"));
// Should have 2 elements
ArrayList<Contact> contacts = gateway.getContacts();
gateway.deleteContact(bob); // this will not impact 'contacts' yet
// Bob is gone, only 1 friend now
contacts = gateway.getContacts()
Signing and Verifying
A critical part of any cryptographic application is signing and verifying signatures. In Keychain you always sign with the active persona’s signature, and you verify against the active persona’s contacts and other personas that are in the Gateway
. Let’s see a few simple examples!
Adding Signatures
First, let’s have Alice create and sign a message.
String message = "Hello, world!";
byte[] signedData = gateway.sign(message.getBytes("UTF-16"), CharEncoding.UTF16, true);
Next, Bob receives it and verifies the signatures (there’s only 1)
VerificationResult result = gateway.verify(signedData);
ArrayList<Verification> verifications = result.getVerifications();
CharEncoding encoding = result.getEncoding(); // CharEncoding.UTF16
byte[] msg = result.getClearText();
assert verifications.size() == 1;
if (encoding == CharEncoding.UTF16) {
plaintext = new String(msg, "UTF-16"); // "Hello, world!"
}
Verification aliceVerification = verifications.get(0);
// Various assertions to show what you can do with the Verification
assert aliceVerification.isVerified() == true;
assert aliceVerification.signerIsKnown() == true;
Imagine we are in a system where multiple people must attest to a message for it to be treated by some logic - a consensus-driven network. Then maybe Bob wants to add his signature to the message and pass it along to the next person or broadcast to the network as a whole:
byte[] bobSignedToo = gateway.addSignature(signedData, false); // not an approval
The resulting message is now 'Hello, world!'
with 2 signatures on it. You can envision a validator contact who does similar logic as Bob did when verifying, only he will only act when he sees 3 valid signatures in the verifications
list.
VerificationResult result = gateway.verify(bobSignedToo);
ArrayList<Verification> verifications = result.getVerifications();
// quick-fail check
if (verifications.size() < 3) {
// Failure logic
}
for (Verification v : result.getVerifications()) {
// Business logic
}
Encrypting and Decrypting
Lastly let’s show a couple quick encryption/decryption patterns.
Basic String
The first place to start is with a simple message, like Alice’s 'Hello, world!'
. Let’s encrypt it for all of her contacts to read:
String message = 'Hello, world!'
String encryptedData = gateway.encrypt(gateway.getContacts(), message.getBytes("UTF-16"));
Now her contacts, for example Bob from above, can decrypt and read it:
byte[] cleartextData = gateway.decrypt(encryptedData);
String cleartext = new String(cleartextData, "UTF-16");
Alice can also encrypt for just a select few contacts by altering the list she passes to encrypt
.