Java talks SJCL

by zemion

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:

SJCL online demo

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:


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:


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:


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(
                        + "iJpdGVyIjoxMDAwLA0KImtzIjoxMjgsDQoidHMiOjY0LA0KIm1vZGUiOi"
                        + "JjY20iLA0KImFkYXRhIjoiIiwNCiJjaXBoZXIiOiJhZXMiLA0KInNhbHQ"
                        + "iOiJwYXJZdjlFMUxZTT0iLA0KImN0IjoiS2RNZHBRbmZiWUFuUnRyNktj"
                        + "cjU2NkxJc1R6WC93PT0ifQ==",

    // 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.
        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.

Function get_magic_quotes_gpc() is deprecated
The error has been logged in /anchor/errors.log
Uncaught Exception

Uncaught Exception

Function get_magic_quotes_gpc() is deprecated


system/boot.php on line 34


#0 [internal function]: System\error::shutdown()
#1 {main}