/* * EncFS Java Library * Copyright (C) 2013 encfs-java authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ package org.mrpdaemon.sec.encfs; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; import java.security.spec.KeySpec; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.IllegalBlockSizeException; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; /** * Class containing static methods implementing volume key functionality. */ public final class VolumeKey { private VolumeKey() { } /** * Derive volume key for the given config and password-based key/IV data. */ private static byte[] encryptVolumeKey(EncFSConfig config, byte[] pbkdf2Data, byte[] volKeyData) throws EncFSUnsupportedException, EncFSInvalidConfigException, EncFSCorruptDataException { // Prepare key/IV for decryption int keySizeInBytes = config.getVolumeKeySizeInBits()/8; byte[] passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); byte[] passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, keySizeInBytes+EncFSVolume.IV_LENGTH_IN_BYTES); Key passKey = EncFSCrypto.newKey(passKeyData); // Encrypt the volume key data Mac mac = encryptVolumeKeyData(passKey); // Calculate MAC for the key byte[] mac32 = EncFSCrypto.mac32(mac, volKeyData, new byte[0]); byte[] cipherVolKeyData = EncFSCrypto.encryptKeyData(volKeyData, passIvData, passKey, mac, mac32); // Combine MAC with key data byte[] result = new byte[mac32.length+cipherVolKeyData.length]; System.arraycopy(mac32, 0, result, 0, mac32.length); System.arraycopy(cipherVolKeyData, 0, result, mac32.length, cipherVolKeyData.length); return result; } private static Mac encryptVolumeKeyData(Key passKey) throws EncFSUnsupportedException, EncFSInvalidConfigException { Mac mac; try { mac = EncFSCrypto.newMac(passKey); } catch (InvalidKeyException e) { throw new EncFSInvalidConfigException(e); } return mac; } /** * Derive volume key for the given config and password-based key/IV data. */ static byte[] decryptVolumeKey(EncFSConfig config, byte[] pbkdf2Data) throws EncFSChecksumException, EncFSInvalidConfigException, EncFSCorruptDataException, EncFSUnsupportedException { // Decode Base64 encoded ciphertext data byte[] cipherVolKeyData; try { cipherVolKeyData = EncFSBase64.decode(config .getBase64EncodedVolumeKey()); } catch (IOException e) { throw new EncFSInvalidConfigException("Corrupt key data in config"); } byte[] encryptedVolKey = Arrays.copyOfRange(cipherVolKeyData, 4, cipherVolKeyData.length); // Prepare key/IV for decryption int keySizeInBytes = config.getVolumeKeySizeInBits()/8; byte[] passKeyData = Arrays.copyOfRange(pbkdf2Data, 0, keySizeInBytes); byte[] passIvData = Arrays.copyOfRange(pbkdf2Data, keySizeInBytes, keySizeInBytes+EncFSVolume.IV_LENGTH_IN_BYTES); Key passKey = EncFSCrypto.newKey(passKeyData); byte[] ivSeed = Arrays.copyOfRange(cipherVolKeyData, 0, 4); // Decrypt the volume key data Mac mac = encryptVolumeKeyData(passKey); byte[] clearVolKeyData = decryptVolumeKeyData(encryptedVolKey, passIvData, passKey, ivSeed, mac); // Perform checksum computation byte[] mac32 = EncFSCrypto.mac32(mac, clearVolKeyData, new byte[0]); if (!Arrays.equals(ivSeed, mac32)) { throw new EncFSChecksumException("Volume key checksum mismatch"); } return clearVolKeyData; } /** * Decrypt volume key data. */ private static byte[] decryptVolumeKeyData(byte[] encryptedVolKey, byte[] passIvData, Key passKey, byte[] ivSeed, Mac mac) throws EncFSUnsupportedException, EncFSInvalidConfigException, EncFSCorruptDataException { byte[] clearVolKeyData; try { clearVolKeyData = StreamCrypto.streamDecrypt( StreamCrypto.newStreamCipher(), mac, passKey, passIvData, ivSeed, encryptedVolKey); } catch (InvalidAlgorithmParameterException e) { throw new EncFSInvalidConfigException(e); } catch (IllegalBlockSizeException e) { throw new EncFSCorruptDataException(e); } catch (BadPaddingException e) { throw new EncFSCorruptDataException(e); } return clearVolKeyData; } /** * Derive password-based key from input/config parameters using PBKDF2. */ static byte[] deriveKeyDataFromPassword(EncFSConfig config, String password, EncFSPBKDF2Provider pbkdf2Provider) throws EncFSInvalidConfigException, EncFSUnsupportedException { // Decode base 64 salt data byte[] cipherSaltData; try { cipherSaltData = EncFSBase64.decode(config.getBase64Salt()); } catch (IOException e) { throw new EncFSInvalidConfigException("Corrupt salt data in config"); } if (pbkdf2Provider==null) { // Execute PBKDF2 to derive key data from the password SecretKeyFactory f; try { f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); } catch (NoSuchAlgorithmException e) { throw new EncFSUnsupportedException(e); } KeySpec ks = new PBEKeySpec(password.toCharArray(), cipherSaltData, config.getIterationForPasswordKeyDerivationCount(), config.getVolumeKeySizeInBits() +EncFSVolume.IV_LENGTH_IN_BYTES*8); SecretKey pbkdf2Key; try { pbkdf2Key = f.generateSecret(ks); } catch (InvalidKeySpecException e) { throw new EncFSInvalidConfigException(e); } return pbkdf2Key.getEncoded(); } else { return pbkdf2Provider.doPBKDF2(password.length(), password, cipherSaltData.length, cipherSaltData, config.getIterationForPasswordKeyDerivationCount(), (config.getVolumeKeySizeInBits()/8) +EncFSVolume.IV_LENGTH_IN_BYTES); } } /** * Encodes the given volume key using the supplied password parameters. */ static void encodeVolumeKey(EncFSConfig config, String password, byte[] volKey, EncFSPBKDF2Provider pbkdf2Provider) throws EncFSInvalidConfigException, EncFSUnsupportedException, EncFSCorruptDataException { SecureRandom random = new SecureRandom(); config.setSaltLengthBytes(20); // Generate random salt byte[] salt = new byte[20]; random.nextBytes(salt); config.setBase64Salt(EncFSBase64.encodeBytes(salt)); // Get password key data byte[] pbkdf2Data = deriveKeyDataFromPassword(config, password, pbkdf2Provider); // Encode volume key byte[] encodedVolKey = encryptVolumeKey(config, pbkdf2Data, volKey); config.setEncodedKeyLengthInBytes(encodedVolKey.length); config.setBase64EncodedVolumeKey(EncFSBase64.encodeBytes(encodedVolKey)); } }