Java talks SJCL
While investigating Nextcloud / Passman solutions as a secure, self-hosted password manager, I stumbled upon a question I couldn't find a ready-made answers anywhere. In that scenario, passwords are encrypted client-side by the Stanford Javascript Crypto Library employing AES encryption in CCM mode. The question: How to decode the data in Java?
A little demo of the output can be generated using the SJCL demo page:
The JSON string on the bottom contains the encrypted message (I used the highly creative string secret message
and the even more creative password password
) and everything necessary to decode it short from the password. This is the output:
{
"iv":"5olvvndy2od23jThveFRyw==",
"v":1,
"iter":1000,
"ks":128,
"ts":64,
"mode":"ccm",
"adata":"",
"cipher":"aes",
"salt":"parYv9E1LYM=",
"ct":"KdMdpQnfbYAnRtr6Kcr566LIsTzX/w=="
}
Short explanation: iv
is the initialization vector, or nonce (c.f. image above on the right), iter
are the number of iterations, ks
is the keysize, ts
(tag size) is the authentication strength, salt
is the salt used, mode
and cipher
are self-explanatory, and finally ct
is the ciphertext, or encrypted message. The values for iv
, salt
and ct
are Base64 encoded, as is the whole JSON object for transmission and storage: Passman would transfer this string:
eyJpdiI6IjVvbHZ2bmR5Mm9kMjNqVGh2ZUZSeXc9PSIsDQoidiI6MSwNCiJpdGVyIjoxMDAwLA0KImtzIjoxMjgsDQoidHMiOjY0LA0KIm1vZGUiOiJjY20iLA0KImFkYXRhIjoiIiwNCiJjaXBoZXIiOiJhZXMiLA0KInNhbHQiOiJwYXJZdjlFMUxZTT0iLA0KImN0IjoiS2RNZHBRbmZiWUFuUnRyNktjcjU2NkxJc1R6WC93PT0ifQ==
Now the task at hand was to decrypt this string back to the original message. Here is a Java class (using Bouncycastle in the lates release) which demonstrates how to decrypt this particular String:
package name.degering.blog.sjcl;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Decoder;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.json.JSONObject;
public class SJCLDecryptTest {
// Simply prints out the decoded string.
public static void main(String[] args) throws Exception {
System.out.println(new SJCLDecryptTest().decrypt(
"eyJpdiI6IjVvbHZ2bmR5Mm9kMjNqVGh2ZUZSeXc9PSIsDQoidiI6MSwNC"
+ "iJpdGVyIjoxMDAwLA0KImtzIjoxMjgsDQoidHMiOjY0LA0KIm1vZGUiOi"
+ "JjY20iLA0KImFkYXRhIjoiIiwNCiJjaXBoZXIiOiJhZXMiLA0KInNhbHQ"
+ "iOiJwYXJZdjlFMUxZTT0iLA0KImN0IjoiS2RNZHBRbmZiWUFuUnRyNktj"
+ "cjU2NkxJc1R6WC93PT0ifQ==",
"password"));
}
// We need a Base64 decoder, so lets get one (and reuse it)
private Decoder d = Base64.getDecoder();
// The function takes the Base64 encoded JSON object and the password as arguments
private String decrypt(String encodedJSON, String password) throws Exception {
// Decode the encoded JSON and create a JSON Object from it
JSONObject j = new JSONObject(new String(d.decode(encodedJSON)));
// We need the salt, the IV and the cipher text;
// all of them need to be Base64 decoded
byte[] salt=d.decode(j.getString("salt"));
byte[] iv=d.decode(j.getString("iv"));
byte[] cipherText=d.decode(j.getString("ct"));
// Also, we need the keySize and the iteration count
int keySize = j.getInt("ks"), iterations = j.getInt("iter");
// Now, SJCL doesn't use the whole IV in CCM mode;
// the length L depends on the length of the cipher text and is
// either 2 (< 32768 bit length),
// 3 (< 8388608 bit length) or
// 4 (everything larger).
// c.f. https://github.com/bitwiseshiftleft/sjcl/blob/master/core/ccm.js#L60
int lol = 2;
if (cipherText.length >= 1<<16) lol++;
if (cipherText.length >= 1<<24) lol++;
// Cut the IV to the appropriate length, which is 15 - L
iv = Arrays.copyOf(iv, 15-lol);
// Crypto stuff.
// First, we need the secret AES key,
// which is generated from password and salt
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(),
salt, iterations, keySize);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
// Now it's time to decrypt.
Cipher cipher = Cipher.getInstance("AES/CCM/NoPadding",
new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
// Return the final result after converting it to a string.
return new String(cipher.doFinal(cipherText));
}
}
When running the above code, a console output says secret message
. VoilĂ , we have successfully decrypted a message encrypted by SJCL!
P.S.: I might move this code to Github as soon at it matures.
Addendum: It seems I can't properly use Google as I would've found the Java SJCL Compatibility Library earlier. It describes in detail what needs to be done in Java, although there are still some limitations (e.g. the calculation of the length L is still marked as TODO). Also, in this issue the author recomend using Mozilla's Rhino to invoke the SJCL library from Java.