This class is responsible for storing an API Key and Secret pair. The secret is encrypted with AES in CFB mode, using a key derived from a passphrase, a random salt and a number of rounds of SHA1.An additional hash of the plaintext and the salt is stored to allow for detecting incorrect passphrase entry.This is tested and working, but I would love feedback on correctness or improvements, especially areas where the code can be simplified or replaced with built-in functionality I am unaware of.The key id is a string UUID format, and the secret is provided as a base-64 encoded string.package com.bitgrind.mtgox;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.security.InvalidKeyException;import java.security.NoSuchAlgorithmException;import java.security.SecureRandom;import java.util.Arrays;import java.util.Properties;import javax.crypto.Cipher;import javax.crypto.Mac;import javax.crypto.SecretKey;import javax.crypto.spec.IvParameterSpec;import javax.crypto.spec.SecretKeySpec;import com.google.common.base.Charsets;import com.google.common.hash.HashCode;import com.google.common.hash.HashFunction;import com.google.common.hash.Hashing;import com.google.common.io.BaseEncoding;import com.google.common.io.Closer;import com.google.common.primitives.Bytes;public class ApiKey {  private static final String PROP_HASH = hash;  private static final String PROP_SALT = salt;  private static final String PROP_SECRET = secret;  private static final String PROP_KEY = key;  private static final BaseEncoding BASE64 = BaseEncoding.base64();  private static final BaseEncoding HEX = BaseEncoding.base16().lowerCase();  private static final byte[] IV = new byte[] {    0x10, 0x10, 0x01, 0x04, 0x01, 0x01, 0x01, 0x02,     0x14, 0x1c, 0x11, 0x44, 0x21, 0x4a, 0x22, 0x31   };  private static final int SALT_LENGTH = 16;  private static final int HASH_ROUNDS = 65535;  private static final String CIPHER_PARAMS = AES/OFB8/NOPADDING;  private static final SecureRandom RND;  static {    try {      RND = SecureRandom.getInstance(SHA1PRNG);    } catch (NoSuchAlgorithmException e) {      throw new AssertionError(e);    }  }  private static byte[] createRandomSalt() {    byte[] salt = new byte[SALT_LENGTH];    RND.nextBytes(salt);    return salt;  }  private static SecretKey deriveKey(byte[] salt, int iterations, String passphrase) {    final HashFunction sha1 = Hashing.sha1();    byte[] hash = new byte[salt.length + (sha1.bits() / 8)];    HashCode code = sha1.hashBytes(Bytes.concat(salt, passphrase.getBytes(Charsets.UTF_8)));    int len = code.writeBytesTo(hash, 0, hash.length);    System.arraycopy(salt, 0, hash, len, salt.length);    for (int i=0; i < iterations; i++) {      code = sha1.hashBytes(hash);      len = code.writeBytesTo(hash, 0, hash.length);      System.arraycopy(salt, 0, hash, len, salt.length);    }    return new SecretKeySpec(code.asBytes(), 0, 16, AES);  }  public static final ApiKeyResult read(InputStream in, String passphrase) {    Properties p = new Properties();    try {      p.load(in);      String apiKey = p.getProperty(PROP_KEY, );      byte[] encryptedKey = BASE64.decode(p.getProperty(PROP_SECRET, 0));      byte[] salt = HEX.decode(p.getProperty(PROP_SALT, 0));      byte[] hash = HEX.decode(p.getProperty(PROP_HASH, 0));      SecretKey key = deriveKey(salt, HASH_ROUNDS, passphrase);      Cipher aes =  Cipher.getInstance(CIPHER_PARAMS);      aes.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(IV));      byte[] decryptedKey = aes.doFinal(encryptedKey);      byte[] computedHash = Hashing.sha512().newHasher().putBytes(salt).putBytes(decryptedKey).hash().asBytes();      if (!Arrays.equals(hash, computedHash)) {        return new ApiKeyResult(Wrong passphrase);      }      return new ApiKeyResult(new ApiKey(apiKey, BASE64.encode(decryptedKey)));    } catch (Exception e) {      return new ApiKeyResult(e);    }  }  private final String key;  private final String secret;  ApiKey(String key, String secret) {    this.key = key;    this.secret = secret;  }  public byte[] getKeyBytes() {    return HEX.decode(key.replace(-, ));  }  public byte[] signMessage(byte[] content) throws InvalidKeyException {    Mac mac;    try {      mac = Mac.getInstance(HmacSHA512);    } catch (NoSuchAlgorithmException e) {      throw new AssertionError(e);    }    SecretKeySpec secretKey = new SecretKeySpec(BASE64.decode(secret), HmacSHA512);    mac.init(secretKey);    return mac.doFinal(content);  }  public final ApiKeyResult write(String passphrase, OutputStream out) {    Properties p = new Properties();    try {      byte[] salt = createRandomSalt();      SecretKey aesKey = deriveKey(salt, HASH_ROUNDS, passphrase);      Cipher aes =  Cipher.getInstance(CIPHER_PARAMS);      aes.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(IV));      byte[] decryptedBytes = BASE64.decode(secret);      byte[] encryptedKey = aes.doFinal(decryptedBytes);      p.put(PROP_KEY, key);      p.put(PROP_SECRET, BASE64.encode(encryptedKey));      p.put(PROP_SALT, HEX.encode(salt));      p.put(PROP_HASH, Hashing.sha512().newHasher().putBytes(salt).putBytes(decryptedBytes).hash().toString());      p.store(out, null);      out.flush();      return new ApiKeyResult();    } catch (Exception e) {      return new ApiKeyResult(e);    }  }}
API Key storage using AES encryption in Java
java;security;cryptography