Created
September 22, 2012 19:00
-
-
Save harningt/3767416 to your computer and use it in GitHub Desktop.
Simple Android Stream Crypto
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package us.eharning.android.cryptosample; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.io.OutputStream; | |
import java.security.GeneralSecurityException; | |
import java.security.SecureRandom; | |
import java.security.spec.AlgorithmParameterSpec; | |
import java.security.spec.KeySpec; | |
import javax.crypto.Cipher; | |
import javax.crypto.CipherInputStream; | |
import javax.crypto.CipherOutputStream; | |
import javax.crypto.SecretKey; | |
import javax.crypto.SecretKeyFactory; | |
import javax.crypto.spec.IvParameterSpec; | |
import javax.crypto.spec.PBEKeySpec; | |
import javax.crypto.spec.SecretKeySpec; | |
class SimpleCrypto { | |
private static final String KEY_ALGORITHM = "AES"; | |
private static final byte CIPHER_STREAM_VERSION = 1; | |
private static final String DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1"; | |
/* | |
* Useful fast algorithm that doesn't change the length of the data w/ | |
* padding. | |
* AES/GCM would be excellent and would offer corruption-manipulation, but | |
* this would be more challenging to integrate since Android does not ship | |
* with it by default. | |
*/ | |
private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding"; | |
/* | |
* 128-bit IV (based on key) | |
*/ | |
private static final int IV_LENGTH = 128 / 8; | |
/* | |
* 128-bit keys should be sufficient, though could be tweaked to 192 or 256 | |
*/ | |
private static final int KEY_LENGTH = 128 / 8; | |
/* | |
* Number of iterations - longer = more secure but slower to convert PW -> key | |
*/ | |
private static final int ITERATION_COUNT = 1000; | |
/* SecureRandom - somewhat expensive to construct, but thread-safe */ | |
private static final SecureRandom RNG = new SecureRandom(); | |
/* 8 random bytes for salt is 'pretty good' */ | |
private static final int SALT_LENGTH = 8; | |
public interface SaltSource { | |
byte[] getSalt(); | |
} | |
private static class ServerSaltSource implements SaltSource { | |
private byte[] getServerSalt() { | |
return null; | |
} | |
private void setServerSalt(byte[] salt) { | |
} | |
@Override | |
public byte[] getSalt() { | |
byte[] salt = getServerSalt(); | |
if (null == salt) { | |
salt = new byte[SALT_LENGTH]; | |
RNG.nextBytes(salt); | |
setServerSalt(salt); | |
} | |
return salt; | |
} | |
} | |
private static class ApplicationSaltSource implements SaltSource { | |
/* | |
* Fake application salt, you'd want to set this up as your own random | |
* value (sequence of 8 -128 - 127 values) | |
*/ | |
private static final byte[] PER_APP_SALT = { -128, 127, 3, 29, 4, 2, 3, | |
2 }; | |
@Override | |
public byte[] getSalt() { | |
return PER_APP_SALT; | |
} | |
} | |
private final SaltSource saltSource; | |
public SimpleCrypto(boolean useServerSalt) { | |
/* | |
* Could very well be a user preference, if 'strong' then useServerSalt | |
* would be true | |
*/ | |
if (useServerSalt) { | |
saltSource = new ServerSaltSource(); | |
} else { | |
saltSource = new ApplicationSaltSource(); | |
} | |
} | |
byte[] generateKey(String password) throws GeneralSecurityException { | |
byte[] salt = saltSource.getSalt(); | |
char[] passwordChars = password.toCharArray(); | |
final int keyBitLength = KEY_LENGTH * 8; | |
KeySpec keySpec = new PBEKeySpec(passwordChars, salt, ITERATION_COUNT, keyBitLength); | |
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(DERIVATION_ALGORITHM); | |
return keyFactory.generateSecret(keySpec).getEncoded(); | |
} | |
/** | |
* Construct a new decryption stream. | |
* @param input stream to read from | |
* @param key to use for decryption | |
* @return wrapped input stream to operate on | |
* @throws IOException | |
* @throws GeneralSecurityException | |
*/ | |
public InputStream createDecryptor(InputStream input, byte[] key) throws IOException, GeneralSecurityException | |
{ | |
/* Read out version field - useful to add in case you want to change cipher later */ | |
if (CIPHER_STREAM_VERSION != input.read()) { | |
throw new Error("Unknown data format"); | |
} | |
/* Read out IV - random for every encrypted form */ | |
byte[] iv = new byte[IV_LENGTH]; | |
if (IV_LENGTH != input.read(iv)) { | |
throw new Error("Data too short"); | |
} | |
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | |
SecretKey keySpec = new SecretKeySpec(key, KEY_ALGORITHM); | |
AlgorithmParameterSpec params = new IvParameterSpec(iv); | |
cipher.init(Cipher.DECRYPT_MODE, keySpec, params); | |
CipherInputStream cipherStream = new CipherInputStream(input, cipher); | |
return cipherStream; | |
} | |
/** | |
* Construct a new encryption stream. | |
* Be sure to close this out. If you don't want the underlying stream closed, but data flushed, | |
* use a stream filter like "CloseShield*" from commons.io. | |
* @param output stream to write output data to | |
* @param key to use for encryption | |
* @return wrapped output stream to operate on | |
* @throws IOException | |
* @throws GeneralSecurityException | |
*/ | |
public OutputStream createEncryptor(OutputStream output, byte[] key) throws IOException, GeneralSecurityException | |
{ | |
/* Write out version field - useful to add in case you want to change cipher later */ | |
output.write(CIPHER_STREAM_VERSION); | |
/* Create IV - random for every encrypted form */ | |
byte[] iv = new byte[IV_LENGTH]; | |
RNG.nextBytes(iv); | |
output.write(iv); | |
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); | |
SecretKey keySpec = new SecretKeySpec(key, KEY_ALGORITHM); | |
AlgorithmParameterSpec params = new IvParameterSpec(iv); | |
cipher.init(Cipher.ENCRYPT_MODE, keySpec, params); | |
CipherOutputStream cipherStream = new CipherOutputStream(output, cipher); | |
return cipherStream; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment