Skip to content

Instantly share code, notes, and snippets.

@harningt
Created September 22, 2012 19:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save harningt/3767416 to your computer and use it in GitHub Desktop.
Save harningt/3767416 to your computer and use it in GitHub Desktop.
Simple Android Stream Crypto
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