Add tests for AccountManager and AndroidAccountManager.

This commit is contained in:
akwizgran
2018-07-27 15:29:42 +01:00
parent adbfa26364
commit 58d09d0742
4 changed files with 298 additions and 41 deletions

View File

@@ -69,12 +69,16 @@ class AndroidAccountManager extends AccountManagerImpl
public void deleteAccount() { public void deleteAccount() {
synchronized (stateChangeLock) { synchronized (stateChangeLock) {
super.deleteAccount(); super.deleteAccount();
SharedPreferences defaultPrefs = SharedPreferences defaultPrefs = getDefaultSharedPreferences();
PreferenceManager.getDefaultSharedPreferences(appContext);
deleteAppData(prefs, defaultPrefs); deleteAppData(prefs, defaultPrefs);
} }
} }
// Package access for testing
SharedPreferences getDefaultSharedPreferences() {
return PreferenceManager.getDefaultSharedPreferences(appContext);
}
// Locking: stateChangeLock // Locking: stateChangeLock
private void deleteAppData(SharedPreferences... clear) { private void deleteAppData(SharedPreferences... clear) {
// Clear and commit shared preferences // Clear and commit shared preferences

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.account;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
@@ -25,37 +26,50 @@ import static org.briarproject.bramble.util.StringUtils.toHexString;
public class AndroidAccountManagerTest extends BrambleMockTestCase { public class AndroidAccountManagerTest extends BrambleMockTestCase {
private final SharedPreferences prefs = private final SharedPreferences prefs =
context.mock(SharedPreferences.class); context.mock(SharedPreferences.class, "prefs");
private final SharedPreferences defaultPrefs =
context.mock(SharedPreferences.class, "defaultPrefs");
private final DatabaseConfig databaseConfig = private final DatabaseConfig databaseConfig =
context.mock(DatabaseConfig.class); context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final SharedPreferences.Editor private final SharedPreferences.Editor
editor = context.mock(SharedPreferences.Editor.class); editor = context.mock(SharedPreferences.Editor.class);
private final Application app; private final Application app;
private final ApplicationInfo applicationInfo;
private final String encryptedKeyHex = toHexString(getRandomBytes(123)); private final String encryptedKeyHex = toHexString(getRandomBytes(123));
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final File keyDir = new File(testDir, "key"); private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key"); private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak"); private final File keyBackupFile = new File(keyDir, "db.key.bak");
private final File dbDir = new File(testDir, "db");
private AndroidAccountManager accountManager; private AndroidAccountManager accountManager;
public AndroidAccountManagerTest() { public AndroidAccountManagerTest() {
context.setImposteriser(ClassImposteriser.INSTANCE); context.setImposteriser(ClassImposteriser.INSTANCE);
app = context.mock(Application.class); app = context.mock(Application.class);
applicationInfo = new ApplicationInfo();
applicationInfo.dataDir = testDir.getAbsolutePath();
} }
@Before @Before
public void setUp() { public void setUp() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseDirectory();
will(returnValue(dbDir));
allowing(databaseConfig).getDatabaseKeyDirectory(); allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir)); will(returnValue(keyDir));
allowing(app).getApplicationContext(); allowing(app).getApplicationContext();
will(returnValue(app)); will(returnValue(app));
}}); }});
accountManager = new AndroidAccountManager(databaseConfig, crypto, accountManager = new AndroidAccountManager(databaseConfig, crypto,
prefs, app); prefs, app) {
@Override
SharedPreferences getDefaultSharedPreferences() {
return defaultPrefs;
}
};
} }
@Test @Test
@@ -74,12 +88,70 @@ public class AndroidAccountManagerTest extends BrambleMockTestCase {
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, accountManager.loadEncryptedDatabaseKey()); assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey());
assertTrue(keyFile.exists()); assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists()); assertTrue(keyBackupFile.exists());
} }
@Test
public void testDeleteAccountClearsSharedPrefsAndDeletesFiles()
throws Exception {
// Directories 'lib' and 'shared_prefs' should be spared
File libDir = new File(testDir, "lib");
File libFile = new File(libDir, "file");
File sharedPrefsDir = new File(testDir, "shared_prefs");
File sharedPrefsFile = new File(sharedPrefsDir, "file");
// Directory 'cache' should be emptied
File cacheDir = new File(testDir, "cache");
File cacheFile = new File(cacheDir, "file");
// Other directories should be deleted
File potatoDir = new File(testDir, ".potato");
File potatoFile = new File(potatoDir, "file");
context.checking(new Expectations() {{
oneOf(prefs).edit();
will(returnValue(editor));
oneOf(editor).clear();
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
oneOf(defaultPrefs).edit();
will(returnValue(editor));
oneOf(editor).clear();
will(returnValue(editor));
oneOf(editor).commit();
will(returnValue(true));
oneOf(app).getApplicationInfo();
will(returnValue(applicationInfo));
}});
assertTrue(dbDir.mkdirs());
assertTrue(keyDir.mkdirs());
assertTrue(libDir.mkdirs());
assertTrue(libFile.createNewFile());
assertTrue(sharedPrefsDir.mkdirs());
assertTrue(sharedPrefsFile.createNewFile());
assertTrue(cacheDir.mkdirs());
assertTrue(cacheFile.createNewFile());
assertTrue(potatoDir.mkdirs());
assertTrue(potatoFile.createNewFile());
accountManager.deleteAccount();
assertFalse(dbDir.exists());
assertFalse(keyDir.exists());
assertTrue(libDir.exists());
assertTrue(libFile.exists());
assertTrue(sharedPrefsDir.exists());
assertTrue(sharedPrefsFile.exists());
assertTrue(cacheDir.exists());
assertFalse(cacheFile.exists());
assertFalse(potatoDir.exists());
assertFalse(potatoFile.exists());
}
@After @After
public void tearDown() { public void tearDown() {
deleteTestDirectory(testDir); deleteTestDirectory(testDir);

View File

@@ -12,7 +12,7 @@ public interface AccountManager {
* Returns true if the manager has the database key. This will be false * Returns true if the manager has the database key. This will be false
* before {@link #createAccount(String)} or {@link #signIn(String)} has * before {@link #createAccount(String)} or {@link #signIn(String)} has
* been called, and true after {@link #createAccount(String)} or * been called, and true after {@link #createAccount(String)} or
* {@link #signIn(String)} has returned, until the process exits. * {@link #signIn(String)} has returned true, until the process exits.
*/ */
boolean hasDatabaseKey(); boolean hasDatabaseKey();
@@ -20,14 +20,14 @@ public interface AccountManager {
* Returns the database key if the manager has it. This will be null * Returns the database key if the manager has it. This will be null
* before {@link #createAccount(String)} or {@link #signIn(String)} has * before {@link #createAccount(String)} or {@link #signIn(String)} has
* been called, and non-null after {@link #createAccount(String)} or * been called, and non-null after {@link #createAccount(String)} or
* {@link #signIn(String)} has returned, until the process exits. * {@link #signIn(String)} has returned true, until the process exits.
*/ */
@Nullable @Nullable
SecretKey getDatabaseKey(); SecretKey getDatabaseKey();
/** /**
* Returns true if the encrypted database key can be loaded from disk and * Returns true if the encrypted database key can be loaded from disk, and
* the database directory exists. * the database directory exists and is a directory.
*/ */
boolean accountExists(); boolean accountExists();

View File

@@ -1,6 +1,7 @@
package org.briarproject.bramble.account; package org.briarproject.bramble.account;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.SecretKey;
import org.briarproject.bramble.api.db.DatabaseConfig; import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.test.BrambleMockTestCase; import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations; import org.jmock.Expectations;
@@ -22,9 +23,12 @@ import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.assertTrue;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory; import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes; import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getSecretKey;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory; import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.briarproject.bramble.util.StringUtils.toHexString; import static org.briarproject.bramble.util.StringUtils.toHexString;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public class AccountManagerImplTest extends BrambleMockTestCase { public class AccountManagerImplTest extends BrambleMockTestCase {
@@ -32,10 +36,15 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
context.mock(DatabaseConfig.class); context.mock(DatabaseConfig.class);
private final CryptoComponent crypto = context.mock(CryptoComponent.class); private final CryptoComponent crypto = context.mock(CryptoComponent.class);
private final SecretKey key = getSecretKey();
private final byte[] encryptedKey = getRandomBytes(123); private final byte[] encryptedKey = getRandomBytes(123);
private final String encryptedKeyHex = toHexString(encryptedKey); private final String encryptedKeyHex = toHexString(encryptedKey);
private final String oldEncryptedKeyHex = toHexString(getRandomBytes(123)); private final byte[] newEncryptedKey = getRandomBytes(123);
private final String newEncryptedKeyHex = toHexString(newEncryptedKey);
private final String password = "some.password";
private final String newPassword = "some.new.password";
private final File testDir = getTestDirectory(); private final File testDir = getTestDirectory();
private final File dbDir = new File(testDir, "db");
private final File keyDir = new File(testDir, "key"); private final File keyDir = new File(testDir, "key");
private final File keyFile = new File(keyDir, "db.key"); private final File keyFile = new File(keyDir, "db.key");
private final File keyBackupFile = new File(keyDir, "db.key.bak"); private final File keyBackupFile = new File(keyDir, "db.key.bak");
@@ -45,56 +54,115 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Before @Before
public void setUp() { public void setUp() {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
allowing(databaseConfig).getDatabaseDirectory();
will(returnValue(dbDir));
allowing(databaseConfig).getDatabaseKeyDirectory(); allowing(databaseConfig).getDatabaseKeyDirectory();
will(returnValue(keyDir)); will(returnValue(keyDir));
}}); }});
assertTrue(keyDir.mkdirs());
accountManager = new AccountManagerImpl(databaseConfig, crypto); accountManager = new AccountManagerImpl(databaseConfig, crypto);
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testCreatingAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).generateSecretKey();
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
}});
accountManager.createAccount(password);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testSignInReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.signIn(password));
assertFalse(accountManager.hasDatabaseKey());
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
}
@Test
public void testSignInReturnsFalseIfPasswordIsWrong() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(accountManager.signIn(password));
assertFalse(accountManager.hasDatabaseKey());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testSignInReturnsTrueIfPasswordIsRight() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(key.getBytes()));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.signIn(password));
assertTrue(accountManager.hasDatabaseKey());
SecretKey decrypted = accountManager.getDatabaseKey();
assertNotNull(decrypted);
assertArrayEquals(key.getBytes(), decrypted.getBytes());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
} }
@Test @Test
public void testDbKeyIsLoadedFromPrimaryFile() throws Exception { public void testDbKeyIsLoadedFromPrimaryFile() throws Exception {
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyFile, encryptedKeyHex); storeDatabaseKey(keyFile, encryptedKeyHex);
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey()); accountManager.loadEncryptedDatabaseKey());
assertTrue(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
} }
@Test @Test
public void testDbKeyIsLoadedFromBackupFile() throws Exception { public void testDbKeyIsLoadedFromBackupFile() throws Exception {
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
storeDatabaseKey(keyBackupFile, encryptedKeyHex); storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertEquals(encryptedKeyHex, assertEquals(encryptedKeyHex,
accountManager.loadEncryptedDatabaseKey()); accountManager.loadEncryptedDatabaseKey());
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
} }
@Test @Test
public void testDbKeyIsNullIfNotFound() { public void testDbKeyIsNullIfNotFound() {
assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists());
assertNull(accountManager.loadEncryptedDatabaseKey()); assertNull(accountManager.loadEncryptedDatabaseKey());
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
@@ -103,43 +171,156 @@ public class AccountManagerImplTest extends BrambleMockTestCase {
@Test @Test
public void testStoringDbKeyOverwritesPrimary() throws Exception { public void testStoringDbKeyOverwritesPrimary() throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertFalse(keyBackupFile.exists());
assertTrue(accountManager.storeEncryptedDatabaseKey(
newEncryptedKeyHex));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testStoringDbKeyOverwritesBackup() throws Exception {
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertFalse(keyFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.storeEncryptedDatabaseKey(
newEncryptedKeyHex));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
@Test
public void testAccountExistsReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.accountExists());
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
}
storeDatabaseKey(keyFile, oldEncryptedKeyHex); @Test
public void testAccountExistsReturnsFalseIfDbDirectoryDoesNotExist()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(keyFile.exists()); assertFalse(dbDir.exists());
assertFalse(keyBackupFile.exists());
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyFile));
assertTrue(accountManager.storeEncryptedDatabaseKey(encryptedKeyHex)); assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertFalse(dbDir.exists());
}
@Test
public void testAccountExistsReturnsFalseIfDbDirectoryIsNotDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.createNewFile());
assertFalse(dbDir.isDirectory());
assertFalse(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertFalse(dbDir.isDirectory());
}
@Test
public void testAccountExistsReturnsTrueIfDbDirectoryIsDirectory()
throws Exception {
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(dbDir.mkdirs());
assertTrue(dbDir.isDirectory());
assertTrue(accountManager.accountExists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(dbDir.exists());
assertTrue(dbDir.isDirectory());
}
@Test
public void testCreateAccountStoresDbKey() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).generateSecretKey();
will(returnValue(key));
oneOf(crypto).encryptWithPassword(key.getBytes(), password);
will(returnValue(encryptedKey));
}});
assertFalse(accountManager.hasDatabaseKey());
assertTrue(accountManager.createAccount(password));
assertTrue(accountManager.hasDatabaseKey());
SecretKey dbKey = accountManager.getDatabaseKey();
assertNotNull(dbKey);
assertArrayEquals(key.getBytes(), dbKey.getBytes());
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
} }
@Test @Test
public void testStoringDbKeyOverwritesBackup() throws Exception { public void testChangePasswordReturnsFalseIfDbKeyCannotBeLoaded() {
assertFalse(accountManager.changePassword(password, newPassword));
assertFalse(keyFile.exists()); assertFalse(keyFile.exists());
assertFalse(keyBackupFile.exists()); assertFalse(keyBackupFile.exists());
}
storeDatabaseKey(keyBackupFile, oldEncryptedKeyHex); @Test
public void testChangePasswordReturnsFalseIfPasswordIsWrong()
throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(null));
}});
assertFalse(keyFile.exists()); storeDatabaseKey(keyFile, encryptedKeyHex);
assertTrue(keyBackupFile.exists()); storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertEquals(oldEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
assertTrue(accountManager.storeEncryptedDatabaseKey(encryptedKeyHex)); assertFalse(accountManager.changePassword(password, newPassword));
assertTrue(keyFile.exists());
assertTrue(keyBackupFile.exists());
assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile)); assertEquals(encryptedKeyHex, loadDatabaseKey(keyBackupFile));
} }
@Test
public void testChangePasswordReturnsTrueIfPasswordIsRight() throws Exception {
context.checking(new Expectations() {{
oneOf(crypto).decryptWithPassword(encryptedKey, password);
will(returnValue(key.getBytes()));
oneOf(crypto).encryptWithPassword(key.getBytes(), newPassword);
will(returnValue(newEncryptedKey));
}});
storeDatabaseKey(keyFile, encryptedKeyHex);
storeDatabaseKey(keyBackupFile, encryptedKeyHex);
assertTrue(accountManager.changePassword(password, newPassword));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyFile));
assertEquals(newEncryptedKeyHex, loadDatabaseKey(keyBackupFile));
}
private void storeDatabaseKey(File f, String hex) throws IOException { private void storeDatabaseKey(File f, String hex) throws IOException {
f.getParentFile().mkdirs();
FileOutputStream out = new FileOutputStream(f); FileOutputStream out = new FileOutputStream(f);
out.write(hex.getBytes("UTF-8")); out.write(hex.getBytes("UTF-8"));
out.flush(); out.flush();