Jacs is a Java library and command-line tool for encrypting streams and files using a passphrase, including support for PBKDF2, SCrypt, BCrypt, AES, Explicit IV, and HMAC-based integrity checking.
Jacs extends the Java CipherInputStream and CipherOutputStream classes to support Explicit IV and HMAC integrity checking.
Jacs is integrated with common key derivation algorithms, including PBKDF2, SCrypt, and BCrypt.
Jacs supports AES out-of-the-box via JCE. Other cipher algorithms can easily be added if the Bouncy Castle library is present.
Classes:
-
CipherOutputStreamIVMAC -- Encrypt data to binary, using the format Explicit IV, ciphertext data, and HMAC signature.
-
CipherInputStreamIVMAC -- Decrypt data from binary, using the format Explicit IV, ciphertext data, and HMAC signature.
-
CipherOutputStreamIVMACBase64 -- Like CipherOutputStreamIVMAC, but encrypt to base64.
-
CipherInputStreamIVMACBase64 -- Like CipherInputStreamIVMAC, but decrypt to base64.
Key Derivation: PBKDF2, SCrypt, or BCrypt, caller may choose key derivation algorithm strength
Cipher algorithms: AES-256-CBC with PKCS5 Padding. Other algorithms can easily be added if the Bouncy Castle crypto library is present.
IV: Explicit, never reused, generated from strong PRNG
HMAC: SHA256 (using encrypt-then-MAC approach, where leading IV + all ciphertext is signed)
Encrypted stream format:
[ 16 bytes -- IV ]
[ n bytes -- ciphertext ]
[ 32 bytes -- HMAC-SHA256 signature of IV, ciphertext ]
Encrypt:
// outStream is an OutputStream that
// ciphertext (base64) will be written to.
CipherMacSpec spec = JacsAlgs.getInstance("PBKDF2-SHA1-AES256-HMAC-SHA256");
spec.init("mypassword", 16); // 2^16 PBKDF2 iterations
CipherOutputStreamIVMACBase64 cos = new CipherOutputStreamIVMACBase64(outStream, spec);
// You can now write plaintext to cos like a
// CipherOutputStream object. Don't forget to
// close cos when done, as that triggers the
// writing of the HMAC signature.
Decrypt:
// inStream is an InputStream that
// will read ciphertext (base64).
CipherInputStreamIVMACBase64 cis = new CipherInputStreamIVMACBase64(inStream, "mypassword", false);
// You can now read plaintext from cis like
// a CipherInputStream object. Don't forget
// to close cis when done, as that triggers
// the HMAC signature verification.
See src/main/java/net/openvpn/jacs/Jacs.java for a more complete example that implements the jacs command line tool.
Java's CipherInputStream and CipherOutputStream class (from the JCE) lack two important capabilities:
-
Support for using a randomized "Explicit IV" to ensure that identical plaintexts encrypt to different ciphertexts even when the same key is used.
-
Integrity checking when decrypting ciphertext, to ensure that the ciphertext was not forged or tampered with (Jacs uses encrypt-then-MAC approach, where leading IV + all ciphertext is signed).
Both of these capabilities are considered essential to modern security protocols and are present in TLS 1.1 and higher, ESP (Used in IPSec), and OpenVPN.
The Jacs library adds these capabilities via the new classes CipherInputStreamIVMAC and CipherOutputStreamIVMAC, which are intended to be drop-in replacements for CipherInputStream and CipherOutputStream.
When using CipherInputStreamIVMAC and CipherOutputStreamIVMAC, the encrypted stream format is expanded to include both Explicit IV and HMAC signature.
For example, when using PBKDF2-SHA1-AES256-HMAC-SHA256, the stream format is as follows:
[ 16 bytes -- IV ]
[ n bytes -- ciphertext ]
[ 32 bytes -- HMAC-SHA256 signature of IV, ciphertext ]
The Jacs library generates encryption keys from a user-supplied passphrase, using one of several key derivation or "key-stretching" algorithms. Using a key derivation algorithm with a sufficiently high iteration count is essential when deriving encryption keys from user-supplied passphrases, as most users are incapable of remembering a password with sufficient entropy to foil modern password cracking schemes.
Jacs supports the following key derivation algorithms:
-
PBKDF2-SHA1 -- A NIST standard, also published as an Internet Standard (RFC 2898).
Pros: Developed by RSA labs, well-researched and standardized.
Cons: Modern password cracking methods using off-the-shelf GPU hardware can brute force SHA1-hashed passwords at the rate of 2.3 billion per second.
-
PBKDF2-SHA512 -- While not standardized, this algorithm is identical to PBKDF2-SHA1, except that SHA512 is used in place of SHA1.
Pros: SHA512 is more difficult than SHA1 for an attacker to accelerate with off-the-shelf GPU hardware because it relies extensively on 64-bit integer operations.
Cons: not officially standardized.
-
Bcrypt -- Uses a variation of the Blowfish cipher algorithm to derive keys.
Pros: actively used since 1999 without known vulnerabilities. Less amenable to GPU acceleration due to memory requirements.
Cons: not officially standardized, lacks the depth of security analysis that has gone into PBKDF2-SHA1.
-
Scrypt -- Intentionally designed to be resource-intensive both in memory requirements and computational complexity, to thwart attempts by attackers to accelerate the algorithm using GPUs or specialized hardware such as ASICs or FPGAs. Scrypt is published as an internet draft.
Pros: Difficult or impossible for an attacker to accelerate using GPUs or specialized hardware, no known vulnerabilities.
Cons: Scrypt is a recent algorithm, first presented in 2009, and has less of a track-record than other algorithms in common use. Like Bcrypt, it also lacks the depth of security analysis that has gone into PBKDF2-SHA1.
Jacs implementation notes:
Because Jacs provides integrity checking via HMAC, the key derivation methods used to generate a key from a passphrase must actually generate two keys: one for the cipher key and one for the HMAC key. These are the methods used by Jacs to generate these keys for each key derivation method:
-
PBKDF2-SHA1: two distinct constant salts (16 bytes each) are used with the PBKDF2-SHA1 algorithm to transform the passphrase into the cipher key and HMAC key. The iteration count is taken as 2^S where S is the "strength" parameter provided by the user. Alternatively, if strength >= 64, it will be treated as an iteration count.
-
PBKDF2-SHA512: a single 32 byte constant salt and passphrase are used as input to the PBKDF2-SHA512 algorithm to derive a 512 bit key. The PBKDF2-SHA512 algorithm is implemented identically to PBKDF2-SHA1, except SHA512 is used as the hashing algorithm. The 512 bit key produced by PBKDF2-SHA512 is then split into two 256 bit keys to be used as cipher and HMAC keys. Like PBKDF2-SHA1 above, the iteration count is taken as 2^S where S is the "strength" parameter provided by the user. Alternatively, if strength >= 64, it will be treated as an iteration count.
-
Bcrypt: a single 32 byte constant salt and passphrase are used to derive a 24-byte key using the Bcrypt algorithm, which is then expanded to 64 bytes using one round of SHA512. The resulting key is then split into two 32-byte keys to be used as cipher and HMAC keys. The "strength" parameter provided by the user is passed to the Bcrypt algorithm as the "log_rounds" parameter.
-
Scrypt: a single 32 byte constant salt and passphrase are used by SCrypt to derive a 64-byte key, which is then split into two 32-byte keys to be used as cipher and HMAC keys. Scrypt parameters are set as follows:
N -- CPU cost parameter : set to 2^S where S is the "strength" parameter provided by the user.
r -- memory cost parameter : set to 8
p -- parallelization parameter : set to 1
PRNG and entropy:
-
For encryption, a random IV of size equal to the cipher block size is generated using java.security.SecureRandom and passed to the Java Cipher.init method as an IvParameterSpec.
-
All constant salts used in Jacs were generated from /dev/urandom
Validation:
To validate the correctness of the Java/JCE code used in the PBKDF2-SHA1-AES256-HMAC-SHA256 implementation, an alternative implementation supporting key derivation and decryption is provided using C/OpenSSL in test/dec.c. The automated test script (test/go) verifies the consistency of plaintext encrypted with Jacs and decrypted using the C/OpenSSL code.
Jacs also includes standard junit-based unit tests, which are provided for Jacs, PBKDF2, SCrypt, and BCrypt. The test/go script also verifies the junit tests.
Sources:
-
Bcrypt implementation -- Damien Miller https://github.com/jeremyh/jBCrypt
-
Scrypt and PBKDF2 implementations -- Will Glozer https://github.com/wg/scrypt
$ ./build
Standalone executable will be written to ./jacs
$ ./jacs
jacs: symmetric encryption tool
usage:
encrypt : jacs E <alg> <password> <strength> <infile> <outfile>
decrypt : jacs D <alg> <password> <strength> <infile> <outfile>
encrypt to base64 : jacs E64 <alg> <password> <strength> <infile> <outfile>
decrypt from base64 : jacs D64[A] <password> <infile> <outfile>
algs:
PBKDF2-SHA1-AES256-HMAC-SHA256
PBKDF2-SHA512-AES256-HMAC-SHA256
SCRYPT-AES256-HMAC-SHA256
BCRYPT-AES256-HMAC-SHA256
PBKDF2-SHA1-AES256-HMAC-SHA1
PBKDF2-SHA512-AES256-HMAC-SHA1
SCRYPT-AES256-HMAC-SHA1
BCRYPT-AES256-HMAC-SHA1
password : password or '.' to prompt from stdin without echo
strength : strength of password derivation (1 to 32 for exponential
strength or 64 and higher for iteration count)
infile : input pathname or 'stdin'
outfile : output pathname or 'stdout'
'A' suffix : for D64, pass through input if not encrypted
To run test suite:
$ test/go
Note that the test suite not only verifies consistency of the Jacs library, but also verifies decryption using a C/OpenSSL tool instead of one based on Java/JCE to verify the consistency of Jacs encryption file format using an alternative implementation.