Skip to content

Commit

Permalink
- fixes #7
Browse files Browse the repository at this point in the history
- removes any use of CBC mode (might affect issue #9)
  • Loading branch information
Sebastian Stenzel committed Dec 30, 2014
1 parent 4f91adb commit d8c9279
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
private static final SecretKeyFactory PBKDF2_FACTORY;

/**
* Defined in static initializer. Defaults to 256, but falls back to maximum value possible, if JCE isn't installed. JCE can be
* installed from here: http://www.oracle.com/technetwork/java/javase/downloads/.
* 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/.
*/
private static final int AES_KEY_LENGTH;

Expand Down Expand Up @@ -227,11 +227,11 @@ private byte[] randomData(int length) {
return result;
}

private SecretKey pbkdf2(byte[] password, byte[] salt, int iterations, int keyLength) {
final char[] pw = new char[password.length];
private SecretKey deriveSecretKeyFromMasterKey() {
final char[] pw = new char[masterKey.length];
try {
byteToChar(password, pw);
return pbkdf2(CharBuffer.wrap(pw), salt, iterations, keyLength);
byteToChar(masterKey, pw);
return pbkdf2(CharBuffer.wrap(pw), EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
} finally {
Arrays.fill(pw, (char) 0);
}
Expand Down Expand Up @@ -271,7 +271,7 @@ private long crc32Sum(byte[] source) {
@Override
public String encryptPath(String cleartextPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
try {
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
final SecretKey key = this.deriveSecretKeyFromMasterKey();
final String[] cleartextPathComps = StringUtils.split(cleartextPath, cleartextPathSep);
final List<String> encryptedPathComps = new ArrayList<>(cleartextPathComps.length);
for (final String cleartext : cleartextPathComps) {
Expand Down Expand Up @@ -300,27 +300,30 @@ public String encryptPath(String cleartextPath, char encryptedPathSep, char clea
* {@link FileNamingConventions#LONG_NAME_FILE_EXT}.
*/
private String encryptPathComponent(final String cleartext, final SecretKey key, CryptorIOSupport ioSupport) throws IllegalBlockSizeException, BadPaddingException, IOException {
final Cipher cipher = this.cipher(FILE_NAME_CIPHER, key, EMPTY_IV, Cipher.ENCRYPT_MODE);
final byte[] ivRandomPart = randomData(FILE_NAME_IV_LENGTH);
final ByteBuffer iv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
iv.put(ivRandomPart);
final Cipher cipher = this.cipher(FILE_NAME_CIPHER, key, iv.array(), Cipher.ENCRYPT_MODE);
final byte[] cleartextBytes = cleartext.getBytes(Charsets.UTF_8);
final byte[] encryptedBytes = cipher.doFinal(cleartextBytes);
final String encrypted = ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes) + BASIC_FILE_EXT;
final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(ivRandomPart) + IV_PREFIX_SEPARATOR + ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes);

if (encrypted.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) {
final String crc32 = Long.toHexString(crc32Sum(encrypted.getBytes()));
if (ivAndCiphertext.length() + BASIC_FILE_EXT.length() > ENCRYPTED_FILENAME_LENGTH_LIMIT) {
final String crc32 = Long.toHexString(crc32Sum(ivAndCiphertext.getBytes()));
final String metadataFilename = crc32 + METADATA_FILE_EXT;
final LongFilenameMetadata metadata = this.getMetadata(ioSupport, metadataFilename);
final String alternativeFileName = crc32 + LONG_NAME_PREFIX_SEPARATOR + metadata.getOrCreateUuidForEncryptedFilename(encrypted).toString() + LONG_NAME_FILE_EXT;
final String alternativeFileName = crc32 + LONG_NAME_PREFIX_SEPARATOR + metadata.getOrCreateUuidForEncryptedFilename(ivAndCiphertext).toString() + LONG_NAME_FILE_EXT;
this.storeMetadata(ioSupport, metadataFilename, metadata);
return alternativeFileName;
} else {
return encrypted;
return ivAndCiphertext + BASIC_FILE_EXT;
}
}

@Override
public String decryptPath(String encryptedPath, char encryptedPathSep, char cleartextPathSep, CryptorIOSupport ioSupport) {
try {
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
final SecretKey key = this.deriveSecretKeyFromMasterKey();
final String[] encryptedPathComps = StringUtils.split(encryptedPath, encryptedPathSep);
final List<String> cleartextPathComps = new ArrayList<>(encryptedPathComps.length);
for (final String encrypted : encryptedPathComps) {
Expand All @@ -337,21 +340,27 @@ public String decryptPath(String encryptedPath, char encryptedPathSep, char clea
* @see #encryptPathComponent(String, SecretKey, CryptorIOSupport)
*/
private String decryptPathComponent(final String encrypted, final SecretKey key, CryptorIOSupport ioSupport) throws IllegalBlockSizeException, BadPaddingException, IOException {
final String ciphertext;
final String ivAndCiphertext;
if (encrypted.endsWith(LONG_NAME_FILE_EXT)) {
final String basename = StringUtils.removeEnd(encrypted, LONG_NAME_FILE_EXT);
final String crc32 = StringUtils.substringBefore(basename, LONG_NAME_PREFIX_SEPARATOR);
final String uuid = StringUtils.substringAfter(basename, LONG_NAME_PREFIX_SEPARATOR);
final String metadataFilename = crc32 + METADATA_FILE_EXT;
final LongFilenameMetadata metadata = this.getMetadata(ioSupport, metadataFilename);
ciphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid));
ivAndCiphertext = metadata.getEncryptedFilenameForUUID(UUID.fromString(uuid));
} else if (encrypted.endsWith(BASIC_FILE_EXT)) {
ciphertext = StringUtils.removeEndIgnoreCase(encrypted, BASIC_FILE_EXT);
ivAndCiphertext = StringUtils.removeEndIgnoreCase(encrypted, BASIC_FILE_EXT);
} else {
throw new IllegalArgumentException("Unsupported path component: " + encrypted);
}

final Cipher cipher = this.cipher(FILE_NAME_CIPHER, key, EMPTY_IV, Cipher.DECRYPT_MODE);
final String ivRandomPartStr = StringUtils.substringBefore(ivAndCiphertext, IV_PREFIX_SEPARATOR);
final String ciphertext = StringUtils.substringAfter(ivAndCiphertext, IV_PREFIX_SEPARATOR);
final byte[] ivRandomPart = ENCRYPTED_FILENAME_CODEC.decode(ivRandomPartStr);
final ByteBuffer iv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
iv.put(ivRandomPart);

final Cipher cipher = this.cipher(FILE_NAME_CIPHER, key, iv.array(), Cipher.DECRYPT_MODE);
final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext);
final byte[] cleartextBytes = cipher.doFinal(encryptedBytes);
return new String(cleartextBytes, Charsets.UTF_8);
Expand Down Expand Up @@ -394,7 +403,7 @@ public Long decryptedFile(SeekableByteChannel encryptedFile, OutputStream plaint
}

// derive secret key and generate cipher:
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
final SecretKey key = this.deriveSecretKeyFromMasterKey();
final Cipher cipher = this.cipher(FILE_CONTENT_CIPHER, key, countingIv.array(), Cipher.DECRYPT_MODE);

// read content
Expand Down Expand Up @@ -425,7 +434,7 @@ public Long decryptRange(SeekableByteChannel encryptedFile, OutputStream plainte
encryptedFile.position(SIZE_OF_LONG + AES_BLOCK_LENGTH + beginOfFirstRelevantBlock);

// derive secret key and generate cipher:
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
final SecretKey key = this.deriveSecretKeyFromMasterKey();
final Cipher cipher = this.cipher(FILE_CONTENT_CIPHER, key, countingIv.array(), Cipher.DECRYPT_MODE);

// read content
Expand All @@ -445,7 +454,7 @@ public Long encryptFile(InputStream plaintextFile, SeekableByteChannel encrypted
countingIv.position(0);

// derive secret key and generate cipher:
final SecretKey key = this.pbkdf2(masterKey, EMPTY_SALT, PBKDF2_MASTERKEY_ITERATIONS, AES_KEY_LENGTH);
final SecretKey key = this.deriveSecretKeyFromMasterKey();
final Cipher cipher = this.cipher(FILE_CONTENT_CIPHER, key, countingIv.array(), Cipher.ENCRYPT_MODE);

// 8 bytes (file size: temporarily -1):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ interface AesCryptographicConfiguration {
byte[] EMPTY_SALT = new byte[SALT_LENGTH];

/**
* Algorithm used for key derivation.
* 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
*/
String KEY_FACTORY_ALGORITHM = "PBKDF2WithHmacSHA1";

Expand All @@ -52,14 +55,14 @@ interface AesCryptographicConfiguration {
*
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
*/
String MASTERKEY_CIPHER = "AES/CBC/PKCS5Padding";
String MASTERKEY_CIPHER = "AES/CTR/NoPadding";

/**
* Cipher specs for file name encryption.
*
* @see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#Cipher
*/
String FILE_NAME_CIPHER = "AES/CBC/PKCS5Padding";
String FILE_NAME_CIPHER = "AES/CTR/NoPadding";

/**
* Cipher specs for content encryption. Using CTR-mode for random access.
Expand All @@ -74,9 +77,10 @@ interface AesCryptographicConfiguration {
int AES_BLOCK_LENGTH = 16;

/**
* 0-filled initialization vector.
* Number of non-zero bytes in the IV used for file name encryption. Less means shorter encrypted filenames, more means higher entropy.
* Maximum length is {@value #AES_BLOCK_LENGTH}.
*/
byte[] EMPTY_IV = new byte[AES_BLOCK_LENGTH];
int FILE_NAME_IV_LENGTH = 4;

/**
* Number of iterations for key derived from user pw. High iteration count for better resistance to bruteforcing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ interface FileNamingConventions {
*/
String BASIC_FILE_EXT = ".aes";

/**
* Prefix in front of the actual encrypted file name used as IV.
*/
String IV_PREFIX_SEPARATOR = "_";

/**
* For plaintext file names > {@value #ENCRYPTED_FILENAME_LENGTH_LIMIT} chars.
*/
Expand Down

0 comments on commit d8c9279

Please sign in to comment.