A Java implementation of the [Spore Protocol] (Add link to the REST API here)
Download the JAR file and add it to your project's path. You will also need to following dependencies:
To build JAR file:
$ mvn clean install
JAR located in .../target directory.
- rootofqaos.com
- rootofentropy.com
- entropy.2keys.io
Any of the servers from the Known Spore Servers section offer good quality entropy through the Spore Protocol. Once the server is chosen, simply pass it's address to the SporeClient
constructor.
String serverAddress = "http://127.0.0.41:8099/eaasp/";
SporeClient sporeClient = new SporeClient(serverAddress);
Once the SporeClient
is instantiated, a simple way of testing the connection is to simply send a getInfo
request. This will also give us basic information on the server, namely it's name and the quantity of entropy it serves.
JSONObject infoResponse = sporeClient.doInfoRequest();
int entropySize = infoResponse.getInt("entropySize");
Now that we know the connection is working, we can request entropy from the server. If we care about verifying the validity of the response, we should send a challenge, however, this is not required.
- With a challenge
SecureRandom rbg = new SecureRandom();
byte[] randomBytes = new byte[16];
rbg.nextBytes(randomBytes);
String challenge = Base64.getUrlEncoder().encodeToString(randomBytes);
JSONObject entropyResponse = sporeClient.doEntropyRequest(challenge);
Here we generate a random challenge. This is the best practice to avoid repeating the challenge. If lots of requests are expected to be sent, increasing the length of the challenge would decrease the probability of challenge collisions.
- Without a challenge
JSONObject entropyResponse = sporeClient.doEntropyRequest(null);
Now that we have received good quality entropy, we can seed our local source of randomness. It is best practice to first perform some hash to combine the received entropy with some local entropy. Indeed, if one was to simply seed the local entropy source with compromised entropy, the system would become deterministic to the malicious party.
Here a simple XOR
is aplied with localy generated bytes. However, one could also use a more cryptographically secure hash function such as SHA256
.
String b64entropy = entropyResponse.getString("entropy");
byte[] entropy = Base64.getUrlDecoder().decode(b64entropy);
randomBytes = new byte[entropySize];
rbg.nextBytes(randomBytes);
for (int i = 0; i < entropySize; i++) {
entropy[i] ^= randomBytes[i];
}
rbg.setSeed(entropy);
Many IoT devices with extremely limited resources would stop here, now having an entropy pool at worst similar to before the operation and at best of high quality. However, some devices with more resources or with tasks critically relying on cryptographic security will want to verify and authenticate the entropy.
The first obvious step is to confirm that the entropy was indeed generated for our request, and not anyone else's. To do that, we can simply make sure the returned challenge matches the one we sent.
String receivedChallenge = entropyResponse.getString("challenge");
if (challenge.equals(receivedChallenge)) {
System.out.println("Challenges match");
} else {
throw new Exception("Challenges do not match");
}
Another verification that can be made is to verify that the entropy sent is fresh. This can easily be done with the help of the returned timestamp.
long timestamp = entropyResponse.getLong("timestamp");
long localTime = System.currentTimeMillis() / 1000L;
long freshnessWindowSec = 60;
if (Math.abs(timestamp - localTime) <= freshnessWindowSec) {
System.out.println("Entropy response is fresh");
} else {
throw new Exception("Entropy response is not fresh");
}
Here we allow a one minute window.
Using the JWT received in the response, we can authenticate it using the server's public signature. If it is not known, it can be obtained with a getCertChain
request.
JSONObject certResponse = sporeClient.doCertificateChainRequest();
byte[] certChain = certResponse.getString("certificateChain").getBytes();
InputStream is = new ByteArrayInputStream(certChain);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate) cf.generateCertificate(is);
PublicKey publicKey = certificate.getPublicKey();
String token = entropyResponse.getString("JWT");
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withClaim("challenge", challenge)
.withClaim("timestamp", timestamp)
.withClaim("entropy", b64entropy)
.build();
verifier.verify(token);
It is also recommended to make sure the certificate chain's root is a trusted CA.
If you found a bug, have a feature request, or a design recommendation, please open a new issue at this repository's issues section. If you find a security vulnerablility, please do not report it on the public GitHub issue tracker but instead contact Crypto4a directly.