Skip to content

Commit

Permalink
- fixes #8: Using Scrypt key derivation function now
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Stenzel committed Jan 4, 2015
1 parent 3f2ef3a commit 0e288f0
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 70 deletions.
11 changes: 11 additions & 0 deletions main/crypto-aes/pom.xml
Expand Up @@ -17,12 +17,23 @@
<artifactId>crypto-aes</artifactId>
<name>Cryptomator cryptographic module (AES)</name>
<description>Provides stream ciphers and filename pseudonymization functions.</description>

<properties>
<bouncycastle.version>1.51</bouncycastle.version>
</properties>

<dependencies>
<dependency>
<groupId>org.cryptomator</groupId>
<artifactId>crypto-api</artifactId>
</dependency>

<!-- Bouncycastle -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>

<!-- Commons -->
<dependency>
Expand Down
Expand Up @@ -12,16 +12,13 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryStream.Filter;
import java.nio.file.Path;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -37,9 +34,7 @@
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.DestroyFailedException;
import javax.security.auth.Destroyable;
Expand All @@ -48,6 +43,7 @@
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.crypto.generators.SCrypt;
import org.cryptomator.crypto.AbstractCryptor;
import org.cryptomator.crypto.CryptorIOSupport;
import org.cryptomator.crypto.exceptions.DecryptFailedException;
Expand All @@ -68,14 +64,6 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
*/
private static final SecureRandom SECURE_PRNG;

/**
* Factory for deriveing keys. Defaults to PBKDF2/HMAC-SHA1.
*
* @see PKCS #5, defined in RFC 2898
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory
*/
private static final SecretKeyFactory PBKDF2_FACTORY;

/**
* Defined in static initializer. Defaults to 256, but falls back to maximum value possible, if JCE Unlimited Strength Jurisdiction
* Policy Files isn't installed. Those files can be downloaded here: http://www.oracle.com/technetwork/java/javase/downloads/.
Expand All @@ -102,10 +90,9 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo

static {
try {
PBKDF2_FACTORY = SecretKeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
SECURE_PRNG = SecureRandom.getInstance(PRNG_ALGORITHM);
final int maxKeyLength = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
AES_KEY_LENGTH_IN_BITS = (maxKeyLength >= MAX_MASTER_KEY_LENGTH_IN_BITS) ? MAX_MASTER_KEY_LENGTH_IN_BITS : maxKeyLength;
AES_KEY_LENGTH_IN_BITS = (maxKeyLength >= PREF_MASTER_KEY_LENGTH_IN_BITS) ? PREF_MASTER_KEY_LENGTH_IN_BITS : maxKeyLength;
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Algorithm should exist.", e);
}
Expand Down Expand Up @@ -154,22 +141,23 @@ public Aes256Cryptor() {
public void encryptMasterKey(OutputStream out, CharSequence password) throws IOException {
try {
// derive key:
final byte[] userSalt = randomData(SALT_LENGTH);
final SecretKey kek = pbkdf2(password, userSalt, PBKDF2_PW_ITERATIONS, AES_KEY_LENGTH_IN_BITS);
final byte[] kekSalt = randomData(SCRYPT_SALT_LENGTH);
final SecretKey kek = scrypt(password, kekSalt, SCRYPT_COST_PARAM, SCRYPT_BLOCK_SIZE, AES_KEY_LENGTH_IN_BITS);

// encrypt:
final Cipher encCipher = aesKeyWrapCipher(kek, Cipher.WRAP_MODE);
byte[] wrappedPrimaryKey = encCipher.wrap(primaryMasterKey);
byte[] wrappedSecondaryKey = encCipher.wrap(hMacMasterKey);

// save encrypted masterkey:
final KeyFile key = new KeyFile();
key.setIterations(PBKDF2_PW_ITERATIONS);
key.setKeyLength(AES_KEY_LENGTH_IN_BITS);
key.setPrimaryMasterKey(wrappedPrimaryKey);
key.setHMacMasterKey(wrappedSecondaryKey);
key.setSalt(userSalt);
objectMapper.writeValue(out, key);
final KeyFile keyfile = new KeyFile();
keyfile.setScryptSalt(kekSalt);
keyfile.setScryptCostParam(SCRYPT_COST_PARAM);
keyfile.setScryptBlockSize(SCRYPT_BLOCK_SIZE);
keyfile.setKeyLength(AES_KEY_LENGTH_IN_BITS);
keyfile.setPrimaryMasterKey(wrappedPrimaryKey);
keyfile.setHMacMasterKey(wrappedSecondaryKey);
objectMapper.writeValue(out, keyfile);
} catch (InvalidKeyException | IllegalBlockSizeException ex) {
throw new IllegalStateException("Invalid hard coded configuration.", ex);
}
Expand All @@ -188,21 +176,21 @@ public void encryptMasterKey(OutputStream out, CharSequence password) throws IOE
public void decryptMasterKey(InputStream in, CharSequence password) throws DecryptFailedException, WrongPasswordException, UnsupportedKeyLengthException, IOException {
try {
// load encrypted masterkey:
final KeyFile key = objectMapper.readValue(in, KeyFile.class);
final KeyFile keyfile = objectMapper.readValue(in, KeyFile.class);

// check, whether the key length is supported:
final int maxKeyLen = Cipher.getMaxAllowedKeyLength(AES_KEY_ALGORITHM);
if (key.getKeyLength() > maxKeyLen) {
throw new UnsupportedKeyLengthException(key.getKeyLength(), maxKeyLen);
if (keyfile.getKeyLength() > maxKeyLen) {
throw new UnsupportedKeyLengthException(keyfile.getKeyLength(), maxKeyLen);
}

// derive key:
final SecretKey kek = pbkdf2(password, key.getSalt(), key.getIterations(), key.getKeyLength());
final SecretKey kek = scrypt(password, keyfile.getScryptSalt(), keyfile.getScryptCostParam(), keyfile.getScryptBlockSize(), AES_KEY_LENGTH_IN_BITS);

// decrypt and check password by catching AEAD exception
final Cipher decCipher = aesKeyWrapCipher(kek, Cipher.UNWRAP_MODE);
SecretKey primary = (SecretKey) decCipher.unwrap(key.getPrimaryMasterKey(), AES_KEY_ALGORITHM, Cipher.SECRET_KEY);
SecretKey secondary = (SecretKey) decCipher.unwrap(key.getPrimaryMasterKey(), HMAC_KEY_ALGORITHM, Cipher.SECRET_KEY);
SecretKey primary = (SecretKey) decCipher.unwrap(keyfile.getPrimaryMasterKey(), AES_KEY_ALGORITHM, Cipher.SECRET_KEY);
SecretKey secondary = (SecretKey) decCipher.unwrap(keyfile.getPrimaryMasterKey(), HMAC_KEY_ALGORITHM, Cipher.SECRET_KEY);

// everything ok, assign decrypted keys:
this.primaryMasterKey = primary;
Expand Down Expand Up @@ -259,19 +247,19 @@ private byte[] randomData(int length) {
return result;
}

private SecretKey pbkdf2(CharSequence password, byte[] salt, int iterations, int keyLengthInBits) {
final int pwLen = password.length();
final char[] pw = new char[pwLen];
CharBuffer.wrap(password).get(pw, 0, pwLen);
private SecretKey scrypt(CharSequence password, byte[] salt, int costParam, int blockSize, int keyLengthInBits) {
// use sb, as password.toString's implementation is unknown
final StringBuilder sb = new StringBuilder(password);
final byte[] pw = sb.toString().getBytes();
try {
final KeySpec specs = new PBEKeySpec(pw, salt, iterations, keyLengthInBits);
final SecretKey pbkdf2Key = PBKDF2_FACTORY.generateSecret(specs);
final SecretKey aesKey = new SecretKeySpec(pbkdf2Key.getEncoded(), AES_KEY_ALGORITHM);
return aesKey;
} catch (InvalidKeySpecException ex) {
throw new IllegalStateException("Specs are hard-coded.", ex);
final byte[] key = SCrypt.generate(pw, salt, costParam, blockSize, 1, keyLengthInBits / Byte.SIZE);
return new SecretKeySpec(key, AES_KEY_ALGORITHM);
} finally {
Arrays.fill(pw, (char) 0);
// destroy copied bytes of the plaintext password:
Arrays.fill(pw, (byte) 0);
for (int i = 0; i < password.length(); i++) {
sb.setCharAt(i, (char) 0);
}
}
}

Expand Down
Expand Up @@ -11,32 +11,29 @@
interface AesCryptographicConfiguration {

/**
* Number of bytes used as seed for the PRNG.
* Number of bytes used as salt, where needed.
*/
int PRNG_SEED_LENGTH = 16;
int SCRYPT_SALT_LENGTH = 8;

/**
* Number of bytes of the master key. Should be the maximum possible AES key length to provide best security.
* Scrypt CPU/Memory cost parameter.
*/
int MAX_MASTER_KEY_LENGTH_IN_BITS = 256;
int SCRYPT_COST_PARAM = 1 << 14;

/**
* Number of bytes used as salt, where needed.
* Scrypt block size (affects memory consumption)
*/
int SALT_LENGTH = 8;
int SCRYPT_BLOCK_SIZE = 8;

/**
* 0-filled salt.
* Number of bytes of the master key. Should be the maximum possible AES key length to provide best security.
*/
byte[] EMPTY_SALT = new byte[SALT_LENGTH];
int PREF_MASTER_KEY_LENGTH_IN_BITS = 256;

/**
* Algorithm used for key derivation as defined in RFC 2898 / PKCS #5.
*
* SHA1 will deprecate soon, but the main purpose of PBKDF2 is waisting CPU cycles, so cryptographically strong hash algorithms are not
* necessary here. See also http://crypto.stackexchange.com/a/11017
* Number of bytes used as seed for the PRNG.
*/
String KEY_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA1";
int PRNG_SEED_LENGTH = 16;

/**
* Algorithm used for random number generation.
Expand Down Expand Up @@ -81,9 +78,4 @@ interface AesCryptographicConfiguration {
*/
int FILE_NAME_IV_LENGTH = 5;

/**
* Number of iterations for key derived from user pw. High iteration count for better resistance to bruteforcing.
*/
int PBKDF2_PW_ITERATIONS = 1000;

}
Expand Up @@ -4,30 +4,39 @@

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder(value = {"salt", "iv", "iterations", "keyLength", "primaryMasterKey", "hMacMasterKey"})
@JsonPropertyOrder(value = {"scryptSalt", "scryptCostParam", "scryptBlockSize", "keyLength", "primaryMasterKey", "hMacMasterKey"})
public class KeyFile implements Serializable {

private static final long serialVersionUID = 8578363158959619885L;
private byte[] salt;
private int iterations;
private byte[] scryptSalt;
private int scryptCostParam;
private int scryptBlockSize;
private int keyLength;
private byte[] primaryMasterKey;
private byte[] hMacMasterKey;

public byte[] getSalt() {
return salt;
public byte[] getScryptSalt() {
return scryptSalt;
}

public void setSalt(byte[] salt) {
this.salt = salt;
public void setScryptSalt(byte[] scryptSalt) {
this.scryptSalt = scryptSalt;
}

public int getIterations() {
return iterations;
public int getScryptCostParam() {
return scryptCostParam;
}

public void setIterations(int iterations) {
this.iterations = iterations;
public void setScryptCostParam(int scryptCostParam) {
this.scryptCostParam = scryptCostParam;
}

public int getScryptBlockSize() {
return scryptBlockSize;
}

public void setScryptBlockSize(int scryptBlockSize) {
this.scryptBlockSize = scryptBlockSize;
}

public int getKeyLength() {
Expand Down

0 comments on commit 0e288f0

Please sign in to comment.