diff --git a/main/crypto-aes/pom.xml b/main/crypto-aes/pom.xml index bedaf1888f..761cc84f61 100644 --- a/main/crypto-aes/pom.xml +++ b/main/crypto-aes/pom.xml @@ -17,12 +17,23 @@ crypto-aes Cryptomator cryptographic module (AES) Provides stream ciphers and filename pseudonymization functions. + + + 1.51 + org.cryptomator crypto-api + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java index dd0225c2b1..1c2121b740 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java @@ -12,7 +12,6 @@ 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; @@ -20,8 +19,6 @@ 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; @@ -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; @@ -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; @@ -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/. @@ -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); } @@ -154,8 +141,8 @@ 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); @@ -163,13 +150,14 @@ public void encryptMasterKey(OutputStream out, CharSequence password) throws IOE 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); } @@ -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; @@ -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); + } } } diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java index e2bb643b88..2d9c7b360a 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/AesCryptographicConfiguration.java @@ -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. @@ -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; - } diff --git a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java index be0508f155..330730d6f4 100644 --- a/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java +++ b/main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/KeyFile.java @@ -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() {