If you haven't already, go read SexpressionFormats.
The advanced S-expression format for a public key is:
(public-key (rsa (e #010001#) (n |AOQjBj+wSG/BlAlir8Xuz62Hv3xAfAxJQeMl0kf93oWFzEcbK03h0kP3ueX4FaMMvsBYEqT uCK7h1CQHvuZrsRmjZmoP08zTOfrYYstU9wHW0QrPvTPrWlh52YXygS3NE8fHLOQkjwdCVf1 CHubDxTnovrO7j7xBOsbeMgJArrvv|)))
Once you understand canonical S-expressions, it's fairly easy to understand how to generate the above in canonical form. The only extra knowledge you need is the calls used to extract the public key's exponent and modulus. In Java, it goes as follows (taken from the file [SpkiDemo.java]):
public static void writeSpkiPublicKey(RSAPublicKey k, OutputStream os)
throws IOException {
BigInteger e; /** Public key's exponent */
BigInteger n; /** Public key's modulus */
SexpWriter xw; /** Wraps given OutputStream */
/* Extract exponent and modulus */
e = k.getPublicExponent();
n = k.getModulus();
/* Begin writing public key in SPKI canonical form */
xw = new SexpWriter(os);
xw.startList();
xw.writeString("public-key");
xw.startList();
xw.writeString("rsa");
//xw.writeString("rsa-pkcs1-md5"); // older versions of SDSI libraries
//xw.writeString("rsa-pkcs1-sha1"); // had stricter key types
/* Write exponent, e.g., (e |bytes in exponent|) */
xw.startList();
xw.writeString("e");
xw.writeData(e.toByteArray());
xw.endList();
/* Write modulus, e.g., (n |bytes in modulus|) */
xw.startList();
xw.writeString("n");
xw.writeData(n.toByteArray());
xw.endList();
/* Close S-expression lists */
xw.endList(); // (rsa ... )
xw.endList(); // (public-key ... )
}
NOTE: See the full [SpkiDemo.java] to see which import statements you need. Also see [SexpWriter.java].
To generate the hash of a public key, you could call the above method to write the key's canonical form to a ByteArrayOutputStream, use standard Java crypto routines to hash it, then write out the hash value in its proper S-expression format. Java code for this:
public static void writeHash(byte[] data, String algorithm, OutputStream os)
throws NoSuchAlgorithmException, IOException {
MessageDigest md;
byte[] hashValue;
SexpWriter xw;
/* Calculate the hash value */
md = MessageDigest.getInstance(algorithm);
hashValue = md.digest(data);
/* Write an S-expression, e.g., (hash md5 |bytes in hashValue|) */
xw = new SexpWriter(os);
xw.startList();
xw.writeString("hash");
xw.writeString(algorithm);
xw.writeData(hashValue);
xw.endList();
}
Running the above code on the public key shown at the top of the page yields (when converted to advanced form):
(hash md5 |9WgBTLBGk6kIIvJVwZLbAg==|)
The hex hash used by Greenpass in, e.g., the greenpass_hash cookie, is also generated by hashing the canonical form of a public key's SPKI format. In this case, however, the hash is converted directly to a hexadecimal string rather than being put into an S-expression. Hopefully the following code will illustrate:
public static String getHexHash(byte[] data, String algorithm)
throws NoSuchAlgorithmException {
MessageDigest md;
byte[] hashValue;
int[] nibbles; /** "nibbles" are half-bytes, i.e., 4-bits */
char[] hexDigits;
/* Calculate hash value */
md = MessageDigest.getInstance(algorithm);
hashValue = md.digest(data);
/* Populate "nibbles" array */
nibbles = new int[2*hashValue.length];
for (int i = 0; i < hashValue.length; ++i) {
nibbles[2*i] = (hashValue[i] & 0xff) >> 4; //high 4 bits
nibbles[2*i+1] = (hashValue[i] & 0x0f); //low 4 bits
}
/* Populate hexDigits array */
hexDigits = new char[nibbles.length];
for (int i = 0; i < nibbles.length; ++i) {
if (nibbles[i] < 10)
hexDigits[i] = (char) ('0' + nibbles[i]);
else
hexDigits[i] = (char) ('a' + nibbles[i] - 10);
}
/* Return hexDigits array as a String */
return new String(hexDigits);
}
Running the above on the public key used in our previous examples yields the string:
f568014cb04693a90822f255c192db02
Fortunately, Java's crypto libraries are complete enough that it's possible to extract public keys from X.509 certs and pass them off the the above writeSpkiPublicKey() method fairly easily. The [ExtractKey.java] utility discussed below does it using the following code snippet:
InputStream is;
OutputStream os;
CertificateFactory cf;
X509Certificate cert;
RSAPublicKey k;
/* ... code to set up InputStream and OutputStream omitted ... */
/* read X.509 certificate from input */
cf = CertificateFactory.getInstance("X.509");
cert = (X509Certificate) cf.generateCertificate(is);
is.close();
/* extract public key */
k = (RSAPublicKey) cert.getPublicKey();
SpkiDemo.writeSpkiPublicKey(k, os);
A sample utility for generating all of the above hash formats can be found in [ExtractKey.java]. Download [SexpWriter.java], [SpkiDemo.java], and [ExtractKey.java] to an otherwise empty directory and compile them using:
$ javac *.javaUsage of the utility is as follows:
usage: java ExtractKey <options...>
Extracts the public key from an X.509 certificate stored in a PEM file and
outputs either a SPKI/SDSI public key, a SPKI/SDSI hash of the key, or a
hexadecimal hash of the key.
-i <input file> specify input file (default: stdin)
-o <output file> specify output file (default: stdout)
-k output full SPKI/SDSI public key (default)
-h <hash algorithm> output hash of key using given algorithm
(available algorithms: "md5" or "sha1")
-x <hash algorithm> output hexadecimal hash of key using given algorithm
The three examples shown previously on this page (full SPKI public key, SPKI hash, and hex hash) were all generated using this utility as follows (see SexpressionFormats for how to get, compile, and use Ron Rivest's sexp utility):
$ java ExtractKey -i mydartmouthcert.pem | sexp -a $ java ExtractKey -h md5 -i mydartmouthcert.pem | sexp -a $ java ExtractKey -x md5 -i mydartmouthcert.pem